diff --git a/Documentation/edgefs-monitoring.md b/Documentation/edgefs-monitoring.md index 99cb6e3b5..ed1ed132a 100644 --- a/Documentation/edgefs-monitoring.md +++ b/Documentation/edgefs-monitoring.md @@ -1,10 +1,10 @@ --- -title: Monitoring +title: EdgeFS Monitoring weight: 48 indent: true --- -# Monitoring +# EdgeFS Monitoring Each Rook EdgeFS cluster has some built in metrics collectors/exporters for monitoring with [Prometheus](https://prometheus.io/). If you do not have Prometheus running, follow the steps below to enable monitoring of Rook. If your cluster already diff --git a/Documentation/edgefs-ui.md b/Documentation/edgefs-ui.md new file mode 100755 index 000000000..f4f7544d8 --- /dev/null +++ b/Documentation/edgefs-ui.md @@ -0,0 +1,47 @@ +--- +title: EdgeFS User Interface +weight: 49 +indent: true +--- + +# EdgeFS Dashboard and User Interface + +EdgeFS comes with built-in local cluster Dashboard and User Interface. + +The dashboard is a very helpful tool to give you an overview of the status of your cluster, including overall health, +status of the corosync quorum, status of the VDEVs, and other EdgeFS services. + +![EdgeFS UI Dashboard](media/edgefs-ui-dashboard.png) + +Rook EdgeFS Operator enables the dashboard by default. +A K8s service will be created to expose HTTP/S ports inside the cluster. + +This example shows what ports was configured for EdgeFS right after successful cluster deployment: +```bash +# kubectl get svc -n rook-edgefs +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +rook-edgefs-restapi NodePort 10.105.177.93 8881:32135/TCP,8080:31363/TCP,4443:31485/TCP 3h14m +rook-edgefs-ui NodePort 10.97.70.209 3000:32048/TCP,3443:30273/TCP 3h14m +rook-edgefs-mgr NodePort 10.108.199.1 6789:31370/TCP 3h14m +``` + +The `rook-edgefs-restapi` service is used for the [Prometheus metrics](edgefs-monitoring.md) scrape and REST API endpoints. +REST API endpoint is used by Console and Graphical UIs. It exposing ClusterIPs with HTTP ports 8080 and 4443(SSL). NodePort(s) for external access automatically pre-created. + +The `rook-edgefs-ui` service is used to expose browser based User Interface ports. +Using NodePort you will be able to connect to the dashboard by direct node IP and provided port number, in this example at `http://NODEIP:32048`. + +The `rook-edgefs-mgr` service is for gRPC communication between the [EdgeFS CSI Provisioner](edgefs-si.md), REST API and services. It exposes cluster internal port 6789. + +## Dynamic EdgeFS Service CRD editing + +One of the very useful features of EdgeFS Dashboard is Dynamic Service CRD editing UI. Ability to edit CRDs via intuitive graphical inteface can simplify on-going EdgeFS cluster management drastically. For instance, below picture demostrantes how NFS CRD service can be dynamically edited, exports can be added or removed: + +![EdgeFS UI NFS Editing](media/edgefs-ui-nfs-edit.png) + +EdgeFS UI renders user requests and sends them to the REST API where service CRD is constructed, validated and executed. Certain parameters can only be updated within a cluster service definition itself, and as such this UI blends the two, thus providing simplified management functionality. + +### Credentials + +After you connect to the dashboard you will need to login for secure access. Rook EdgeFS operator creates a default user named +`admin` and password `edgefs`. It can be changed later via `neadm system passwd` command executed within `mgr` pod. diff --git a/Documentation/media/edgefs-ui-dashboard.png b/Documentation/media/edgefs-ui-dashboard.png new file mode 100644 index 000000000..31c589b4e Binary files /dev/null and b/Documentation/media/edgefs-ui-dashboard.png differ diff --git a/Documentation/media/edgefs-ui-nfs-edit.png b/Documentation/media/edgefs-ui-nfs-edit.png new file mode 100644 index 000000000..fad41d819 Binary files /dev/null and b/Documentation/media/edgefs-ui-nfs-edit.png differ diff --git a/cluster/examples/kubernetes/edgefs/cluster.yaml b/cluster/examples/kubernetes/edgefs/cluster.yaml index d7f4d996d..40503121c 100644 --- a/cluster/examples/kubernetes/edgefs/cluster.yaml +++ b/cluster/examples/kubernetes/edgefs/cluster.yaml @@ -18,6 +18,9 @@ rules: - apiGroups: [""] resources: ["configmaps", "endpoints"] verbs: [ "get", "list", "watch", "create", "update", "delete" ] +- apiGroups: ["edgefs.rook.io"] + resources: ["*"] + verbs: ["*"] - apiGroups: [""] resources: ["pods"] verbs: [ "get", "list" ] diff --git a/pkg/operator/edgefs/cluster/mgr/mgr.go b/pkg/operator/edgefs/cluster/mgr/mgr.go index 7e567e440..40a3df9a4 100644 --- a/pkg/operator/edgefs/cluster/mgr/mgr.go +++ b/pkg/operator/edgefs/cluster/mgr/mgr.go @@ -38,11 +38,15 @@ var logger = capnslog.NewPackageLogger("github.com/rook/rook", "edgefs-op-mgr") const ( appName = "rook-edgefs-mgr" + restapiSvcName = "rook-edgefs-restapi" + uiSvcName = "rook-edgefs-ui" defaultServiceAccountName = "rook-edgefs-cluster" defaultGrpcPort = 6789 defaultRestPort = 8080 defaultRestSPort = 4443 defaultMetricsPort = 8881 + defaultUiPort = 3000 + defaultUiSPort = 3443 /* Volumes definitions */ dataVolumeName = "edgefs-datadir" @@ -121,14 +125,36 @@ func (c *Cluster) Start(rookImage string) error { } // create the mgr service - service := c.makeMgrService(appName) - if _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Create(service); err != nil { + mgrService := c.makeMgrService(appName) + if _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Create(mgrService); err != nil { if !errors.IsAlreadyExists(err) { return fmt.Errorf("failed to create mgr service. %+v", err) } - logger.Infof("mgr mgr service already exists") + logger.Infof("mgr service already exists") } else { - logger.Infof("mgr mgr service started") + logger.Infof("mgr service started") + } + + // create the restapi service + restapiService := c.makeRestapiService(restapiSvcName) + if _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Create(restapiService); err != nil { + if !errors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create restapi service. %+v", err) + } + logger.Infof("restapi service already exists") + } else { + logger.Infof("restapi service started") + } + + // create the ui/dashboard service + uiService := c.makeUiService(uiSvcName) + if _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Create(uiService); err != nil { + if !errors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create ui service. %+v", err) + } + logger.Infof("ui service already exists") + } else { + logger.Infof("ui service started") } return nil @@ -144,25 +170,45 @@ func (c *Cluster) makeMgrService(name string) *v1.Service { }, Spec: v1.ServiceSpec{ Selector: labels, - Type: v1.ServiceTypeClusterIP, + Type: v1.ServiceTypeNodePort, Ports: []v1.ServicePort{ { Name: "mgr", Port: int32(defaultGrpcPort), Protocol: v1.ProtocolTCP, }, + }, + }, + } + + k8sutil.SetOwnerRef(c.context.Clientset, c.Namespace, &svc.ObjectMeta, &c.ownerRef) + return svc +} + +func (c *Cluster) makeRestapiService(name string) *v1.Service { + labels := c.getLabels() + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: c.Namespace, + Labels: labels, + }, + Spec: v1.ServiceSpec{ + Selector: labels, + Type: v1.ServiceTypeNodePort, + Ports: []v1.ServicePort{ { Name: "http-metrics", Port: int32(defaultMetricsPort), Protocol: v1.ProtocolTCP, }, { - Name: "http-mgmt", + Name: "http-restapi", Port: int32(defaultRestPort), Protocol: v1.ProtocolTCP, }, { - Name: "https-mgmt", + Name: "https-restapi", Port: int32(defaultRestSPort), Protocol: v1.ProtocolTCP, }, @@ -174,6 +220,36 @@ func (c *Cluster) makeMgrService(name string) *v1.Service { return svc } +func (c *Cluster) makeUiService(name string) *v1.Service { + labels := c.getLabels() + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: c.Namespace, + Labels: labels, + }, + Spec: v1.ServiceSpec{ + Selector: labels, + Type: v1.ServiceTypeNodePort, + Ports: []v1.ServicePort{ + { + Name: "http-ui", + Port: int32(defaultUiPort), + Protocol: v1.ProtocolTCP, + }, + { + Name: "https-ui", + Port: int32(defaultUiSPort), + Protocol: v1.ProtocolTCP, + }, + }, + }, + } + + k8sutil.SetOwnerRef(c.context.Clientset, c.Namespace, &svc.ObjectMeta, &c.ownerRef) + return svc +} + func (c *Cluster) makeDeployment(name, clusterName, rookImage string, replicas int32) *extensions.Deployment { volumes := []v1.Volume{} @@ -215,12 +291,13 @@ func (c *Cluster) makeDeployment(name, clusterName, rookImage string, replicas i }, Spec: v1.PodSpec{ ServiceAccountName: c.serviceAccount, - Containers: []v1.Container{c.mgmtContainer(name, "edgefs/edgefs-restapi:"+rookImageVer), c.mgrContainer("grpc", rookImage)}, - RestartPolicy: v1.RestartPolicyAlways, - Volumes: volumes, - HostIPC: true, - HostNetwork: isHostNetworkDefined(c.HostNetworkSpec), - NodeSelector: map[string]string{c.Namespace: "cluster"}, + Containers: []v1.Container{c.mgmtContainer(name, "edgefs/edgefs-restapi:"+rookImageVer), + c.mgrContainer("grpc", rookImage), c.uiContainer("ui", "edgefs/edgefs-ui:"+rookImageVer)}, + RestartPolicy: v1.RestartPolicyAlways, + Volumes: volumes, + HostIPC: true, + HostNetwork: isHostNetworkDefined(c.HostNetworkSpec), + NodeSelector: map[string]string{c.Namespace: "cluster"}, }, } if isHostNetworkDefined(c.HostNetworkSpec) { @@ -239,6 +316,43 @@ func (c *Cluster) makeDeployment(name, clusterName, rookImage string, replicas i return d } +func (c *Cluster) uiContainer(name string, containerImage string) v1.Container { + + runAsUser := int64(0) + readOnlyRootFilesystem := false + securityContext := &v1.SecurityContext{ + RunAsUser: &runAsUser, + ReadOnlyRootFilesystem: &readOnlyRootFilesystem, + } + + return v1.Container{ + Name: name, + Image: containerImage, + ImagePullPolicy: v1.PullAlways, + Args: []string{}, + Env: []v1.EnvVar{ + { + Name: "API_ENDPOINT", + Value: "http://0.0.0.0:8080", + }, + }, + SecurityContext: securityContext, + Resources: c.resources, + Ports: []v1.ContainerPort{ + { + Name: "http-ui", + ContainerPort: int32(defaultUiPort), + Protocol: v1.ProtocolTCP, + }, + { + Name: "https-ui", + ContainerPort: int32(defaultUiSPort), + Protocol: v1.ProtocolTCP, + }, + }, + } +} + func (c *Cluster) mgmtContainer(name string, containerImage string) v1.Container { runAsUser := int64(0) diff --git a/pkg/operator/edgefs/cluster/mgr/mgr_test.go b/pkg/operator/edgefs/cluster/mgr/mgr_test.go index f8dbdaa00..60cf3daac 100644 --- a/pkg/operator/edgefs/cluster/mgr/mgr_test.go +++ b/pkg/operator/edgefs/cluster/mgr/mgr_test.go @@ -117,7 +117,7 @@ func TestServiceSpec(t *testing.T) { s := c.makeMgrService("rook-edgefs-mgr") assert.NotNil(t, s) assert.Equal(t, "rook-edgefs-mgr", s.Name) - assert.Equal(t, 4, len(s.Spec.Ports)) + assert.Equal(t, 1, len(s.Spec.Ports)) } func TestHostNetwork(t *testing.T) {