diff --git a/Documentation/ceph-dashboard.md b/Documentation/ceph-dashboard.md index cdac5a337..12b1533c4 100755 --- a/Documentation/ceph-dashboard.md +++ b/Documentation/ceph-dashboard.md @@ -51,14 +51,29 @@ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o yaml | grep "pas ## Configure the Dashboard -If you are accessing the dashboard via a reverse proxy, you may wish to serve it under a URL prefix. -To get the dashboard to use hyperlinks that include your prefix, you can set the `urlPrefix` setting: +The following dashboard configuration settings are supported: + ```yaml spec: dashboard: urlPrefix: /ceph-dashboard + port: 8443 + ssl: true ``` +* `urlPrefix` If you are accessing the dashboard via a reverse proxy, you may + wish to serve it under a URL prefix. To get the dashboard to use hyperlinks + that include your prefix, you can set the `urlPrefix` setting. + +* `port` The port that the dashboard is served on may be changed from the + default using the `port` setting. The corresponding K8s service exposing the + port will automatically be updated. + +* `ssl` The dashboard may be served without SSL (useful for when you deploy the + dashboard behind a proxy already served using SSL) by setting the `ssl` option + to be false. Note that the ssl setting will be ignored in Luminous as well as + Mimic 13.2.2 or older where it is not supported + ## Viewing the Dashboard External to the Cluster Commonly you will want to view the dashboard from outside the cluster. For example, on a development machine with the diff --git a/PendingReleaseNotes.md b/PendingReleaseNotes.md index 1274cda13..c8c89d1f5 100644 --- a/PendingReleaseNotes.md +++ b/PendingReleaseNotes.md @@ -11,6 +11,9 @@ - Use `Readiness` and `Liveness` probes. - Updated automatically on Object Store CRD changes. +- Added the dashboard `port` configuration setting. +- Added the dashboard `ssl` configuration setting. + ## Breaking Changes - Rook no longer supports Kubernetes `1.8` and `1.9`. diff --git a/cluster/charts/rook-ceph/templates/resources.yaml b/cluster/charts/rook-ceph/templates/resources.yaml index fddc570f2..7a22a68fc 100644 --- a/cluster/charts/rook-ceph/templates/resources.yaml +++ b/cluster/charts/rook-ceph/templates/resources.yaml @@ -32,6 +32,10 @@ spec: type: boolean urlPrefix: type: string + port: + type: integer + minimum: 0 + maximum: 65535 dataDirHostPath: pattern: ^/(\S+) type: string diff --git a/cluster/examples/kubernetes/ceph/cluster.yaml b/cluster/examples/kubernetes/ceph/cluster.yaml index 2128a8b40..48cfeeb41 100755 --- a/cluster/examples/kubernetes/ceph/cluster.yaml +++ b/cluster/examples/kubernetes/ceph/cluster.yaml @@ -179,6 +179,10 @@ spec: enabled: true # serve the dashboard under a subpath (useful when you are accessing the dashboard via a reverse proxy) # urlPrefix: /ceph-dashboard + # serve the dashboard at the given port. + # port: 8443 + # serve the dashboard using SSL + # ssl: true network: # toggle to use hostNetwork hostNetwork: false diff --git a/cluster/examples/kubernetes/ceph/operator.yaml b/cluster/examples/kubernetes/ceph/operator.yaml index a0b092ae2..6dea64201 100755 --- a/cluster/examples/kubernetes/ceph/operator.yaml +++ b/cluster/examples/kubernetes/ceph/operator.yaml @@ -36,6 +36,8 @@ spec: type: boolean urlPrefix: type: string + port: + type: integer dataDirHostPath: pattern: ^/(\S+) type: string diff --git a/pkg/apis/ceph.rook.io/v1/types.go b/pkg/apis/ceph.rook.io/v1/types.go index cd49bd2ee..7fb33c66c 100755 --- a/pkg/apis/ceph.rook.io/v1/types.go +++ b/pkg/apis/ceph.rook.io/v1/types.go @@ -94,6 +94,10 @@ type DashboardSpec struct { Enabled bool `json:"enabled,omitempty"` // A prefix for all URLs to use the dashboard with a reverse proxy UrlPrefix string `json:"urlPrefix,omitempty"` + // The dashboard webserver port + Port int `json:"port,omitempty"` + // Whether SSL should be used + SSL *bool `json:"ssl,omitempty"` } type ClusterStatus struct { diff --git a/pkg/operator/ceph/cluster/cluster.go b/pkg/operator/ceph/cluster/cluster.go index 71255d1b5..9df87c45c 100755 --- a/pkg/operator/ceph/cluster/cluster.go +++ b/pkg/operator/ceph/cluster/cluster.go @@ -21,6 +21,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "strings" "time" @@ -262,6 +263,27 @@ func clusterChanged(oldCluster, newCluster cephv1.ClusterSpec, clusterRef *clust changeFound = true } + if oldCluster.Dashboard.Port != newCluster.Dashboard.Port { + logger.Infof("dashboard port has changed from \"%d\" to \"%d\"", oldCluster.Dashboard.Port, newCluster.Dashboard.Port) + changeFound = true + } + + if (oldCluster.Dashboard.SSL == nil && newCluster.Dashboard.SSL != nil) || + (oldCluster.Dashboard.SSL != nil && newCluster.Dashboard.SSL == nil) || + (oldCluster.Dashboard.SSL != nil && newCluster.Dashboard.SSL != nil && + *oldCluster.Dashboard.SSL != *newCluster.Dashboard.SSL) { + oldSSL := "" + if oldCluster.Dashboard.SSL != nil { + oldSSL = strconv.FormatBool(*oldCluster.Dashboard.SSL) + } + newSSL := "" + if newCluster.Dashboard.SSL != nil { + newSSL = strconv.FormatBool(*newCluster.Dashboard.SSL) + } + logger.Infof("dashboard ssl option has changed from \"%s\" to \"%s\"", oldSSL, newSSL) + changeFound = true + } + if oldCluster.Mon.Count != newCluster.Mon.Count { logger.Infof("number of mons have changed from %d to %d. The health check will update the mons...", oldCluster.Mon.Count, newCluster.Mon.Count) clusterRef.mons.MonCountMutex.Lock() diff --git a/pkg/operator/ceph/cluster/mgr/dashboard.go b/pkg/operator/ceph/cluster/mgr/dashboard.go index e1814bc94..24da5c6de 100755 --- a/pkg/operator/ceph/cluster/mgr/dashboard.go +++ b/pkg/operator/ceph/cluster/mgr/dashboard.go @@ -21,6 +21,7 @@ import ( "fmt" "math/rand" "os/exec" + "strconv" "syscall" "time" @@ -54,7 +55,7 @@ func init() { func (c *Cluster) configureDashboard(port int) error { // enable or disable the dashboard module - if err := c.toggleDashboardModule(); err != nil { + if err := c.toggleDashboardModule(port); err != nil { return err } @@ -66,6 +67,17 @@ func (c *Cluster) configureDashboard(port int) error { return fmt.Errorf("failed to create dashboard mgr service. %+v", err) } logger.Infof("dashboard service already exists") + original, err := c.context.Clientset.CoreV1().Services(c.Namespace).Get(dashboardService.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get dashboard service. %+v", err) + } + if original.Spec.Ports[0].Port != int32(port) { + logger.Infof("dashboard port changed. updating service") + original.Spec.Ports[0].Port = int32(port) + if _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Update(original); err != nil { + return fmt.Errorf("failed to update dashboard mgr service. %+v", err) + } + } } else { logger.Infof("dashboard service started") } @@ -81,7 +93,7 @@ func (c *Cluster) configureDashboard(port int) error { } // Ceph docs about the dashboard module: http://docs.ceph.com/docs/luminous/mgr/dashboard/ -func (c *Cluster) toggleDashboardModule() error { +func (c *Cluster) toggleDashboardModule(dashboardPort int) error { if c.dashboard.Enabled { if err := client.MgrEnableModule(c.context, c.Namespace, dashboardModuleName, true); err != nil { return fmt.Errorf("failed to enable mgr dashboard module. %+v", err) @@ -91,7 +103,7 @@ func (c *Cluster) toggleDashboardModule() error { return fmt.Errorf("failed to initialize dashboard. %+v", err) } - if err := c.configureDashboardModule(); err != nil { + if err := c.configureDashboardModule(dashboardPort); err != nil { return fmt.Errorf("failed to configure mgr dashboard module. %+v", err) } } else { @@ -102,11 +114,34 @@ func (c *Cluster) toggleDashboardModule() error { return nil } -func (c *Cluster) configureDashboardModule() error { +func (c *Cluster) configureDashboardModule(dashboardPort int) error { + // url prefix hasChanged, err := client.MgrSetAllConfig(c.context, c.Namespace, c.cephVersion.Name, "mgr/dashboard/url_prefix", c.dashboard.UrlPrefix) if err != nil { return err } + + // server port + port := strconv.Itoa(dashboardPort) + changed, err := client.MgrSetAllConfig(c.context, c.Namespace, c.cephVersion.Name, "mgr/dashboard/server_port", port) + if err != nil { + return err + } + hasChanged = hasChanged || changed + + // ssl support + var ssl string + if c.dashboard.SSL == nil { + ssl = "" + } else { + ssl = strconv.FormatBool(*c.dashboard.SSL) + } + changed, err = client.MgrSetAllConfig(c.context, c.Namespace, c.cephVersion.Name, "mgr/dashboard/ssl", ssl) + if err != nil { + return err + } + hasChanged = hasChanged || changed + if hasChanged { logger.Infof("dashboard config has changed") return c.restartDashboard() @@ -128,12 +163,14 @@ func (c *Cluster) initializeSecureDashboard() error { return fmt.Errorf("failed to generate a password. %+v", err) } - alreadyCreated, err := c.createSelfSignedCert() - if err != nil { - return fmt.Errorf("failed to create a self signed cert. %+v", err) - } - if alreadyCreated { - return nil + if c.dashboard.SSL == nil || *c.dashboard.SSL { + alreadyCreated, err := c.createSelfSignedCert() + if err != nil { + return fmt.Errorf("failed to create a self signed cert. %+v", err) + } + if alreadyCreated { + return nil + } } if err := c.setLoginCredentials(password); err != nil { diff --git a/pkg/operator/ceph/cluster/mgr/dashboard_test.go b/pkg/operator/ceph/cluster/mgr/dashboard_test.go index 0505c8f18..a5c9bcad9 100644 --- a/pkg/operator/ceph/cluster/mgr/dashboard_test.go +++ b/pkg/operator/ceph/cluster/mgr/dashboard_test.go @@ -103,9 +103,10 @@ func TestStartSecureDashboard(t *testing.T) { dashboardInitWaitTime = 0 err := c.configureDashboard(dashboardPortHttp) assert.Nil(t, err) - // the dashboard is enabled, then disabled and enabled again to restart it with the cert - assert.Equal(t, 2, enables) - assert.Equal(t, 1, disables) + // the dashboard is enabled, then disabled and enabled again to restart + // it with the cert, and another restart when setting the dashboard port + assert.Equal(t, 3, enables) + assert.Equal(t, 2, disables) assert.Equal(t, 2, moduleRetries) svc, err := c.context.Clientset.CoreV1().Services(c.Namespace).Get("rook-ceph-mgr-dashboard", metav1.GetOptions{}) @@ -116,8 +117,8 @@ func TestStartSecureDashboard(t *testing.T) { c.dashboard.Enabled = false err = c.configureDashboard(dashboardPortHttp) assert.Nil(t, err) - assert.Equal(t, 2, enables) - assert.Equal(t, 2, disables) + assert.Equal(t, 3, enables) + assert.Equal(t, 3, disables) svc, err = c.context.Clientset.CoreV1().Services(c.Namespace).Get("rook-ceph-mgr-dashboard", metav1.GetOptions{}) assert.NotNil(t, err) diff --git a/pkg/operator/ceph/cluster/mgr/mgr.go b/pkg/operator/ceph/cluster/mgr/mgr.go index 1b3f4ea1f..88eb5db29 100644 --- a/pkg/operator/ceph/cluster/mgr/mgr.go +++ b/pkg/operator/ceph/cluster/mgr/mgr.go @@ -90,9 +90,17 @@ var updateDeploymentAndWait = k8sutil.UpdateDeploymentAndWait func (c *Cluster) Start() error { logger.Infof("start running mgr") - dashboardPort := dashboardPortHttps - if c.cephVersion.Name == cephv1.Luminous { - dashboardPort = dashboardPortHttp + var dashboardPort int + if c.dashboard.Port == 0 { + // select default ports + if c.cephVersion.Name == cephv1.Luminous { + dashboardPort = dashboardPortHttp + } else { + dashboardPort = dashboardPortHttps + } + } else { + // crd validates port >= 0 + dashboardPort = c.dashboard.Port } for i := 0; i < c.Replicas; i++ { diff --git a/pkg/operator/ceph/cluster/mgr/mgr_test.go b/pkg/operator/ceph/cluster/mgr/mgr_test.go index bf8f50994..c6b7d7de1 100755 --- a/pkg/operator/ceph/cluster/mgr/mgr_test.go +++ b/pkg/operator/ceph/cluster/mgr/mgr_test.go @@ -61,6 +61,7 @@ func TestStartMGR(t *testing.T) { testopk8s.ClearDeploymentsUpdated(deploymentsUpdated) c.dashboard.UrlPrefix = "/test" + c.dashboard.Port = 12345 err = c.Start() assert.Nil(t, err) validateStart(t, c) @@ -92,9 +93,16 @@ func validateStart(t *testing.T, c *Cluster) { _, err := c.context.Clientset.CoreV1().Services(c.Namespace).Get("rook-ceph-mgr", metav1.GetOptions{}) assert.Nil(t, err) - _, err = c.context.Clientset.CoreV1().Services(c.Namespace).Get("rook-ceph-mgr-dashboard", metav1.GetOptions{}) + ds, err := c.context.Clientset.CoreV1().Services(c.Namespace).Get("rook-ceph-mgr-dashboard", metav1.GetOptions{}) if c.dashboard.Enabled { assert.Nil(t, err) + if c.dashboard.Port == 0 { + // port=0 -> default port + assert.Equal(t, ds.Spec.Ports[0].Port, int32(dashboardPortHttps)) + } else { + // non-zero ports are configured as-is + assert.Equal(t, ds.Spec.Ports[0].Port, int32(c.dashboard.Port)) + } } else { assert.True(t, errors.IsNotFound(err)) }