From 88a24090a54727aa1c5cf43d11a3549ce3517798 Mon Sep 17 00:00:00 2001 From: raihankhan Date: Wed, 1 Jan 2025 23:51:47 +0600 Subject: [PATCH 1/5] Add doc for recommendation Signed-off-by: raihankhan --- docs/guides/elasticsearch/cli/cli.md | 2 +- .../elasticsearch/recommendation/_index.md | 10 + .../images/recommendation-generation.png | Bin 0 -> 67084 bytes .../elasticsearch/recommendation/overview.md | 45 +++ .../rotate-auth-recommendation.md | 100 ++++++ .../rotate-tls-recommendation.md | 100 ++++++ .../version-update-recommendation.md | 326 ++++++++++++++++++ 7 files changed, 582 insertions(+), 1 deletion(-) create mode 100755 docs/guides/elasticsearch/recommendation/_index.md create mode 100644 docs/guides/elasticsearch/recommendation/images/recommendation-generation.png create mode 100644 docs/guides/elasticsearch/recommendation/overview.md create mode 100644 docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md create mode 100644 docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md create mode 100644 docs/guides/elasticsearch/recommendation/version-update-recommendation.md diff --git a/docs/guides/elasticsearch/cli/cli.md b/docs/guides/elasticsearch/cli/cli.md index 540a6b911d..223ce51d0f 100644 --- a/docs/guides/elasticsearch/cli/cli.md +++ b/docs/guides/elasticsearch/cli/cli.md @@ -5,7 +5,7 @@ menu: identifier: es-cli-cli name: Quickstart parent: es-cli-elasticsearch - weight: 100 + weight: 110 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/elasticsearch/recommendation/_index.md b/docs/guides/elasticsearch/recommendation/_index.md new file mode 100755 index 0000000000..7111595f40 --- /dev/null +++ b/docs/guides/elasticsearch/recommendation/_index.md @@ -0,0 +1,10 @@ +--- +title: Elasticsearch Recommendation +menu: + docs_{{ .version }}: + identifier: es-recommendation-elasticsearch + name: Recommendation + parent: es-elasticsearch-guides + weight: 100 +menu_name: docs_{{ .version }} +--- diff --git a/docs/guides/elasticsearch/recommendation/images/recommendation-generation.png b/docs/guides/elasticsearch/recommendation/images/recommendation-generation.png new file mode 100644 index 0000000000000000000000000000000000000000..dd96452a27e09599fdc1a6924e587f041f0c90a6 GIT binary patch literal 67084 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/guides/elasticsearch/recommendation/overview.md b/docs/guides/elasticsearch/recommendation/overview.md new file mode 100644 index 0000000000..b14b98690a --- /dev/null +++ b/docs/guides/elasticsearch/recommendation/overview.md @@ -0,0 +1,45 @@ +--- +title: Elasticsearch Recommendation +description: Elasticsearch Recommendation Overview +menu: + docs_{{ .version }}: + identifier: es-recommendation-overview + name: Overview + parent: es-recommendation-elasticsearch + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Recommendation for KubeDB managed Elasticsearch + +Databases on Kubernetes in production grade infrastructure often need to go through several administrative operations depending on specific resource requirements. Such operations include vertical scaling (cpu, memory) and storage expansion. Autoscaling support for KubeDB managed databases takes care of it. However, databases also need to go through some maintenance operations in order to ensure security, enhance performance, getting bug fixes and new features etc. Such operations mostly require organization's manual intervention. Even if these operations are automated, they need to be done in surveillance. KubeDB simplifies this by generating K8s Native Recommendations. + +## Overview + +Recommendation is a custom resource definition object which is created by KubeDB ops-manager controller and managed by supervisor. So, You need to have KubeDB and Supervisor installed first. You can simply install supervisor along with other KubeDB components using `--set supervisor.enabled=true` flag while installing KubeDB via helm chart. + +

+Recommendation Generation +

+ +KubeDB provisioner watches user provided database custom resource spec and creates/sync all the necessary DB resources. Once the Database is ready KubeDB Ops-manager watches the DB and creates Recommendation if it requires. KubeDB Supervisor then watches the Recommendation, updates status of the recommendation, creates recommended operation via OpsRequest if deadline reaches or manually triggered and watches the OpsRequest status to update accordingly in Recommendation custom resource. + +KubeDB provides Three types of recommendation for Elasticsearch and Opensearch. + +1. [Version Update Recommendation](/docs/guides/elasticsearch/recommendation/version-update-recommendation.md) +2. TLS Certificate Rotation Recommendation +3. Authentication Secret Rotation Recommendation + +The next page describes these recommendations, how to enable/disable them, their generation mechanism and usability. + +## Next Steps + +- Learn how to monitor Elasticsearch database with KubeDB using [builtin-Prometheus](/docs/guides/elasticsearch/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). +- Learn how to monitor PostgreSQL database with KubeDB using [builtin-Prometheus](/docs/guides/postgres/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/postgres/monitoring/using-prometheus-operator.md). +- Learn how to monitor MySQL database with KubeDB using [builtin-Prometheus](/docs/guides/mysql/monitoring/builtin-prometheus/index.md) and using [Prometheus operator](/docs/guides/mysql/monitoring/prometheus-operator/index.md). +- Learn how to monitor MongoDB database with KubeDB using [builtin-Prometheus](/docs/guides/mongodb/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). +- Learn how to monitor Redis server with KubeDB using [builtin-Prometheus](/docs/guides/redis/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/redis/monitoring/using-prometheus-operator.md). +- Learn how to monitor Memcached server with KubeDB using [builtin-Prometheus](/docs/guides/memcached/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/memcached/monitoring/using-prometheus-operator.md). diff --git a/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md new file mode 100644 index 0000000000..3f0c3c8d91 --- /dev/null +++ b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md @@ -0,0 +1,100 @@ +--- +title: Elasticsearch Version Update Recommendation +menu: + docs_{{ .version }}: + identifier: es-version-update-recommendation + name: Version Update Recommendation + parent: es-recommendation-elasticsearch + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Elasticsearch Version Update Recommendation + +Rotating authentication secrets in database management is vital to mitigate security risks, such as credential leakage or unauthorized access, and to comply with regulatory requirements. Regular rotation limits the exposure of compromised credentials, reduces the risk of insider threats, and enforces updated security policies like stronger passwords or algorithms. It also ensures operational resilience by testing the rotation process and revoking stale or unused credentials. KubeDB provides `RotateAuth OpsRequest` which reduces manual errors, and strengthens database security with minimal effort. KubeDB Ops-manager generates Recommendation for rotating authentication secrets via this OpsRequest. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Rotate Auth recommendation regarding three particular cases. + +1. There's been an update in the current version image +2. There's a new version available with a minor/patch fix. +3. There's a new major version available + +Let's go through a demo to see version update recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. + +```bash +$ kubectl get elasticsearchversions | grep xpack +xpack-6.8.23 6.8.23 ElasticStack ghcr.io/appscode-images/elastic:6.8.23 17h +xpack-7.13.4 7.13.4 ElasticStack ghcr.io/appscode-images/elastic:7.13.4 17h +xpack-7.14.2 7.14.2 ElasticStack ghcr.io/appscode-images/elastic:7.14.2 17h +xpack-7.16.3 7.16.3 ElasticStack ghcr.io/appscode-images/elastic:7.16.3 17h +xpack-7.17.15 7.17.15 ElasticStack ghcr.io/appscode-images/elastic:7.17.15 17h +xpack-7.17.23 7.17.23 ElasticStack ghcr.io/appscode-images/elastic:7.17.23 17h +xpack-7.17.25 7.17.25 ElasticStack ghcr.io/appscode-images/elastic:7.17.25 16h +xpack-8.11.1 8.11.1 ElasticStack ghcr.io/appscode-images/elastic:8.11.1 17h +xpack-8.11.4 8.11.4 ElasticStack ghcr.io/appscode-images/elastic:8.11.4 17h +xpack-8.13.4 8.13.4 ElasticStack ghcr.io/appscode-images/elastic:8.13.4 17h +xpack-8.14.1 8.14.1 ElasticStack ghcr.io/appscode-images/elastic:8.14.1 17h +xpack-8.14.3 8.14.3 ElasticStack ghcr.io/appscode-images/elastic:8.14.3 17h +xpack-8.15.0 8.15.0 ElasticStack ghcr.io/appscode-images/elastic:8.15.0 17h +xpack-8.15.4 8.15.4 ElasticStack ghcr.io/appscode-images/elastic:8.15.4 16h +xpack-8.16.0 8.16.0 ElasticStack ghcr.io/appscode-images/elastic:8.16.0 16h +xpack-8.2.3 8.2.3 ElasticStack ghcr.io/appscode-images/elastic:8.2.3 17h +xpack-8.5.3 8.5.3 ElasticStack ghcr.io/appscode-images/elastic:8.5.3 17h +xpack-8.6.2 8.6.2 ElasticStack ghcr.io/appscode-images/elastic:8.6.2 17h +xpack-8.8.2 8.8.2 ElasticStack ghcr.io/appscode-images/elastic:8.8.2 17h +``` + +Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. + +```yaml +apiVersion: kubedb.com/v1 +kind: Elasticsearch +metadata: + name: es + namespace: demo +spec: + version: xpack-8.15.0 + storageType: Durable + deletionPolicy: WipeOut + topology: + master: + replicas: 2 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + data: + replicas: 2 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + ingest: + replicas: 1 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +``` + + + +## Next Steps + +- Learn about [backup & restore](/docs/guides/elasticsearch/backup/stash/overview/index.md) Elasticsearch database using Stash. +- Learn how to configure [Elasticsearch Topology Cluster](/docs/guides/elasticsearch/clustering/topology-cluster/simple-dedicated-cluster/index.md). +- Monitor your Elasticsearch database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/elasticsearch/private-registry/using-private-registry.md) to deploy Elasticsearch with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md new file mode 100644 index 0000000000..169a5959ab --- /dev/null +++ b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md @@ -0,0 +1,100 @@ +--- +title: Elasticsearch Rotate TLS Recommendation +menu: + docs_{{ .version }}: + identifier: es-rotate-tls-recommendation + name: Rotate TLS Recommendation + parent: es-recommendation-elasticsearch + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Elasticsearch Rotate TLS Recommendation + +TLS certificate rotation in databases is essential for maintaining security, ensuring compliance, and preventing service disruptions. Regular rotation mitigates risks like certificate expiry and key compromise, adapts to evolving cryptographic standards, and maintains trust relationships with Certificate Authorities. It also enhances operational resilience by testing renewal processes and ensures smooth auditing and monitoring. To minimize risks and streamline the process, KubeDB provides ReconfigureTLS OpsRequest support. KubeDB Ops-manager generates Recommendation to rotate TLS certificates via this OpsRequest when their expiry is near. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Rotate TLS recommendation regarding three particular cases. + +1. There's been an update in the current version image +2. There's a new version available with a minor/patch fix. +3. There's a new major version available + +Let's go through a demo to see version update recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. + +```bash +$ kubectl get elasticsearchversions | grep xpack +xpack-6.8.23 6.8.23 ElasticStack ghcr.io/appscode-images/elastic:6.8.23 17h +xpack-7.13.4 7.13.4 ElasticStack ghcr.io/appscode-images/elastic:7.13.4 17h +xpack-7.14.2 7.14.2 ElasticStack ghcr.io/appscode-images/elastic:7.14.2 17h +xpack-7.16.3 7.16.3 ElasticStack ghcr.io/appscode-images/elastic:7.16.3 17h +xpack-7.17.15 7.17.15 ElasticStack ghcr.io/appscode-images/elastic:7.17.15 17h +xpack-7.17.23 7.17.23 ElasticStack ghcr.io/appscode-images/elastic:7.17.23 17h +xpack-7.17.25 7.17.25 ElasticStack ghcr.io/appscode-images/elastic:7.17.25 16h +xpack-8.11.1 8.11.1 ElasticStack ghcr.io/appscode-images/elastic:8.11.1 17h +xpack-8.11.4 8.11.4 ElasticStack ghcr.io/appscode-images/elastic:8.11.4 17h +xpack-8.13.4 8.13.4 ElasticStack ghcr.io/appscode-images/elastic:8.13.4 17h +xpack-8.14.1 8.14.1 ElasticStack ghcr.io/appscode-images/elastic:8.14.1 17h +xpack-8.14.3 8.14.3 ElasticStack ghcr.io/appscode-images/elastic:8.14.3 17h +xpack-8.15.0 8.15.0 ElasticStack ghcr.io/appscode-images/elastic:8.15.0 17h +xpack-8.15.4 8.15.4 ElasticStack ghcr.io/appscode-images/elastic:8.15.4 16h +xpack-8.16.0 8.16.0 ElasticStack ghcr.io/appscode-images/elastic:8.16.0 16h +xpack-8.2.3 8.2.3 ElasticStack ghcr.io/appscode-images/elastic:8.2.3 17h +xpack-8.5.3 8.5.3 ElasticStack ghcr.io/appscode-images/elastic:8.5.3 17h +xpack-8.6.2 8.6.2 ElasticStack ghcr.io/appscode-images/elastic:8.6.2 17h +xpack-8.8.2 8.8.2 ElasticStack ghcr.io/appscode-images/elastic:8.8.2 17h +``` + +Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. + +```yaml +apiVersion: kubedb.com/v1 +kind: Elasticsearch +metadata: + name: es + namespace: demo +spec: + version: xpack-8.15.0 + storageType: Durable + deletionPolicy: WipeOut + topology: + master: + replicas: 2 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + data: + replicas: 2 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + ingest: + replicas: 1 + storage: + storageClassName: "standard" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +``` + + + +## Next Steps + +- Learn about [backup & restore](/docs/guides/elasticsearch/backup/stash/overview/index.md) Elasticsearch database using Stash. +- Learn how to configure [Elasticsearch Topology Cluster](/docs/guides/elasticsearch/clustering/topology-cluster/simple-dedicated-cluster/index.md). +- Monitor your Elasticsearch database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/elasticsearch/private-registry/using-private-registry.md) to deploy Elasticsearch with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/elasticsearch/recommendation/version-update-recommendation.md b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md new file mode 100644 index 0000000000..97e5d6a045 --- /dev/null +++ b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md @@ -0,0 +1,326 @@ +--- +title: Elasticsearch Version Update Recommendation +menu: + docs_{{ .version }}: + identifier: es-version-update-recommendation + name: Version Update Recommendation + parent: es-recommendation-elasticsearch + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Elasticsearch Version Update Recommendation + +Database versions often need to be updated due to several reasons. Older database versions may have vulnerabilities that hackers can exploit. New versions often include optimizations for query execution, indexing, and storage mechanisms. Modern databases frequently introduce new features, such as better data types, improved indexing methods, or advanced analytics capabilities. Database vendors release patches and updates to address these issues and introduce new features. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Version Update recommendation regarding three particular cases. + +1. There's been an update in the current version image +2. There's a new major/minor version available +3. There's a version available with patch fix + +Let's go through a demo to see version update recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. + +```bash +$ kubectl get elasticsearchversions | grep xpack +xpack-6.8.23 6.8.23 ElasticStack ghcr.io/appscode-images/elastic:6.8.23 17h +xpack-7.13.4 7.13.4 ElasticStack ghcr.io/appscode-images/elastic:7.13.4 17h +xpack-7.14.2 7.14.2 ElasticStack ghcr.io/appscode-images/elastic:7.14.2 17h +xpack-7.16.3 7.16.3 ElasticStack ghcr.io/appscode-images/elastic:7.16.3 17h +xpack-7.17.15 7.17.15 ElasticStack ghcr.io/appscode-images/elastic:7.17.15 17h +xpack-7.17.23 7.17.23 ElasticStack ghcr.io/appscode-images/elastic:7.17.23 17h +xpack-7.17.25 7.17.25 ElasticStack ghcr.io/appscode-images/elastic:7.17.25 16h +xpack-8.11.1 8.11.1 ElasticStack ghcr.io/appscode-images/elastic:8.11.1 17h +xpack-8.11.4 8.11.4 ElasticStack ghcr.io/appscode-images/elastic:8.11.4 17h +xpack-8.13.4 8.13.4 ElasticStack ghcr.io/appscode-images/elastic:8.13.4 17h +xpack-8.14.1 8.14.1 ElasticStack ghcr.io/appscode-images/elastic:8.14.1 17h +xpack-8.14.3 8.14.3 ElasticStack ghcr.io/appscode-images/elastic:8.14.3 17h +xpack-8.15.0 8.15.0 ElasticStack ghcr.io/appscode-images/elastic:8.15.0 17h +xpack-8.15.4 8.15.4 ElasticStack ghcr.io/appscode-images/elastic:8.15.4 16h +xpack-8.16.0 8.16.0 ElasticStack ghcr.io/appscode-images/elastic:8.16.0 16h +xpack-8.2.3 8.2.3 ElasticStack ghcr.io/appscode-images/elastic:8.2.3 17h +xpack-8.5.3 8.5.3 ElasticStack ghcr.io/appscode-images/elastic:8.5.3 17h +xpack-8.6.2 8.6.2 ElasticStack ghcr.io/appscode-images/elastic:8.6.2 17h +xpack-8.8.2 8.8.2 ElasticStack ghcr.io/appscode-images/elastic:8.8.2 17h +``` + +Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. We are going to create a cluster topology with 2 master nodes, 3 data nodes and 2 ingest node. We also have to provide an available storageclass for each of the node types. + +```yaml +apiVersion: kubedb.com/v1 +kind: Elasticsearch +metadata: + name: elastic + namespace: es +spec: + version: xpack-8.15.0 + storageType: Durable + deletionPolicy: WipeOut + topology: + master: + replicas: 2 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + data: + replicas: 3 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + ingest: + replicas: 2 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +``` + +Wait for a while till elasicsearch cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get es elastic -n es -w +NAME VERSION STATUS AGE +elastic xpack-8.15.0 Provisioning 98s +elastic xpack-8.15.0 Provisioning 5m43s +elastic xpack-8.15.0 Provisioning 8m7s +. +. +. +elastic xpack-8.15.0 Ready 10m +elastic xpack-8.15.0 Ready 10m +``` + +Once elastic instance is `Ready`, a `Recommendation` instance will be automatically generated by KubeDB `Ops-Manager` controller. + +```bash +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-update-version-2juuee Pending false 10m +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + annotations: + kubedb.com/recommendation-for-version: xpack-8.15.0 + creationTimestamp: "2025-01-29T12:06:43Z" + generation: 5 + labels: + app.kubernetes.io/instance: elastic + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: version-update + kubedb.com/version-update-recommendation-type: major-minor + name: elastic-x-elasticsearch-x-update-version-2juuee + namespace: es + resourceVersion: "783271" + uid: 3026d740-64fd-4ac4-8f33-2bd305ab0e69 +spec: + backoffLimit: 5 + description: Latest Major/Minor version is available. Recommending version Update + from xpack-8.15.0 to xpack-8.16.0. + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: ElasticsearchOpsRequest + metadata: + name: update-version + namespace: es + spec: + databaseRef: + name: elastic + type: UpdateVersion + updateVersion: + targetVersion: xpack-8.16.0 + status: {} + recommender: + name: kubedb-ops-manager + requireExplicitApproval: true + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: Elasticsearch + name: elastic + vulnerabilityReport: + message: 'ImageScanRequest phase is not Current: timed out waiting for the condition' + status: Failure +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.spec.description}' +Latest Major/Minor version is available. Recommending version Update from xpack-8.15.0 to xpack-8.16.0. +``` + +The recommendation says current version `xpack-8.15.0` should be latest upgradable version `xpack-8.16.0`. You can also find the recommended operation which is a `ElasticsearchOpsRequest` of `UpdateVersion` type in this case. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: ElasticsearchOpsRequest +metadata: + name: update-version + namespace: es +spec: + databaseRef: + name: elastic + type: UpdateVersion + updateVersion: + targetVersion: xpack-8.16.0 +status: {} +``` + +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl cli for this - + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-update-version-2juuee \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-update-version-2juuee patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-01-29T13:01:40Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: elastic-1738155700-update-version-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation +``` + +You will find an `ElasticsearchOpsRequest` custom resource have been created and, it is updating the `elastic` cluster version to `xpack-8.16.0` with negligible downtime. Let's wait for it to reach `Successfull` status. + +```bash +$ kubectl get elasticsearchopsrequest -n es elastic-1738155700-update-version-auto -w +NAME TYPE STATUS AGE +elastic-1738155700-update-version-auto UpdateVersion Progressing 3m12s +elastic-1738155700-update-version-auto UpdateVersion Progressing 3m34s +. +. +elastic-1738155700-update-version-auto UpdateVersion Successful 11m +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-update-version-2juuee Succeeded false 78m +``` + +Finally, You can check `elastic` cluster version now, which should be upgraded to version `xpack-8.16.0`. + +```bash +$ kubectl get es elastic -n es +NAME VERSION STATUS AGE +elastic xpack-8.16.0 Ready 85m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: elastic-maintenance + namespace: es +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `Elasticsearch` custom resource deployed in `es` namespace. This `ApprovalPolicy` custom resource is referring to the `elastic-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: es-policy + namespace: es +maintenanceWindowRef: + name: elastic-maintenance +targets: + - group: kubedb.com + kind: Elasticsearch + operations: + - group: ops.kubedb.com + kind: ElasticsearchOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-update-version-2juuee \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-update-version-2juuee patched +``` + +## Next Steps + +- Learn about [backup & restore](/docs/guides/elasticsearch/backup/stash/overview/index.md) Elasticsearch database using Stash. +- Learn how to configure [Elasticsearch Topology Cluster](/docs/guides/elasticsearch/clustering/topology-cluster/simple-dedicated-cluster/index.md). +- Monitor your Elasticsearch database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/elasticsearch/private-registry/using-private-registry.md) to deploy Elasticsearch with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From 3bf72d283d778becd6a5a8549ac9a2581d47bef9 Mon Sep 17 00:00:00 2001 From: raihankhan Date: Thu, 30 Jan 2025 17:17:11 +0600 Subject: [PATCH 2/5] Add recommendation crds Signed-off-by: raihankhan --- .github/workflows/ci.yml | 4 ++++ docs/guides/elasticsearch/recommendation/overview.md | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 406461a679..3b2a34c26b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,10 @@ jobs: kubectl create -f https://github.com/kubedb/installer/raw/master/crds/kubedb-crds.yaml kubectl create -f https://github.com/kubernetes-csi/external-snapshotter/raw/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml kubectl create -f https://github.com/kubestash/installer/raw/master/crds/kubestash-crds.yaml + kubectl create -f https://github.com/kubeops/supervisor/raw/master/crds/supervisor.appscode.com_approvalpolicies.yaml + kubectl create -f https://github.com/kubeops/supervisor/raw/master/crds/supervisor.appscode.com_clustermaintenancewindows.yaml + kubectl create -f https://github.com/kubeops/supervisor/raw/master/crds/supervisor.appscode.com_maintenancewindows.yaml + kubectl create -f https://github.com/kubeops/supervisor/raw/master/crds/supervisor.appscode.com_recommendations.yaml - name: Check codespan schema run: | diff --git a/docs/guides/elasticsearch/recommendation/overview.md b/docs/guides/elasticsearch/recommendation/overview.md index b14b98690a..3c275a2466 100644 --- a/docs/guides/elasticsearch/recommendation/overview.md +++ b/docs/guides/elasticsearch/recommendation/overview.md @@ -19,7 +19,7 @@ Databases on Kubernetes in production grade infrastructure often need to go thro ## Overview -Recommendation is a custom resource definition object which is created by KubeDB ops-manager controller and managed by supervisor. So, You need to have KubeDB and Supervisor installed first. You can simply install supervisor along with other KubeDB components using `--set supervisor.enabled=true` flag while installing KubeDB via helm chart. +Recommendation is a custom resource definition (CRD) object which is created by KubeDB ops-manager controller and managed by supervisor. So, You need to have KubeDB and Supervisor installed first. You can simply install supervisor along with other KubeDB components using `--set supervisor.enabled=true` flag while installing KubeDB via helm chart.

Recommendation Generation @@ -33,7 +33,7 @@ KubeDB provides Three types of recommendation for Elasticsearch and Opensearch. 2. TLS Certificate Rotation Recommendation 3. Authentication Secret Rotation Recommendation -The next page describes these recommendations, how to enable/disable them, their generation mechanism and usability. +The next page describes these recommendations, how to approve/reject them, their generation mechanism and usability. ## Next Steps From d140f256c30be3ed230350527e77d74f1336df23 Mon Sep 17 00:00:00 2001 From: raihankhan Date: Thu, 30 Jan 2025 17:31:44 +0600 Subject: [PATCH 3/5] Fix weight Signed-off-by: raihankhan --- docs/guides/elasticsearch/plugins/_index.md | 2 +- docs/guides/elasticsearch/recommendation/_index.md | 2 +- .../recommendation/rotate-auth-recommendation.md | 8 ++++---- .../recommendation/rotate-tls-recommendation.md | 2 +- .../recommendation/version-update-recommendation.md | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/guides/elasticsearch/plugins/_index.md b/docs/guides/elasticsearch/plugins/_index.md index efa9f0d86b..088eeea570 100755 --- a/docs/guides/elasticsearch/plugins/_index.md +++ b/docs/guides/elasticsearch/plugins/_index.md @@ -5,6 +5,6 @@ menu: identifier: es-plugin-elasticsearch name: Extensions & Plugins parent: es-elasticsearch-guides - weight: 60 + weight: 90 menu_name: docs_{{ .version }} --- \ No newline at end of file diff --git a/docs/guides/elasticsearch/recommendation/_index.md b/docs/guides/elasticsearch/recommendation/_index.md index 7111595f40..98fb488a17 100755 --- a/docs/guides/elasticsearch/recommendation/_index.md +++ b/docs/guides/elasticsearch/recommendation/_index.md @@ -5,6 +5,6 @@ menu: identifier: es-recommendation-elasticsearch name: Recommendation parent: es-elasticsearch-guides - weight: 100 + weight: 60 menu_name: docs_{{ .version }} --- diff --git a/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md index 3f0c3c8d91..b222012610 100644 --- a/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md @@ -1,11 +1,11 @@ --- -title: Elasticsearch Version Update Recommendation +title: Elasticsearch Rotate Auth Recommendation menu: docs_{{ .version }}: - identifier: es-version-update-recommendation - name: Version Update Recommendation + identifier: es-rotate-auth-recommendation + name: Rotate Auth Recommendation parent: es-recommendation-elasticsearch - weight: 20 + weight: 30 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md index 169a5959ab..8955248c6d 100644 --- a/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md @@ -5,7 +5,7 @@ menu: identifier: es-rotate-tls-recommendation name: Rotate TLS Recommendation parent: es-recommendation-elasticsearch - weight: 30 + weight: 40 menu_name: docs_{{ .version }} section_menu_id: guides --- diff --git a/docs/guides/elasticsearch/recommendation/version-update-recommendation.md b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md index 97e5d6a045..bf58743470 100644 --- a/docs/guides/elasticsearch/recommendation/version-update-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md @@ -173,14 +173,14 @@ status: In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. -```bash +```shell $ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.spec.description}' Latest Major/Minor version is available. Recommending version Update from xpack-8.15.0 to xpack-8.16.0. ``` The recommendation says current version `xpack-8.15.0` should be latest upgradable version `xpack-8.16.0`. You can also find the recommended operation which is a `ElasticsearchOpsRequest` of `UpdateVersion` type in this case. -```bash +```shell $ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.spec.operation}' | yq -y apiVersion: ops.kubedb.com/v1alpha1 kind: ElasticsearchOpsRequest From 4a7c8b6f1337e4fcf4ed60bb5a619ad5d3297e9b Mon Sep 17 00:00:00 2001 From: raihankhan Date: Thu, 27 Feb 2025 18:25:56 +0600 Subject: [PATCH 4/5] Update Signed-off-by: raihankhan --- .../elasticsearch/recommendation/_index.md | 1 + .../elasticsearch/recommendation/overview.md | 15 +- .../rotate-auth-recommendation.md | 291 +++++++++++-- .../rotate-tls-recommendation.md | 309 +++++++++++++- .../version-update-recommendation.md | 9 +- docs/guides/mongodb/recommendation/_index.md | 11 + .../images/recommendation-generation.png | Bin 0 -> 67084 bytes .../guides/mongodb/recommendation/overview.md | 42 ++ .../rotate-auth-recommendation.md | 401 ++++++++++++++++++ .../rotate-tls-recommendation.md | 343 +++++++++++++++ .../version-update-recommendation.md | 356 ++++++++++++++++ 11 files changed, 1701 insertions(+), 77 deletions(-) create mode 100755 docs/guides/mongodb/recommendation/_index.md create mode 100644 docs/guides/mongodb/recommendation/images/recommendation-generation.png create mode 100644 docs/guides/mongodb/recommendation/overview.md create mode 100644 docs/guides/mongodb/recommendation/rotate-auth-recommendation.md create mode 100644 docs/guides/mongodb/recommendation/rotate-tls-recommendation.md create mode 100644 docs/guides/mongodb/recommendation/version-update-recommendation.md diff --git a/docs/guides/elasticsearch/recommendation/_index.md b/docs/guides/elasticsearch/recommendation/_index.md index 98fb488a17..3ed4e47bf1 100755 --- a/docs/guides/elasticsearch/recommendation/_index.md +++ b/docs/guides/elasticsearch/recommendation/_index.md @@ -8,3 +8,4 @@ menu: weight: 60 menu_name: docs_{{ .version }} --- + diff --git a/docs/guides/elasticsearch/recommendation/overview.md b/docs/guides/elasticsearch/recommendation/overview.md index 3c275a2466..0b81868d5d 100644 --- a/docs/guides/elasticsearch/recommendation/overview.md +++ b/docs/guides/elasticsearch/recommendation/overview.md @@ -13,7 +13,7 @@ section_menu_id: guides > New to KubeDB? Please start [here](/docs/README.md). -# Recommendation for KubeDB managed Elasticsearch +# Recommendation for KubeDB managed Elasticsearch and Opensearch Databases on Kubernetes in production grade infrastructure often need to go through several administrative operations depending on specific resource requirements. Such operations include vertical scaling (cpu, memory) and storage expansion. Autoscaling support for KubeDB managed databases takes care of it. However, databases also need to go through some maintenance operations in order to ensure security, enhance performance, getting bug fixes and new features etc. Such operations mostly require organization's manual intervention. Even if these operations are automated, they need to be done in surveillance. KubeDB simplifies this by generating K8s Native Recommendations. @@ -30,16 +30,13 @@ KubeDB provisioner watches user provided database custom resource spec and creat KubeDB provides Three types of recommendation for Elasticsearch and Opensearch. 1. [Version Update Recommendation](/docs/guides/elasticsearch/recommendation/version-update-recommendation.md) -2. TLS Certificate Rotation Recommendation -3. Authentication Secret Rotation Recommendation +2. [TLS Certificate Rotation Recommendation](/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md) +3. [Authentication Secret Rotation Recommendation](/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md) The next page describes these recommendations, how to approve/reject them, their generation mechanism and usability. ## Next Steps -- Learn how to monitor Elasticsearch database with KubeDB using [builtin-Prometheus](/docs/guides/elasticsearch/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/elasticsearch/monitoring/using-prometheus-operator.md). -- Learn how to monitor PostgreSQL database with KubeDB using [builtin-Prometheus](/docs/guides/postgres/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/postgres/monitoring/using-prometheus-operator.md). -- Learn how to monitor MySQL database with KubeDB using [builtin-Prometheus](/docs/guides/mysql/monitoring/builtin-prometheus/index.md) and using [Prometheus operator](/docs/guides/mysql/monitoring/prometheus-operator/index.md). -- Learn how to monitor MongoDB database with KubeDB using [builtin-Prometheus](/docs/guides/mongodb/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). -- Learn how to monitor Redis server with KubeDB using [builtin-Prometheus](/docs/guides/redis/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/redis/monitoring/using-prometheus-operator.md). -- Learn how to monitor Memcached server with KubeDB using [builtin-Prometheus](/docs/guides/memcached/monitoring/using-builtin-prometheus.md) and using [Prometheus operator](/docs/guides/memcached/monitoring/using-prometheus-operator.md). +- Learn about Elasticsearch [Version Update Recommendation](/docs/guides/elasticsearch/recommendation/version-update-recommendation.md). +- Learn about Elasticsearch [TLS Certificate Rotation Recommendation](/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md) +- Learn about Elasticsearch [Authentication Secret Rotation Recommendation](/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md) diff --git a/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md index b222012610..121bc2c992 100644 --- a/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/rotate-auth-recommendation.md @@ -5,7 +5,7 @@ menu: identifier: es-rotate-auth-recommendation name: Rotate Auth Recommendation parent: es-recommendation-elasticsearch - weight: 30 + weight: 40 menu_name: docs_{{ .version }} section_menu_id: guides --- @@ -14,15 +14,14 @@ section_menu_id: guides # Elasticsearch Version Update Recommendation -Rotating authentication secrets in database management is vital to mitigate security risks, such as credential leakage or unauthorized access, and to comply with regulatory requirements. Regular rotation limits the exposure of compromised credentials, reduces the risk of insider threats, and enforces updated security policies like stronger passwords or algorithms. It also ensures operational resilience by testing the rotation process and revoking stale or unused credentials. KubeDB provides `RotateAuth OpsRequest` which reduces manual errors, and strengthens database security with minimal effort. KubeDB Ops-manager generates Recommendation for rotating authentication secrets via this OpsRequest. +Rotating authentication secrets in database management is vital to mitigate security risks, such as credential leakage or unauthorized access, and to comply with regulatory requirements. Regular rotation limits the exposure of compromised credentials, reduces the risk of insider threats, and enforces updated security policies like stronger passwords or algorithms. It also ensures operational resilience by testing the rotation process and revoking stale or unused credentials. KubeDB provides `RotateAuth` which reduces manual errors, and strengthens database security with minimal effort. KubeDB Ops-manager generates Recommendation for rotating authentication secrets via this OpsRequest. -`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Rotate Auth recommendation regarding three particular cases. +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. The recommendation will only be created if `.spec.authSecret.rotateAfter` is set. KubeDB generates Elasticsearch/Opensearch Rotate Auth recommendation regarding two particular cases. -1. There's been an update in the current version image -2. There's a new version available with a minor/patch fix. -3. There's a new major version available +1. AuthSecret lifespan is more than one month and, less than one month remaining till expiry +2. AuthSecret lifespan is less than one month and, less than one third of lifespan remaining till expiry -Let's go through a demo to see version update recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. +Let's go through a demo to see `RotateAuth` recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. ```bash $ kubectl get elasticsearchversions | grep xpack @@ -47,48 +46,254 @@ xpack-8.6.2 8.6.2 ElasticStack ghcr.io/appscode-images/elastic:8.6 xpack-8.8.2 8.8.2 ElasticStack ghcr.io/appscode-images/elastic:8.8.2 17h ``` -Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. +Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. We are going to create a cluster topology with 2 master nodes, 3 data nodes and 2 ingest node. We also have to provide an available storageclass for each of the node types. ```yaml -apiVersion: kubedb.com/v1 -kind: Elasticsearch + apiVersion: kubedb.com/v1 + kind: Elasticsearch + metadata: + name: elastic + namespace: es + spec: + version: xpack-8.15.0 + storageType: Durable + deletionPolicy: WipeOut + authSecret: + rotateAfter: 1h + topology: + master: + replicas: 2 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + data: + replicas: 2 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + ingest: + replicas: 1 + storage: + storageClassName: "local-path" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +``` + +Wait for a while till elasicsearch cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get es elastic -n es -w +NAME VERSION STATUS AGE +elastic xpack-8.15.0 Provisioning 98s +elastic xpack-8.15.0 Provisioning 5m43s +elastic xpack-8.15.0 Provisioning 8m7s +. +. +. +elastic xpack-8.15.0 Ready 10m +elastic xpack-8.15.0 Ready 10m +``` + +Since, `.spec.authSecret.rotateAfter` is set as `1h`, it is expected that the recommendation engine will generate a rotate-auth recommendation at least after 40 minutes (two-third of lifespan) of the authsecret creation. Once generated you will get a similar recommendation as follows. + +```bash +$ kubectl get recommendation -n es | grep rotate-auth +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-rotate-auth-2juuee Pending false 10m +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-auth-2juuee -oyaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + creationTimestamp: "2025-02-25T09:12:29Z" + generation: 1 + labels: + app.kubernetes.io/instance: elastic + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: rotate-auth + name: elastic-x-elasticsearch-x-rotate-auth-2juuee + namespace: es + resourceVersion: "80116" + uid: 12f24cf6-2f02-420f-863d-3523e32a08dd +spec: + backoffLimit: 5 + deadline: "2025-02-25T09:20:53Z" + description: Recommending AuthSecret rotation,elastic-auth AuthSecret needs to be + rotated before 2025-02-25 09:30:53 +0000 UTC + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: ElasticsearchOpsRequest + metadata: + name: rotate-auth + namespace: es + spec: + databaseRef: + name: elastic + type: RotateAuth + status: {} + recommender: + name: kubedb-ops-manager + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: Elasticsearch + name: elastic +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```shell +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-auth-2juuee -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: ElasticsearchOpsRequest metadata: - name: es - namespace: demo + name: rotate-auth + namespace: es spec: - version: xpack-8.15.0 - storageType: Durable - deletionPolicy: WipeOut - topology: - master: - replicas: 2 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - data: - replicas: 2 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi - ingest: - replicas: 1 - storage: - storageClassName: "standard" - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi + databaseRef: + name: elastic + type: RotateAuth +status: {} ``` +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-auth-2juuee -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-rotate-auth-2juuee \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-rotate-auth-2juuee patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-auth-2juuee -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-02-25T09:23:29Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: elastic-1740475409-rotate-auth-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation +``` + +You will find an `ElasticsearchOpsRequest` custom resource have been created and, it is rotating the authsecret of `elastic` cluster with negligible downtime. Let's wait for it to reach `Successful` status. + +```bash +$ kubectl get elasticsearchopsrequest -n es elastic-1740475409-rotate-auth-auto -w +NAME TYPE STATUS AGE +elastic-1740475409-rotate-auth-auto UpdateVersion Progressing 3m12s +elastic-1740475409-rotate-auth-auto UpdateVersion Progressing 3m34s +. +. +elastic-1740475409-rotate-auth-auto UpdateVersion Successful 11m +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-auth-2juuee +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-rotate-auth-2juuee Succeeded false 78m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: elastic-maintenance + namespace: es +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `Elasticsearch` custom resource deployed in `es` namespace. This `ApprovalPolicy` custom resource is referring to the `elastic-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: es-policy + namespace: es +maintenanceWindowRef: + name: elastic-maintenance +targets: + - group: kubedb.com + kind: Elasticsearch + operations: + - group: ops.kubedb.com + kind: ElasticsearchOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-rotate-auth-2juuee \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-rotate-auth-2juuee patched +``` ## Next Steps diff --git a/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md index 8955248c6d..fbdfbfafcc 100644 --- a/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/rotate-tls-recommendation.md @@ -5,7 +5,7 @@ menu: identifier: es-rotate-tls-recommendation name: Rotate TLS Recommendation parent: es-recommendation-elasticsearch - weight: 40 + weight: 30 menu_name: docs_{{ .version }} section_menu_id: guides --- @@ -16,13 +16,13 @@ section_menu_id: guides TLS certificate rotation in databases is essential for maintaining security, ensuring compliance, and preventing service disruptions. Regular rotation mitigates risks like certificate expiry and key compromise, adapts to evolving cryptographic standards, and maintains trust relationships with Certificate Authorities. It also enhances operational resilience by testing renewal processes and ensures smooth auditing and monitoring. To minimize risks and streamline the process, KubeDB provides ReconfigureTLS OpsRequest support. KubeDB Ops-manager generates Recommendation to rotate TLS certificates via this OpsRequest when their expiry is near. -`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Rotate TLS recommendation regarding three particular cases. +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [Elasticsearch](https://www.elastic.co/products/elasticsearch) and [OpenSearch](https://opensearch.org/) in a Kubernetes native way. KubeDB generates Elasticsearch/Opensearch Rotate TLS recommendation regarding if: -1. There's been an update in the current version image -2. There's a new version available with a minor/patch fix. -3. There's a new major version available +- At least one of its certificate’s lifespan is more than one month and less than one month remaining till expiry -Let's go through a demo to see version update recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. +- At least one of its certificates has one-third of its lifespan remaining till expiry. + +Let's go through a demo to see `RotateTLS` recommendations being generated. First, get the available Elasticsearch versions provided by KubeDB. ```bash $ kubectl get elasticsearchversions | grep xpack @@ -47,48 +47,315 @@ xpack-8.6.2 8.6.2 ElasticStack ghcr.io/appscode-images/elastic:8.6 xpack-8.8.2 8.8.2 ElasticStack ghcr.io/appscode-images/elastic:8.8.2 17h ``` -Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. +Let's deploy an Elasticsearch cluster with version `xpack-8.15.0`. We are going to create a cluster topology with 2 master nodes, 3 data nodes and 2 ingest node. We also have to provide an available storageclass for each of the node types. Make sure to have an issuer/clusterIssuer to refer in the manifest. Though KubeDB managed elasticsearch supports TLS in both cert-manager provisioned and Operator provisioned ways, rotate tls only works when certificates are provisioned via cert-manager. ```yaml apiVersion: kubedb.com/v1 kind: Elasticsearch metadata: - name: es - namespace: demo + name: elastic + namespace: es spec: - version: xpack-8.15.0 - storageType: Durable deletionPolicy: WipeOut + kernelSettings: + disableDefaults: false + storageType: Durable + enableSSL: true + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: ca-issuer + certificates: + - alias: client + duration: 1h20m + - alias: http + duration: 2h10m topology: - master: - replicas: 2 + data: + podTemplate: + spec: + containers: + - name: elasticsearch + resources: + limits: + cpu: 500m + memory: 1536Mi + requests: + cpu: 500m + memory: 1536Mi + nodeSelector: + kubernetes.io/os: linux + podPlacementPolicy: + name: default + replicas: 3 storage: - storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: - storage: 1Gi - data: + storage: 5Gi + storageClassName: local-path + ingest: + podTemplate: + spec: + containers: + - name: elasticsearch + resources: + limits: + cpu: 500m + memory: 1536Mi + requests: + cpu: 500m + memory: 1536Mi + nodeSelector: + kubernetes.io/os: linux + podPlacementPolicy: + name: default replicas: 2 storage: - storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: storage: 1Gi - ingest: - replicas: 1 + storageClassName: local-path + master: + podTemplate: + spec: + containers: + - name: elasticsearch + resources: + limits: + cpu: 500m + memory: 1536Mi + requests: + cpu: 500m + memory: 1536Mi + nodeSelector: + kubernetes.io/os: linux + podPlacementPolicy: + name: default + replicas: 2 storage: - storageClassName: "standard" accessModes: - ReadWriteOnce resources: requests: - storage: 1Gi + storage: 3Gi + storageClassName: local-path + version: xpack-8.15.0 ``` +Wait for a while till elasicsearch cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get es elastic -n es -w +NAME VERSION STATUS AGE +elastic xpack-8.15.0 Provisioning 98s +elastic xpack-8.15.0 Provisioning 5m43s +elastic xpack-8.15.0 Provisioning 8m7s +. +. +. +elastic xpack-8.15.0 Ready 10m +elastic xpack-8.15.0 Ready 10m +``` + +Since,duration for client certificate is set as `1h20min`, it is expected that the recommendation engine will generate a rotate-auth recommendation at least after 54 minutes (two-third of lifespan) of the client certificate creation. Once generated you will get a similar recommendation as follows. + +```bash +$ kubectl get recommendation -n es | grep rotate-tls +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-rotate-tls-6ujvez Pending false 74s +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-tls-6ujvez -oyaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + creationTimestamp: "2025-02-27T11:50:04Z" + generation: 1 + labels: + app.kubernetes.io/instance: elastic + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: rotate-tls + name: elastic-x-elasticsearch-x-rotate-tls-6ujvez + namespace: es + resourceVersion: "309401" + uid: d208df6b-5fbf-4122-b7b7-18e73a4e1d6c +spec: + backoffLimit: 5 + deadline: "2025-02-27T11:59:43Z" + description: Recommending TLS certificate rotation,elastic-client-cert Certificate + is going to be expire on 2025-02-27 12:04:43 +0000 UTC + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: ElasticsearchOpsRequest + metadata: + name: rotate-tls + namespace: es + spec: + databaseRef: + name: elastic + tls: + rotateCertificates: true + type: ReconfigureTLS + status: {} + recommender: + name: kubedb-ops-manager + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: Elasticsearch + name: elastic +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```shell +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-tls-6ujvez -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: ElasticsearchOpsRequest +metadata: + name: rotate-tls + namespace: es +spec: + databaseRef: + name: elastic + tls: + rotateCertificates: true + type: ReconfigureTLS +status: {} +``` + +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-tls-6ujvez -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-rotate-tls-6ujvez \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-rotate-tls-6ujvez patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-tls-6ujvez -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-02-27T11:54:50Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: elastic-1740657290-rotate-tls-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation +``` + +You will find an `ElasticsearchOpsRequest` custom resource have been created and, it is rotating the authsecret of `elastic` cluster with negligible downtime. Let's wait for it to reach `Successful` status. + +```bash +$ kubectl get elasticsearchopsrequest -n es elastic-1740657290-rotate-tls-auto -w +NAME TYPE STATUS AGE +elastic-1740657290-rotate-tls-auto ReconfigureTLS Progressing 60s +elastic-1740657290-rotate-tls-auto ReconfigureTLS Progressing 114s +. +. +elastic-1740657290-rotate-tls-auto ReconfigureTLS Successful 12m + +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n es elastic-x-elasticsearch-x-rotate-tls-6ujvez +NAME STATUS OUTDATED AGE +elastic-x-elasticsearch-x-rotate-tls-6ujvez Succeeded false 78m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: elastic-maintenance + namespace: es +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `Elasticsearch` custom resource deployed in `es` namespace. This `ApprovalPolicy` custom resource is referring to the `elastic-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: es-policy + namespace: es +maintenanceWindowRef: + name: elastic-maintenance +targets: + - group: kubedb.com + kind: Elasticsearch + operations: + - group: ops.kubedb.com + kind: ElasticsearchOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation elastic-x-elasticsearch-x-rotate-tls-6ujvez \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/elastic-x-elasticsearch-x-rotate-tls-6ujvez patched +``` ## Next Steps diff --git a/docs/guides/elasticsearch/recommendation/version-update-recommendation.md b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md index bf58743470..c751a498ed 100644 --- a/docs/guides/elasticsearch/recommendation/version-update-recommendation.md +++ b/docs/guides/elasticsearch/recommendation/version-update-recommendation.md @@ -104,9 +104,10 @@ elastic xpack-8.15.0 Ready 10m elastic xpack-8.15.0 Ready 10m ``` -Once elastic instance is `Ready`, a `Recommendation` instance will be automatically generated by KubeDB `Ops-Manager` controller. +Once elastic instance is `Ready`, a `Recommendation` instance will be automatically generated by KubeDB `Ops-Manager` controller. Might take a few minutes to trigger an event for the database creation in the controller. ```bash +$ kubectl get recommendation -n es NAME STATUS OUTDATED AGE elastic-x-elasticsearch-x-update-version-2juuee Pending false 10m ``` @@ -178,7 +179,7 @@ $ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juu Latest Major/Minor version is available. Recommending version Update from xpack-8.15.0 to xpack-8.16.0. ``` -The recommendation says current version `xpack-8.15.0` should be latest upgradable version `xpack-8.16.0`. You can also find the recommended operation which is a `ElasticsearchOpsRequest` of `UpdateVersion` type in this case. +The recommendation says current version `xpack-8.15.0` should be upgraded to latest upgradable version `xpack-8.16.0`. You can also find the recommended operation which is a `ElasticsearchOpsRequest` of `UpdateVersion` type in this case. ```shell $ kubectl get recommendation -n es elastic-x-elasticsearch-x-update-version-2juuee -o jsonpath='{.spec.operation}' | yq -y @@ -208,7 +209,7 @@ phase: Pending reason: WaitingForApproval ``` -Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl cli for this - +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - ```bash $ kubectl patch Recommendation elastic-x-elasticsearch-x-update-version-2juuee \ @@ -241,7 +242,7 @@ phase: InProgress reason: StartedExecutingOperation ``` -You will find an `ElasticsearchOpsRequest` custom resource have been created and, it is updating the `elastic` cluster version to `xpack-8.16.0` with negligible downtime. Let's wait for it to reach `Successfull` status. +You will find an `ElasticsearchOpsRequest` custom resource have been created and, it is updating the `elastic` cluster version to `xpack-8.16.0` with negligible downtime. Let's wait for it to reach `Successful` status. ```bash $ kubectl get elasticsearchopsrequest -n es elastic-1738155700-update-version-auto -w diff --git a/docs/guides/mongodb/recommendation/_index.md b/docs/guides/mongodb/recommendation/_index.md new file mode 100755 index 0000000000..e466b323b1 --- /dev/null +++ b/docs/guides/mongodb/recommendation/_index.md @@ -0,0 +1,11 @@ +--- +title: MongoDB Recommendation +menu: + docs_{{ .version }}: + identifier: mg-recommendation-mongodb + name: Recommendation + parent: mg-mongodb-guides + weight: 60 +menu_name: docs_{{ .version }} +--- + diff --git a/docs/guides/mongodb/recommendation/images/recommendation-generation.png b/docs/guides/mongodb/recommendation/images/recommendation-generation.png new file mode 100644 index 0000000000000000000000000000000000000000..dd96452a27e09599fdc1a6924e587f041f0c90a6 GIT binary patch literal 67084 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/guides/mongodb/recommendation/overview.md b/docs/guides/mongodb/recommendation/overview.md new file mode 100644 index 0000000000..5a0e8b51e6 --- /dev/null +++ b/docs/guides/mongodb/recommendation/overview.md @@ -0,0 +1,42 @@ +--- +title: MongoDB Recommendation +description: MongoDB Recommendation Overview +menu: + docs_{{ .version }}: + identifier: mg-recommendation-overview + name: Overview + parent: mg-recommendation-mongodb + weight: 10 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# Recommendation for KubeDB managed MongoDB + +Databases on Kubernetes in production grade infrastructure often need to go through several administrative operations depending on specific resource requirements. Such operations include vertical scaling (cpu, memory) and storage expansion. Autoscaling support for KubeDB managed databases takes care of it. However, databases also need to go through some maintenance operations in order to ensure security, enhance performance, getting bug fixes and new features etc. Such operations mostly require organization's manual intervention. Even if these operations are automated, they need to be done in surveillance. KubeDB simplifies this by generating K8s Native Recommendations. + +## Overview + +Recommendation is a custom resource definition (CRD) object which is created by KubeDB ops-manager controller and managed by supervisor. So, You need to have KubeDB and Supervisor installed first. You can simply install supervisor along with other KubeDB components using `--set supervisor.enabled=true` flag while installing KubeDB via helm chart. + +

+Recommendation Generation +

+ +KubeDB provisioner watches user provided database custom resource spec and creates/sync all the necessary DB resources. Once the Database is ready KubeDB Ops-manager watches the DB and creates Recommendation if it requires. KubeDB Supervisor then watches the Recommendation, updates status of the recommendation, creates recommended operation via OpsRequest if deadline reaches or manually triggered and watches the OpsRequest status to update accordingly in Recommendation custom resource. + +KubeDB provides Three types of recommendation for MongoDB. + +1. [Version Update Recommendation](/docs/guides/mongodb/recommendation/version-update-recommendation.md) +2. [TLS Certificate Rotation Recommendation](/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md) +3. [Authentication Secret Rotation Recommendation](/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md) + +The next page describes these recommendations, how to approve/reject them, their generation mechanism and usability. + +## Next Steps + +- Learn about MongoDB [Version Update Recommendation](/docs/guides/mongodb/recommendation/version-update-recommendation.md). +- Learn about MongoDB [TLS Certificate Rotation Recommendation](/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md) +- Learn about MongoDB [Authentication Secret Rotation Recommendation](/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md) diff --git a/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md b/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md new file mode 100644 index 0000000000..880122aeed --- /dev/null +++ b/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md @@ -0,0 +1,401 @@ +--- +title: MongoDB Rotate Auth Recommendation +menu: + docs_{{ .version }}: + identifier: mg-rotate-auth-recommendation + name: Rotate Auth Recommendation + parent: mg-recommendation-mongodb + weight: 40 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# MongoDB Version Update Recommendation + +Rotating authentication secrets in database management is vital to mitigate security risks, such as credential leakage or unauthorized access, and to comply with regulatory requirements. Regular rotation limits the exposure of compromised credentials, reduces the risk of insider threats, and enforces updated security policies like stronger passwords or algorithms. It also ensures operational resilience by testing the rotation process and revoking stale or unused credentials. KubeDB provides `RotateAuth` which reduces manual errors, and strengthens database security with minimal effort. KubeDB Ops-manager generates Recommendation for rotating authentication secrets via this OpsRequest. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.elastic.co/products/mongodb) in a Kubernetes native way. The recommendation will only be created if `.spec.authSecret.rotateAfter` is set. KubeDB generates MongoDB Rotate Auth recommendation regarding two particular cases. + +1. AuthSecret lifespan is more than one month and, less than one month remaining till expiry +2. AuthSecret lifespan is less than one month and, less than one third of lifespan remaining till expiry + +Let's go through a demo to see `RotateAuth` recommendations being generated. First, get the available MongoDB versions provided by KubeDB. + +```bash +$ kubectl get mongodbversions +NAME VERSION DISTRIBUTION DB_IMAGE DEPRECATED AGE +4.2.24 4.2.24 Official ghcr.io/appscode-images/mongo:4.2.24 3h43m +4.4.26 4.4.26 Official ghcr.io/appscode-images/mongo:4.4.26 3h43m +5.0.23 5.0.23 Official ghcr.io/appscode-images/mongo:5.0.23 3h43m +5.0.26 5.0.26 Official ghcr.io/appscode-images/mongo:5.0.26 3h43m +6.0.12 6.0.12 Official ghcr.io/appscode-images/mongo:6.0.12 3h43m +7.0.16 7.0.16 Official ghcr.io/appscode-images/mongo:7.0.16 3h43m +7.0.5 7.0.5 Official ghcr.io/appscode-images/mongo:7.0.5 3h43m +7.0.8 7.0.8 Official ghcr.io/appscode-images/mongo:7.0.8 3h43m +8.0.4 8.0.4 Official ghcr.io/appscode-images/mongo:8.0.4 3h43m +percona-4.2.24 4.2.24 Percona percona/percona-server-mongodb:4.2.24 3h43m +percona-4.4.26 4.4.26 Percona percona/percona-server-mongodb:4.4.26 3h43m +percona-5.0.23 5.0.23 Percona percona/percona-server-mongodb:5.0.23 3h43m +percona-6.0.12 6.0.12 Percona percona/percona-server-mongodb:6.0.12 3h43m +percona-7.0.4 7.0.4 Percona percona/percona-server-mongodb:7.0.4 3h43m +``` + +Let's deploy an MongoDB cluster with version `7.0.8`. + +```yaml +apiVersion: kubedb.com/v1 +kind: MongoDB +metadata: + name: mongo + namespace: mg +spec: + deletionPolicy: WipeOut + authSecret: + rotateAfter: 1h + podTemplate: + spec: + containers: + - name: mongodb + resources: + limits: + cpu: 700m + memory: 1Gi + requests: + cpu: 700m + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + - name: replication-mode-detector + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + initContainers: + - name: copy-config + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + securityContext: + fsGroup: 999 + replicaSet: + name: rs0 + replicas: 2 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: local-path + storageType: Durable + version: 7.0.8 +``` + +Wait for a while till mongodb cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get mg mongo -n mg -w +NAME VERSION STATUS AGE +mongo 7.0.8 Provisioning 98s +mongo 7.0.8 Provisioning 5m43s +mongo 7.0.8 Provisioning 8m7s +. +. +. +mongo 7.0.8 Ready 10m +mongo 7.0.8 Ready 10m +``` + +Since, `.spec.authSecret.rotateAfter` is set as `1h`, it is expected that the recommendation engine will generate a rotate-auth recommendation at least after 40 minutes (two-third of lifespan) of the authsecret creation. Once generated you will get a similar recommendation as follows. + +```bash +$ kubectl get recommendation -n mg | grep rotate-auth +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-rotate-auth-441xqs Pending false 7m11s +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +$ kubectl get recommendation -n mg mongo-x-mongodb-x-rotate-auth-441xqs -oyaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + creationTimestamp: "2025-02-25T09:12:29Z" + generation: 1 + labels: + app.kubernetes.io/instance: mongo + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: rotate-auth + name: mongo-x-mongodb-x-rotate-auth-441xqs + namespace: mg + resourceVersion: "80116" + uid: 12f24cf6-2f02-420f-863d-3523e32a08dd +spec: + backoffLimit: 5 + deadline: "2025-02-25T09:20:53Z" + description: Recommending AuthSecret rotation,mongo-auth AuthSecret needs to be + rotated before 2025-02-25 09:30:53 +0000 UTC + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: MongoDBOpsRequest + metadata: + name: rotate-auth + namespace: mg + spec: + databaseRef: + name: mongo + type: RotateAuth + status: {} + recommender: + name: kubedb-ops-manager + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: MongoDB + name: mongo +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```shell +$ kubectl get recommendation -n mg mongo-x-mongodb-x-rotate-auth-441xqs -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: MongoDBOpsRequest +metadata: + name: rotate-auth + namespace: mg +spec: + databaseRef: + name: mongo + type: RotateAuth +status: {} +``` + +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-rotate-auth-441xqs -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-rotate-auth-441xqs \ + -n mg \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-auth-441xqs patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-rotate-auth-441xqs -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-02-25T09:23:29Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: mongo-1740475409-rotate-auth-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation +``` + +You will find an `MongoDBOpsRequest` custom resource have been created and, it is rotating the authsecret of `mongo` cluster with negligible downtime. Let's wait for it to reach `Successful` status. + +```bash +$ kubectl get mongodbopsrequest -n mg mongo-1740475409-rotate-auth-auto -w +NAME TYPE STATUS AGE +mongo-1740475409-rotate-auth-auto RotateAuth Successful 112s +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-rotate-auth-441xqs +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-rotate-auth-441xqs Succeeded false 78m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: mongo-maintenance + namespace: mg +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `MongoDB` custom resource deployed in `mg` namespace. This `ApprovalPolicy` custom resource is referring to the `mongo-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: mg-policy + namespace: mg +maintenanceWindowRef: + name: mongo-maintenance +targets: + - group: kubedb.com + kind: MongoDB + operations: + - group: ops.kubedb.com + kind: MongoDBOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-rotate-auth-441xqs \ + -n mg \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-auth-441xqs patched +``` + + +## Next Steps + +- Learn about [backup & restore](/docs/guides/mongodb/backup/stash/overview/index.md) MongoDB database using Stash. +- Learn how to configure [MongoDB Cluster](/docs/guides/mongodb/clustering/replicaset.md). +- Monitor your MongoDB database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/mongodb/private-registry/using-private-registry.md) to deploy MongoDB with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). + + +apiVersion: kubedb.com/v1 +kind: Postgres +metadata: + labels: + app.kubernetes.io/instance: postgres + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: postgreses.kubedb.com + name: postgres + namespace: pg +spec: + deletionPolicy: WipeOut + authSecret: + rotateAfter: 1h + podTemplate: + spec: + containers: + - name: postgres + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 500m + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 70 + runAsNonRoot: true + runAsUser: 70 + seccompProfile: + type: RuntimeDefault + - name: pg-coordinator + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 70 + runAsNonRoot: true + runAsUser: 70 + seccompProfile: + type: RuntimeDefault + initContainers: + - name: postgres-init-container + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 70 + runAsNonRoot: true + runAsUser: 70 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + securityContext: + fsGroup: 999 + replicas: 3 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: local-path + storageType: Durable + version: "16.4" + diff --git a/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md b/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md new file mode 100644 index 0000000000..403ad629c8 --- /dev/null +++ b/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md @@ -0,0 +1,343 @@ +--- +title: MongoDB Rotate TLS Recommendation +menu: + docs_{{ .version }}: + identifier: mg-rotate-tls-recommendation + name: Rotate TLS Recommendation + parent: mg-recommendation-mongodb + weight: 30 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# MongoDB Rotate TLS Recommendation + +TLS certificate rotation in databases is essential for maintaining security, ensuring compliance, and preventing service disruptions. Regular rotation mitigates risks like certificate expiry and key compromise, adapts to evolving cryptographic standards, and maintains trust relationships with Certificate Authorities. It also enhances operational resilience by testing renewal processes and ensures smooth auditing and monitoring. To minimize risks and streamline the process, KubeDB provides ReconfigureTLS OpsRequest support. KubeDB Ops-manager generates Recommendation to rotate TLS certificates via this OpsRequest when their expiry is near. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.mongo.co/products/mongodb) in a Kubernetes native way. KubeDB generates MongoDB/Opensearch Rotate TLS recommendation regarding if: + +- At least one of its certificate’s lifespan is more than one month and less than one month remaining till expiry + +- At least one of its certificates has one-third of its lifespan remaining till expiry. + +Let's go through a demo to see `RotateTLS` recommendations being generated. First, get the available MongoDB versions provided by KubeDB. + +```bash +$ kubectl get mongodbversions +NAME VERSION DISTRIBUTION DB_IMAGE DEPRECATED AGE +4.2.24 4.2.24 Official ghcr.io/appscode-images/mongo:4.2.24 3h43m +4.4.26 4.4.26 Official ghcr.io/appscode-images/mongo:4.4.26 3h43m +5.0.23 5.0.23 Official ghcr.io/appscode-images/mongo:5.0.23 3h43m +5.0.26 5.0.26 Official ghcr.io/appscode-images/mongo:5.0.26 3h43m +6.0.12 6.0.12 Official ghcr.io/appscode-images/mongo:6.0.12 3h43m +7.0.16 7.0.16 Official ghcr.io/appscode-images/mongo:7.0.16 3h43m +7.0.5 7.0.5 Official ghcr.io/appscode-images/mongo:7.0.5 3h43m +7.0.8 7.0.8 Official ghcr.io/appscode-images/mongo:7.0.8 3h43m +8.0.4 8.0.4 Official ghcr.io/appscode-images/mongo:8.0.4 3h43m +percona-4.2.24 4.2.24 Percona percona/percona-server-mongodb:4.2.24 3h43m +percona-4.4.26 4.4.26 Percona percona/percona-server-mongodb:4.4.26 3h43m +percona-5.0.23 5.0.23 Percona percona/percona-server-mongodb:5.0.23 3h43m +percona-6.0.12 6.0.12 Percona percona/percona-server-mongodb:6.0.12 3h43m +percona-7.0.4 7.0.4 Percona percona/percona-server-mongodb:7.0.4 3h43m +``` + +Let's deploy an MongoDB cluster with version `7.0.8`. + +```yaml +apiVersion: kubedb.com/v1 +kind: MongoDB +metadata: + name: mongo + namespace: mg +spec: + deletionPolicy: WipeOut + tls: + issuerRef: + apiGroup: "cert-manager.io" + kind: Issuer + name: ca-issuer + certificates: + - alias: client + duration: 1h20m + podTemplate: + spec: + containers: + - name: mongodb + resources: + limits: + cpu: 700m + memory: 1Gi + requests: + cpu: 700m + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + - name: replication-mode-detector + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + initContainers: + - name: copy-config + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + securityContext: + fsGroup: 999 + replicaSet: + name: rs0 + replicas: 2 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: local-path + storageType: Durable + version: 7.0.8 +``` + +Wait for a while till mongodb cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get es mongo -n es -w +NAME VERSION STATUS AGE +mongo 7.0.8 Provisioning 98s +mongo 7.0.8 Provisioning 5m43s +mongo 7.0.8 Provisioning 8m7s +. +. +. +mongo 7.0.8 Ready 10m +mongo 7.0.8 Ready 10m +``` + +Since,duration for client certificate is set as `1h20min`, it is expected that the recommendation engine will generate a rotate-auth recommendation at least after 54 minutes (two-third of lifespan) of the client certificate creation. Once generated you will get a similar recommendation as follows. + +```bash +$ kubectl get recommendation -n es | grep rotate-tls +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-rotate-tls-6ujvez Pending false 74s +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +$ kubectl get recommendation -n es mongo-x-mongodb-x-rotate-tls-6ujvez -oyaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + creationTimestamp: "2025-02-27T11:50:04Z" + generation: 1 + labels: + app.kubernetes.io/instance: mongo + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: rotate-tls + name: mongo-x-mongodb-x-rotate-tls-6ujvez + namespace: es + resourceVersion: "309401" + uid: d208df6b-5fbf-4122-b7b7-18e73a4e1d6c +spec: + backoffLimit: 5 + deadline: "2025-02-27T11:59:43Z" + description: Recommending TLS certificate rotation,mongo-client-cert Certificate + is going to be expire on 2025-02-27 12:04:43 +0000 UTC + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: MongoDBOpsRequest + metadata: + name: rotate-tls + namespace: es + spec: + databaseRef: + name: mongo + tls: + rotateCertificates: true + type: ReconfigureTLS + status: {} + recommender: + name: kubedb-ops-manager + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: MongoDB + name: mongo +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```shell +$ kubectl get recommendation -n es mongo-x-mongodb-x-rotate-tls-6ujvez -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: MongoDBOpsRequest +metadata: + name: rotate-tls + namespace: es +spec: + databaseRef: + name: mongo + tls: + rotateCertificates: true + type: ReconfigureTLS +status: {} +``` + +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n es mongo-x-mongodb-x-rotate-tls-6ujvez -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-rotate-tls-6ujvez \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-tls-6ujvez patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n es mongo-x-mongodb-x-rotate-tls-6ujvez -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-02-27T11:54:50Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: mongo-1740657290-rotate-tls-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation +``` + +You will find an `MongoDBOpsRequest` custom resource have been created and, it is rotating the authsecret of `mongo` cluster with negligible downtime. Let's wait for it to reach `Successful` status. + +```bash +$ kubectl get mongodbopsrequest -n es mongo-1740657290-rotate-tls-auto -w +NAME TYPE STATUS AGE +mongo-1740657290-rotate-tls-auto ReconfigureTLS Progressing 60s +mongo-1740657290-rotate-tls-auto ReconfigureTLS Progressing 114s +. +. +mongo-1740657290-rotate-tls-auto ReconfigureTLS Successful 12m + +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n es mongo-x-mongodb-x-rotate-tls-6ujvez +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-rotate-tls-6ujvez Succeeded false 78m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: mongo-maintenance + namespace: es +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `MongoDB` custom resource deployed in `es` namespace. This `ApprovalPolicy` custom resource is referring to the `mongo-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: mg-policy + namespace: es +maintenanceWindowRef: + name: mongo-maintenance +targets: + - group: kubedb.com + kind: MongoDB + operations: + - group: ops.kubedb.com + kind: MongoDBOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-rotate-tls-6ujvez \ + -n es \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-tls-6ujvez patched +``` + + +## Next Steps + +- Learn about [backup & restore](/docs/guides/mongodb/backup/stash/overview/index.md) MongoDB database using Stash. +- Learn how to configure [MongoDB Topology Cluster](/docs/guides/mongodb/clustering/topology-cluster/simple-dedicated-cluster/index.md). +- Monitor your MongoDB database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/mongodb/private-registry/using-private-registry.md) to deploy MongoDB with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). diff --git a/docs/guides/mongodb/recommendation/version-update-recommendation.md b/docs/guides/mongodb/recommendation/version-update-recommendation.md new file mode 100644 index 0000000000..bedf773244 --- /dev/null +++ b/docs/guides/mongodb/recommendation/version-update-recommendation.md @@ -0,0 +1,356 @@ +--- +title: MongoDB Version Update Recommendation +menu: + docs_{{ .version }}: + identifier: mg-version-update-recommendation + name: Version Update Recommendation + parent: mg-recommendation-mongodb + weight: 20 +menu_name: docs_{{ .version }} +section_menu_id: guides +--- + +> New to KubeDB? Please start [here](/docs/README.md). + +# MongoDB Version Update Recommendation + +Database versions often need to be updated due to several reasons. Older database versions may have vulnerabilities that hackers can exploit. New versions often include optimizations for query execution, indexing, and storage mechanisms. Modern databases frequently introduce new features, such as better data types, improved indexing methods, or advanced analytics capabilities. Database vendors release patches and updates to address these issues and introduce new features. + +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.mongodb.com/) in a Kubernetes native way. KubeDB generates MongoDB Version Update recommendation regarding three particular cases. + +1. There's been an update in the current version image +2. There's a new major/minor version available +3. There's a version available with patch fix + +Let's go through a demo to see version update recommendations being generated. First, get the available MongoDB versions provided by KubeDB. + +```bash +$ kubectl get mongodbversions +NAME VERSION DISTRIBUTION DB_IMAGE DEPRECATED AGE +4.2.24 4.2.24 Official ghcr.io/appscode-images/mongo:4.2.24 3h43m +4.4.26 4.4.26 Official ghcr.io/appscode-images/mongo:4.4.26 3h43m +5.0.23 5.0.23 Official ghcr.io/appscode-images/mongo:5.0.23 3h43m +5.0.26 5.0.26 Official ghcr.io/appscode-images/mongo:5.0.26 3h43m +6.0.12 6.0.12 Official ghcr.io/appscode-images/mongo:6.0.12 3h43m +7.0.16 7.0.16 Official ghcr.io/appscode-images/mongo:7.0.16 3h43m +7.0.5 7.0.5 Official ghcr.io/appscode-images/mongo:7.0.5 3h43m +7.0.8 7.0.8 Official ghcr.io/appscode-images/mongo:7.0.8 3h43m +8.0.4 8.0.4 Official ghcr.io/appscode-images/mongo:8.0.4 3h43m +percona-4.2.24 4.2.24 Percona percona/percona-server-mongodb:4.2.24 3h43m +percona-4.4.26 4.4.26 Percona percona/percona-server-mongodb:4.4.26 3h43m +percona-5.0.23 5.0.23 Percona percona/percona-server-mongodb:5.0.23 3h43m +percona-6.0.12 6.0.12 Percona percona/percona-server-mongodb:6.0.12 3h43m +percona-7.0.4 7.0.4 Percona percona/percona-server-mongodb:7.0.4 3h43m +``` + +Let's deploy an MongoDB cluster with version `7.0.8`. + +```yaml +apiVersion: kubedb.com/v1 +kind: MongoDB +metadata: + name: mongo + namespace: mg +spec: + deletionPolicy: WipeOut + podTemplate: + spec: + containers: + - name: mongodb + resources: + limits: + cpu: 700m + memory: 1Gi + requests: + cpu: 700m + memory: 1Gi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + - name: replication-mode-detector + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + initContainers: + - name: copy-config + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsGroup: 0 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + nodeSelector: + kubernetes.io/os: linux + securityContext: + fsGroup: 999 + replicaSet: + name: rs0 + replicas: 2 + storage: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: local-path + storageType: Durable + version: 7.0.8 +``` + +Wait for a while till mongodb cluster gets into `Ready` state. Required time depends on image pulling and node's physical specifications. + +```bash +$ kubectl get mg mongo -n mg -w +NAME VERSION STATUS AGE +mongo 7.0.8 Provisioning 98s +mongo 7.0.8 Provisioning 5m43s +mongo 7.0.8 Provisioning 8m7s +. +. +. +mongo 7.0.8 Ready 10m +mongo 7.0.8 Ready 10m +``` + +Once mongo instance is `Ready`, a `Recommendation` instance will be automatically generated by KubeDB `Ops-Manager` controller. Might take a few minutes to trigger an event for the database creation in the controller. + +```bash +$ kubectl get recommendation -n mg +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-update-version-uax2ot Pending false 10m +``` + +The `Recommendation` custom resource will be named as `-x--x--`. Initially, the KubeDB `Supervisor` controller will mark the `Status` of this object to `Pending`. Let's check the complete Recommendation custom resource manifest: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: Recommendation +metadata: + annotations: + kubedb.com/recommendation-for-version: 7.0.8 + creationTimestamp: "2025-02-25T08:32:58Z" + generation: 1 + labels: + app.kubernetes.io/instance: mongo + app.kubernetes.io/managed-by: kubedb.com + app.kubernetes.io/type: version-update + kubedb.com/version-update-recommendation-type: major-minor + name: mongo-x-mongodb-x-update-version-uax2ot + namespace: mg + resourceVersion: "76768" + uid: 17722dda-992a-4755-a480-a6ef1f7149a7 +spec: + backoffLimit: 5 + description: Latest Major/Minor version is available. Recommending version Update + from 7.0.8 to 8.0.4. + operation: + apiVersion: ops.kubedb.com/v1alpha1 + kind: MongoDBOpsRequest + metadata: + name: update-version + namespace: mg + spec: + databaseRef: + name: mongo + type: UpdateVersion + updateVersion: + targetVersion: 8.0.4 + status: {} + recommender: + name: kubedb-ops-manager + requireExplicitApproval: true + rules: + failed: has(self.status) && has(self.status.phase) && self.status.phase == 'Failed' + inProgress: has(self.status) && has(self.status.phase) && self.status.phase == + 'Progressing' + success: has(self.status) && has(self.status.phase) && self.status.phase == 'Successful' + target: + apiGroup: kubedb.com + kind: MongoDB + name: mongo + vulnerabilityReport: + message: no matches for kind "ImageScanReport" in version "scanner.appscode.com/v1alpha1" + status: Failure +status: + approvalStatus: Pending + failedAttempt: 0 + outdated: false + parallelism: Namespace + phase: Pending + reason: WaitingForApproval +``` + +In the generated Recommendation you will find a description, targeted db object, recommended operation or Ops-Request manifest, current status of the recommendation etc. Let's just focus on the recommendation description first. + +```shell +$ kubectl get recommendation -n mg mongo-x-mongodb-x-update-version-uax2ot -o jsonpath='{.spec.description}' +Latest Major/Minor version is available. Recommending version Update from 7.0.8 to 8.0.4. +``` + +The recommendation says current version `7.0.8` should be upgraded to latest upgradable version `8.0.4`. You can also find the recommended operation which is a `MongoDBOpsRequest` of `UpdateVersion` type in this case. + +```shell +$ kubectl get recommendation -n mg mongo-x-mongodb-x-update-version-uax2ot -o jsonpath='{.spec.operation}' | yq -y +apiVersion: ops.kubedb.com/v1alpha1 +kind: MongoDBOpsRequest +metadata: + name: update-version + namespace: mg +spec: + databaseRef: + name: mongo + type: UpdateVersion + updateVersion: + targetVersion: 8.0.4 +status: {} +``` + +Note: For the above command to work you need to have YQ v3 installed. + +Let's check the status part of this recommendation. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-update-version-uax2ot -o jsonpath='{.status}' | yq -y +approvalStatus: Pending +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: Pending +reason: WaitingForApproval +``` + +Now, This recommendation can be approved and operation can be executed immediately by setting `ApprovalStatus` to `Approved` and Setting `approvedWindow` to `Immediate`. You can approve this easily through Appscode UI or edit it manually. Also, You can use kubectl CLI for this - + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-update-version-uax2ot \ + -n mg \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Approved","approvedWindow":{"window":"Immediate"}}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-update-version-uax2ot patched +``` + +Now, check the status part again. You will find a condition have appeared which says `OpsRequest is successfully created`. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-update-version-uax2ot -o jsonpath='{.status}' | yq -y +approvalStatus: Approved +approvedWindow: + window: Immediate +conditions: + - lastTransitionTime: '2025-02-25T09:07:19Z' + message: OpsRequest is successfully created + reason: SuccessfullyCreatedOperation + status: 'True' + type: SuccessfullyCreatedOperation +createdOperationRef: + name: mongo-1740474439-update-version-auto +failedAttempt: 0 +outdated: false +parallelism: Namespace +phase: InProgress +reason: StartedExecutingOperation + +``` + +You will find an `MongoDBOpsRequest` custom resource have been created and, it is updating the `mongo` cluster version to `8.0.4` with negligible downtime. Let's wait for it to reach `Successful` status. + +```bash +$ kubectl get mongodbopsrequest -n mg mongo-1740474439-update-version-auto -w +NAME TYPE STATUS AGE +mongo-1740474439-update-version-auto UpdateVersion Progressing 70s +mongo-1740474439-update-version-auto UpdateVersion Progressing 99s +. +. +mongo-1740474439-update-version-auto UpdateVersion Successful 2m15s +``` + +Let's recheck the recommendation for one last time. We should find that `.status.phase` has been marked as `Succeeded`. + +```bash +$ kubectl get recommendation -n mg mongo-x-mongodb-x-update-version-uax2ot +NAME STATUS OUTDATED AGE +mongo-x-mongodb-x-update-version-uax2ot Succeeded false 78m +``` + +Finally, You can check `mongo` cluster version now, which should be upgraded to version `8.0.4`. + +```bash +$ kubectl get mg mongo -n mg +NAME VERSION STATUS AGE +mongo 8.0.4 Ready 40m +``` + +You may not want to do trigger recommended operations manually. Rather, trigger them autonomously in a preferred schedule when infrastructure is idle or traffic rate is at the lowest. For this purpose, You can create a `MaintenanceWindow` custom resource where you can set your desired schedule/period for triggering these recommended operations automatically. Here's a sample one: + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: MaintenanceWindow +metadata: + name: mongo-maintenance + namespace: mg +spec: + timezone: Asia/Dhaka + days: + Wednesday: + - start: 5:40AM + end: 7:00PM + dates: + - start: 2025-01-25T00:00:18Z + end: 2025-01-25T23:41:18Z +``` + +You can now create a `ApprovalPolicy` custom resource to refer this `MaintenanceWindow` for particular DB type. Following is a sample `ApprovalPolicy` for any `MongoDB` custom resource deployed in `mg` namespace. This `ApprovalPolicy` custom resource is referring to the `mongo-maintenance` MaintenanceWindow created in the same namespace. You can also create `ClusterMaintenanceWindow` instead which is effective for cluster-wide operations and refer it here. The following ApprovalPolicy will trigger recommended operations when referred maintenance window timeframe is reached. + +```yaml +apiVersion: supervisor.appscode.com/v1alpha1 +kind: ApprovalPolicy +metadata: + name: mg-policy + namespace: mg +maintenanceWindowRef: + name: mongo-maintenance +targets: + - group: kubedb.com + kind: MongoDB + operations: + - group: ops.kubedb.com + kind: MongoDBOpsRequest +``` + +Lastly, If you want to reject a recommendation, you can just set `ApprovalStatus` to `Rejected` in the recommendation status section. Here's how you can do it using kubectl cli. + +```bash +$ kubectl patch Recommendation mongo-x-mongodb-x-update-version-uax2ot \ + -n mg \ + --type merge \ + --subresource='status' \ + -p '{"status":{"approvalStatus":"Rejected"}}' +recommendation.supervisor.appscode.com/mongo-x-mongodb-x-update-version-uax2ot patched +``` + +## Next Steps + +- Learn about [backup & restore](/docs/guides/mongodb/backup/stash/overview/index.md) MongoDB database using Stash. +- Learn how to configure [MongoDB Cluster](/docs/guides/mongodb/clustering/replicaset.md). +- Monitor your MongoDB database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). +- Use [private Docker registry](/docs/guides/mongodb/private-registry/using-private-registry.md) to deploy MongoDB with KubeDB. +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). From c368ff20fc5ff1e2de9579167624319b56efedae Mon Sep 17 00:00:00 2001 From: raihankhan Date: Thu, 27 Feb 2025 18:51:12 +0600 Subject: [PATCH 5/5] Fix links Signed-off-by: raihankhan --- .../rotate-auth-recommendation.md | 78 +------------------ .../rotate-tls-recommendation.md | 4 +- 2 files changed, 4 insertions(+), 78 deletions(-) diff --git a/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md b/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md index 880122aeed..69ba69dea8 100644 --- a/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md +++ b/docs/guides/mongodb/recommendation/rotate-auth-recommendation.md @@ -16,7 +16,7 @@ section_menu_id: guides Rotating authentication secrets in database management is vital to mitigate security risks, such as credential leakage or unauthorized access, and to comply with regulatory requirements. Regular rotation limits the exposure of compromised credentials, reduces the risk of insider threats, and enforces updated security policies like stronger passwords or algorithms. It also ensures operational resilience by testing the rotation process and revoking stale or unused credentials. KubeDB provides `RotateAuth` which reduces manual errors, and strengthens database security with minimal effort. KubeDB Ops-manager generates Recommendation for rotating authentication secrets via this OpsRequest. -`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.elastic.co/products/mongodb) in a Kubernetes native way. The recommendation will only be created if `.spec.authSecret.rotateAfter` is set. KubeDB generates MongoDB Rotate Auth recommendation regarding two particular cases. +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.mongodb.com/) in a Kubernetes native way. The recommendation will only be created if `.spec.authSecret.rotateAfter` is set. KubeDB generates MongoDB Rotate Auth recommendation regarding two particular cases. 1. AuthSecret lifespan is more than one month and, less than one month remaining till expiry 2. AuthSecret lifespan is less than one month and, less than one third of lifespan remaining till expiry @@ -324,78 +324,4 @@ recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-auth-441xqs patc - Learn how to configure [MongoDB Cluster](/docs/guides/mongodb/clustering/replicaset.md). - Monitor your MongoDB database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). - Use [private Docker registry](/docs/guides/mongodb/private-registry/using-private-registry.md) to deploy MongoDB with KubeDB. -- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). - - -apiVersion: kubedb.com/v1 -kind: Postgres -metadata: - labels: - app.kubernetes.io/instance: postgres - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/name: postgreses.kubedb.com - name: postgres - namespace: pg -spec: - deletionPolicy: WipeOut - authSecret: - rotateAfter: 1h - podTemplate: - spec: - containers: - - name: postgres - resources: - limits: - cpu: 500m - memory: 1Gi - requests: - cpu: 500m - memory: 1Gi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - runAsGroup: 70 - runAsNonRoot: true - runAsUser: 70 - seccompProfile: - type: RuntimeDefault - - name: pg-coordinator - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - runAsGroup: 70 - runAsNonRoot: true - runAsUser: 70 - seccompProfile: - type: RuntimeDefault - initContainers: - - name: postgres-init-container - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - runAsGroup: 70 - runAsNonRoot: true - runAsUser: 70 - seccompProfile: - type: RuntimeDefault - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 999 - replicas: 3 - storage: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 4Gi - storageClassName: local-path - storageType: Durable - version: "16.4" - +- Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md). \ No newline at end of file diff --git a/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md b/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md index 403ad629c8..a71efd32fa 100644 --- a/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md +++ b/docs/guides/mongodb/recommendation/rotate-tls-recommendation.md @@ -16,7 +16,7 @@ section_menu_id: guides TLS certificate rotation in databases is essential for maintaining security, ensuring compliance, and preventing service disruptions. Regular rotation mitigates risks like certificate expiry and key compromise, adapts to evolving cryptographic standards, and maintains trust relationships with Certificate Authorities. It also enhances operational resilience by testing renewal processes and ensures smooth auditing and monitoring. To minimize risks and streamline the process, KubeDB provides ReconfigureTLS OpsRequest support. KubeDB Ops-manager generates Recommendation to rotate TLS certificates via this OpsRequest when their expiry is near. -`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.mongo.co/products/mongodb) in a Kubernetes native way. KubeDB generates MongoDB/Opensearch Rotate TLS recommendation regarding if: +`Recommendation` is a Kubernetes `Custom Resource Definitions` (CRD). It provides a declarative recommendation for KubeDB managed databases like [MongoDB](https://www.mongodb.com/) in a Kubernetes native way. KubeDB generates MongoDB/Opensearch Rotate TLS recommendation regarding if: - At least one of its certificate’s lifespan is more than one month and less than one month remaining till expiry @@ -337,7 +337,7 @@ recommendation.supervisor.appscode.com/mongo-x-mongodb-x-rotate-tls-6ujvez patch ## Next Steps - Learn about [backup & restore](/docs/guides/mongodb/backup/stash/overview/index.md) MongoDB database using Stash. -- Learn how to configure [MongoDB Topology Cluster](/docs/guides/mongodb/clustering/topology-cluster/simple-dedicated-cluster/index.md). +- Learn how to configure [MongoDB Cluster](/docs/guides/mongodb/clustering/replicaset.md). - Monitor your MongoDB database with KubeDB using [`out-of-the-box` Prometheus operator](/docs/guides/mongodb/monitoring/using-prometheus-operator.md). - Use [private Docker registry](/docs/guides/mongodb/private-registry/using-private-registry.md) to deploy MongoDB with KubeDB. - Want to hack on KubeDB? Check our [contribution guidelines](/docs/CONTRIBUTING.md).