From 69048b5c965e56d570e7a34280b8cc465cf62055 Mon Sep 17 00:00:00 2001 From: Valeriano Manassero <14011549+valeriano-manassero@users.noreply.github.com> Date: Thu, 2 Jun 2022 21:20:00 +0200 Subject: [PATCH] Fix glue agent image (#78) * Changed: avoid latest image * Changed: version bump * Fixed: pull policy * Removed: specific ci for glue since now it's on by default * Fixed: don't refresh dependencies * Changed: testing chart action version update * Fixed: action * Changed: dependency updates required * Fixed: lint and install * Revert "Changed: dependency updates required" This reverts commit 34ee22d7d062deac1a49df40c8664d05404b3633. * Changed: use copy of dep charts because ththey may become unavailable * Changed: updated readme --- .github/workflows/ci.yaml | 6 +- charts/clearml/Chart.lock | 10 +- charts/clearml/Chart.yaml | 8 +- charts/clearml/README.md | 10 +- .../clearml/charts/elasticsearch-7.16.2.tgz | Bin 27271 -> 28586 bytes charts/clearml/charts/mongodb-10.3.4.tgz | Bin 50251 -> 50252 bytes charts/clearml/charts/redis-10.9.0.tgz | Bin 60374 -> 60372 bytes charts/clearml/ci/k8sagent-values.yaml | 2 - .../templates/deployment-agentk8s.yaml | 2 +- charts/clearml/values.yaml | 2 +- dependency_charts/elasticsearch/.helmignore | 2 + dependency_charts/elasticsearch/Chart.yaml | 12 + dependency_charts/elasticsearch/Makefile | 1 + dependency_charts/elasticsearch/README.md | 457 ++++++++ .../elasticsearch/examples/config/Makefile | 21 + .../elasticsearch/examples/config/README.md | 27 + .../examples/config/test/goss.yaml | 29 + .../elasticsearch/examples/config/values.yaml | 27 + .../examples/config/watcher_encryption_key | 1 + .../elasticsearch/examples/default/Makefile | 14 + .../elasticsearch/examples/default/README.md | 25 + .../examples/default/rolling_upgrade.sh | 19 + .../examples/default/test/goss.yaml | 38 + .../examples/docker-for-mac/Makefile | 13 + .../examples/docker-for-mac/README.md | 23 + .../examples/docker-for-mac/values.yaml | 23 + .../examples/kubernetes-kind/Makefile | 17 + .../examples/kubernetes-kind/README.md | 36 + .../kubernetes-kind/values-local-path.yaml | 23 + .../examples/kubernetes-kind/values.yaml | 23 + .../elasticsearch/examples/microk8s/Makefile | 13 + .../elasticsearch/examples/microk8s/README.md | 32 + .../examples/microk8s/values.yaml | 32 + .../elasticsearch/examples/migration/Makefile | 10 + .../examples/migration/README.md | 167 +++ .../examples/migration/client.yaml | 23 + .../examples/migration/data.yaml | 17 + .../examples/migration/master.yaml | 26 + .../elasticsearch/examples/minikube/Makefile | 13 + .../elasticsearch/examples/minikube/README.md | 38 + .../examples/minikube/values.yaml | 23 + .../elasticsearch/examples/multi/Makefile | 19 + .../elasticsearch/examples/multi/README.md | 29 + .../elasticsearch/examples/multi/client.yaml | 14 + .../elasticsearch/examples/multi/data.yaml | 11 + .../elasticsearch/examples/multi/master.yaml | 11 + .../examples/multi/test/goss.yaml | 9 + .../examples/networkpolicy/Makefile | 14 + .../examples/networkpolicy/values.yaml | 37 + .../elasticsearch/examples/openshift/Makefile | 13 + .../examples/openshift/README.md | 24 + .../examples/openshift/test/goss.yaml | 16 + .../examples/openshift/values.yaml | 11 + .../elasticsearch/examples/security/Makefile | 38 + .../elasticsearch/examples/security/README.md | 29 + .../examples/security/test/goss.yaml | 44 + .../examples/security/values.yaml | 33 + .../elasticsearch/examples/upgrade/Makefile | 16 + .../elasticsearch/examples/upgrade/README.md | 17 + .../examples/upgrade/scripts/upgrade.sh | 76 ++ .../examples/upgrade/test/goss.yaml | 16 + .../examples/upgrade/values.yaml | 2 + .../elasticsearch/templates/NOTES.txt | 6 + .../elasticsearch/templates/_helpers.tpl | 65 ++ .../elasticsearch/templates/configmap.yaml | 16 + .../elasticsearch/templates/ingress.yaml | 64 ++ .../templates/networkpolicy.yaml | 61 + .../templates/poddisruptionbudget.yaml | 12 + .../templates/podsecuritypolicy.yaml | 14 + .../elasticsearch/templates/role.yaml | 25 + .../elasticsearch/templates/rolebinding.yaml | 24 + .../elasticsearch/templates/service.yaml | 77 ++ .../templates/serviceaccount.yaml | 20 + .../elasticsearch/templates/statefulset.yaml | 380 ++++++ .../test/test-elasticsearch-health.yaml | 36 + dependency_charts/elasticsearch/values.yaml | 348 ++++++ dependency_charts/mongodb/.helmignore | 21 + dependency_charts/mongodb/Chart.lock | 6 + dependency_charts/mongodb/Chart.yaml | 29 + dependency_charts/mongodb/README.md | 663 +++++++++++ .../mongodb/charts/common/.helmignore | 22 + .../mongodb/charts/common/Chart.yaml | 23 + .../mongodb/charts/common/README.md | 309 +++++ .../charts/common/templates/_affinities.tpl | 94 ++ .../charts/common/templates/_capabilities.tpl | 35 + .../charts/common/templates/_errors.tpl | 20 + .../charts/common/templates/_images.tpl | 43 + .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 32 + .../charts/common/templates/_secrets.tpl | 57 + .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 45 + .../charts/common/templates/_warnings.tpl | 14 + .../templates/validations/_cassandra.tpl | 72 ++ .../common/templates/validations/_mariadb.tpl | 103 ++ .../common/templates/validations/_mongodb.tpl | 108 ++ .../templates/validations/_postgresql.tpl | 131 +++ .../common/templates/validations/_redis.tpl | 72 ++ .../templates/validations/_validations.tpl | 44 + .../mongodb/charts/common/values.yaml | 3 + .../ci/values-replicaset-with-rbac.yaml | 8 + dependency_charts/mongodb/templates/NOTES.txt | 174 +++ .../mongodb/templates/_helpers.tpl | 294 +++++ .../mongodb/templates/arbiter/configmap.yaml | 12 + .../templates/arbiter/headless-svc.yaml | 21 + .../mongodb/templates/arbiter/pdb.yaml | 19 + .../templates/arbiter/statefulset.yaml | 267 +++++ .../mongodb/templates/configmap.yaml | 12 + .../templates/initialization-configmap.yaml | 11 + .../mongodb/templates/metrics-svc.yaml | 21 + .../mongodb/templates/prometheusrule.yaml | 14 + .../replicaset/external-access-svc.yaml | 46 + .../templates/replicaset/headless-svc.yaml | 22 + .../mongodb/templates/replicaset/pdb.yaml | 19 + .../replicaset/scripts-configmap.yaml | 88 ++ .../templates/replicaset/statefulset.yaml | 478 ++++++++ dependency_charts/mongodb/templates/role.yaml | 17 + .../mongodb/templates/rolebinding.yaml | 16 + .../mongodb/templates/secrets-ca.yaml | 30 + .../mongodb/templates/secrets.yaml | 30 + .../mongodb/templates/serviceaccount.yaml | 10 + .../mongodb/templates/servicemonitor.yaml | 26 + .../mongodb/templates/standalone/dep-sts.yaml | 411 +++++++ .../mongodb/templates/standalone/pvc.yaml | 18 + .../mongodb/templates/standalone/svc.yaml | 37 + .../mongodb/values-production.yaml | 1014 +++++++++++++++++ dependency_charts/mongodb/values.schema.json | 167 +++ dependency_charts/mongodb/values.yaml | 1014 +++++++++++++++++ dependency_charts/redis/.helmignore | 21 + dependency_charts/redis/Chart.yaml | 24 + dependency_charts/redis/README.md | 564 +++++++++ .../redis/ci/extra-flags-values.yaml | 11 + .../redis/ci/production-sentinel-values.yaml | 682 +++++++++++ .../redis/img/redis-cluster-topology.png | Bin 0 -> 11448 bytes .../redis/img/redis-topology.png | Bin 0 -> 9709 bytes dependency_charts/redis/templates/NOTES.txt | 122 ++ .../redis/templates/_helpers.tpl | 421 +++++++ .../redis/templates/configmap.yaml | 53 + .../redis/templates/headless-svc.yaml | 25 + .../redis/templates/health-configmap.yaml | 201 ++++ .../redis/templates/metrics-prometheus.yaml | 33 + .../redis/templates/metrics-svc.yaml | 31 + .../redis/templates/networkpolicy.yaml | 74 ++ dependency_charts/redis/templates/pdb.yaml | 21 + .../redis/templates/prometheusrule.yaml | 25 + dependency_charts/redis/templates/psp.yaml | 43 + .../templates/redis-master-statefulset.yaml | 524 +++++++++ .../redis/templates/redis-master-svc.yaml | 40 + .../redis/templates/redis-role.yaml | 22 + .../redis/templates/redis-rolebinding.yaml | 19 + .../redis/templates/redis-serviceaccount.yaml | 12 + .../templates/redis-slave-statefulset.yaml | 543 +++++++++ .../redis/templates/redis-slave-svc.yaml | 40 + .../templates/redis-with-sentinel-svc.yaml | 40 + dependency_charts/redis/templates/secret.yaml | 15 + .../redis/values-production.yaml | 776 +++++++++++++ dependency_charts/redis/values.schema.json | 168 +++ dependency_charts/redis/values.yaml | 776 +++++++++++++ 159 files changed, 14491 insertions(+), 23 deletions(-) delete mode 100644 charts/clearml/ci/k8sagent-values.yaml create mode 100644 dependency_charts/elasticsearch/.helmignore create mode 100644 dependency_charts/elasticsearch/Chart.yaml create mode 100644 dependency_charts/elasticsearch/Makefile create mode 100644 dependency_charts/elasticsearch/README.md create mode 100644 dependency_charts/elasticsearch/examples/config/Makefile create mode 100644 dependency_charts/elasticsearch/examples/config/README.md create mode 100644 dependency_charts/elasticsearch/examples/config/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/config/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/config/watcher_encryption_key create mode 100644 dependency_charts/elasticsearch/examples/default/Makefile create mode 100644 dependency_charts/elasticsearch/examples/default/README.md create mode 100644 dependency_charts/elasticsearch/examples/default/rolling_upgrade.sh create mode 100644 dependency_charts/elasticsearch/examples/default/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/docker-for-mac/Makefile create mode 100644 dependency_charts/elasticsearch/examples/docker-for-mac/README.md create mode 100644 dependency_charts/elasticsearch/examples/docker-for-mac/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/kubernetes-kind/Makefile create mode 100644 dependency_charts/elasticsearch/examples/kubernetes-kind/README.md create mode 100644 dependency_charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml create mode 100644 dependency_charts/elasticsearch/examples/kubernetes-kind/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/microk8s/Makefile create mode 100644 dependency_charts/elasticsearch/examples/microk8s/README.md create mode 100644 dependency_charts/elasticsearch/examples/microk8s/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/migration/Makefile create mode 100644 dependency_charts/elasticsearch/examples/migration/README.md create mode 100644 dependency_charts/elasticsearch/examples/migration/client.yaml create mode 100644 dependency_charts/elasticsearch/examples/migration/data.yaml create mode 100644 dependency_charts/elasticsearch/examples/migration/master.yaml create mode 100644 dependency_charts/elasticsearch/examples/minikube/Makefile create mode 100644 dependency_charts/elasticsearch/examples/minikube/README.md create mode 100644 dependency_charts/elasticsearch/examples/minikube/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/multi/Makefile create mode 100644 dependency_charts/elasticsearch/examples/multi/README.md create mode 100644 dependency_charts/elasticsearch/examples/multi/client.yaml create mode 100644 dependency_charts/elasticsearch/examples/multi/data.yaml create mode 100644 dependency_charts/elasticsearch/examples/multi/master.yaml create mode 100644 dependency_charts/elasticsearch/examples/multi/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/networkpolicy/Makefile create mode 100644 dependency_charts/elasticsearch/examples/networkpolicy/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/openshift/Makefile create mode 100644 dependency_charts/elasticsearch/examples/openshift/README.md create mode 100644 dependency_charts/elasticsearch/examples/openshift/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/openshift/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/security/Makefile create mode 100644 dependency_charts/elasticsearch/examples/security/README.md create mode 100644 dependency_charts/elasticsearch/examples/security/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/security/values.yaml create mode 100644 dependency_charts/elasticsearch/examples/upgrade/Makefile create mode 100644 dependency_charts/elasticsearch/examples/upgrade/README.md create mode 100644 dependency_charts/elasticsearch/examples/upgrade/scripts/upgrade.sh create mode 100644 dependency_charts/elasticsearch/examples/upgrade/test/goss.yaml create mode 100644 dependency_charts/elasticsearch/examples/upgrade/values.yaml create mode 100644 dependency_charts/elasticsearch/templates/NOTES.txt create mode 100644 dependency_charts/elasticsearch/templates/_helpers.tpl create mode 100644 dependency_charts/elasticsearch/templates/configmap.yaml create mode 100644 dependency_charts/elasticsearch/templates/ingress.yaml create mode 100644 dependency_charts/elasticsearch/templates/networkpolicy.yaml create mode 100644 dependency_charts/elasticsearch/templates/poddisruptionbudget.yaml create mode 100644 dependency_charts/elasticsearch/templates/podsecuritypolicy.yaml create mode 100644 dependency_charts/elasticsearch/templates/role.yaml create mode 100644 dependency_charts/elasticsearch/templates/rolebinding.yaml create mode 100644 dependency_charts/elasticsearch/templates/service.yaml create mode 100644 dependency_charts/elasticsearch/templates/serviceaccount.yaml create mode 100644 dependency_charts/elasticsearch/templates/statefulset.yaml create mode 100644 dependency_charts/elasticsearch/templates/test/test-elasticsearch-health.yaml create mode 100644 dependency_charts/elasticsearch/values.yaml create mode 100644 dependency_charts/mongodb/.helmignore create mode 100644 dependency_charts/mongodb/Chart.lock create mode 100644 dependency_charts/mongodb/Chart.yaml create mode 100644 dependency_charts/mongodb/README.md create mode 100644 dependency_charts/mongodb/charts/common/.helmignore create mode 100644 dependency_charts/mongodb/charts/common/Chart.yaml create mode 100644 dependency_charts/mongodb/charts/common/README.md create mode 100644 dependency_charts/mongodb/charts/common/templates/_affinities.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_capabilities.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_errors.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_images.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_labels.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_names.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_secrets.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_storage.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_tplvalues.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_utils.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/_warnings.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_cassandra.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_mariadb.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_mongodb.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_postgresql.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_redis.tpl create mode 100644 dependency_charts/mongodb/charts/common/templates/validations/_validations.tpl create mode 100644 dependency_charts/mongodb/charts/common/values.yaml create mode 100644 dependency_charts/mongodb/ci/values-replicaset-with-rbac.yaml create mode 100644 dependency_charts/mongodb/templates/NOTES.txt create mode 100644 dependency_charts/mongodb/templates/_helpers.tpl create mode 100644 dependency_charts/mongodb/templates/arbiter/configmap.yaml create mode 100644 dependency_charts/mongodb/templates/arbiter/headless-svc.yaml create mode 100644 dependency_charts/mongodb/templates/arbiter/pdb.yaml create mode 100644 dependency_charts/mongodb/templates/arbiter/statefulset.yaml create mode 100644 dependency_charts/mongodb/templates/configmap.yaml create mode 100644 dependency_charts/mongodb/templates/initialization-configmap.yaml create mode 100644 dependency_charts/mongodb/templates/metrics-svc.yaml create mode 100644 dependency_charts/mongodb/templates/prometheusrule.yaml create mode 100644 dependency_charts/mongodb/templates/replicaset/external-access-svc.yaml create mode 100644 dependency_charts/mongodb/templates/replicaset/headless-svc.yaml create mode 100644 dependency_charts/mongodb/templates/replicaset/pdb.yaml create mode 100644 dependency_charts/mongodb/templates/replicaset/scripts-configmap.yaml create mode 100644 dependency_charts/mongodb/templates/replicaset/statefulset.yaml create mode 100644 dependency_charts/mongodb/templates/role.yaml create mode 100644 dependency_charts/mongodb/templates/rolebinding.yaml create mode 100644 dependency_charts/mongodb/templates/secrets-ca.yaml create mode 100644 dependency_charts/mongodb/templates/secrets.yaml create mode 100644 dependency_charts/mongodb/templates/serviceaccount.yaml create mode 100644 dependency_charts/mongodb/templates/servicemonitor.yaml create mode 100644 dependency_charts/mongodb/templates/standalone/dep-sts.yaml create mode 100644 dependency_charts/mongodb/templates/standalone/pvc.yaml create mode 100644 dependency_charts/mongodb/templates/standalone/svc.yaml create mode 100644 dependency_charts/mongodb/values-production.yaml create mode 100644 dependency_charts/mongodb/values.schema.json create mode 100644 dependency_charts/mongodb/values.yaml create mode 100755 dependency_charts/redis/.helmignore create mode 100755 dependency_charts/redis/Chart.yaml create mode 100755 dependency_charts/redis/README.md create mode 100755 dependency_charts/redis/ci/extra-flags-values.yaml create mode 100755 dependency_charts/redis/ci/production-sentinel-values.yaml create mode 100755 dependency_charts/redis/img/redis-cluster-topology.png create mode 100755 dependency_charts/redis/img/redis-topology.png create mode 100755 dependency_charts/redis/templates/NOTES.txt create mode 100755 dependency_charts/redis/templates/_helpers.tpl create mode 100755 dependency_charts/redis/templates/configmap.yaml create mode 100755 dependency_charts/redis/templates/headless-svc.yaml create mode 100755 dependency_charts/redis/templates/health-configmap.yaml create mode 100755 dependency_charts/redis/templates/metrics-prometheus.yaml create mode 100755 dependency_charts/redis/templates/metrics-svc.yaml create mode 100755 dependency_charts/redis/templates/networkpolicy.yaml create mode 100755 dependency_charts/redis/templates/pdb.yaml create mode 100755 dependency_charts/redis/templates/prometheusrule.yaml create mode 100755 dependency_charts/redis/templates/psp.yaml create mode 100755 dependency_charts/redis/templates/redis-master-statefulset.yaml create mode 100755 dependency_charts/redis/templates/redis-master-svc.yaml create mode 100755 dependency_charts/redis/templates/redis-role.yaml create mode 100755 dependency_charts/redis/templates/redis-rolebinding.yaml create mode 100755 dependency_charts/redis/templates/redis-serviceaccount.yaml create mode 100755 dependency_charts/redis/templates/redis-slave-statefulset.yaml create mode 100755 dependency_charts/redis/templates/redis-slave-svc.yaml create mode 100755 dependency_charts/redis/templates/redis-with-sentinel-svc.yaml create mode 100755 dependency_charts/redis/templates/secret.yaml create mode 100755 dependency_charts/redis/values-production.yaml create mode 100755 dependency_charts/redis/values.schema.json create mode 100755 dependency_charts/redis/values.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7b08adb..bf8217a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,11 +33,7 @@ jobs: version: v0.11.1 node_image: kindest/node:${{ matrix.k8s }} - name: Set up chart-testing - uses: helm/chart-testing-action@v2.0.1 - - name: Add bitnami repo - run: helm repo add bitnami https://charts.bitnami.com/bitnami - - name: Add elastic repo - run: helm repo add elastic https://helm.elastic.co + uses: helm/chart-testing-action@v2.2.1 - name: Run chart-testing (list-changed) id: list-changed run: | diff --git a/charts/clearml/Chart.lock b/charts/clearml/Chart.lock index a0ade8b..3a443db 100644 --- a/charts/clearml/Chart.lock +++ b/charts/clearml/Chart.lock @@ -1,12 +1,12 @@ dependencies: - name: redis - repository: https://charts.bitnami.com/bitnami + repository: file://../../dependency_charts/redis version: 10.9.0 - name: mongodb - repository: https://charts.bitnami.com/bitnami + repository: file://../../dependency_charts/mongodb version: 10.3.4 - name: elasticsearch - repository: https://helm.elastic.co + repository: file://../../dependency_charts/elasticsearch version: 7.16.2 -digest: sha256:ac733cb02d50e8398c1d2832988333896f1c7b765c19a0f1eea5e9b24bdb8207 -generated: "2022-01-05T07:52:34.913745+01:00" +digest: sha256:149b5a49382d280b1e083f3c193d014d3d2eb7fcdf3ec1402008996960cc173a +generated: "2022-06-02T21:09:00.961174+02:00" diff --git a/charts/clearml/Chart.yaml b/charts/clearml/Chart.yaml index 74789fd..5bb2983 100644 --- a/charts/clearml/Chart.yaml +++ b/charts/clearml/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: clearml description: MLOps platform type: application -version: "3.10.2" +version: "3.10.3" appVersion: "1.4.0" home: https://clear.ml icon: https://raw.githubusercontent.com/allegroai/clearml/master/docs/clearml-logo.svg @@ -19,13 +19,13 @@ keywords: dependencies: - name: redis version: "10.9.0" - repository: "https://charts.bitnami.com/bitnami" + repository: "file://../../dependency_charts/redis" condition: redis.enabled - name: mongodb version: "10.3.4" - repository: "https://charts.bitnami.com/bitnami" + repository: "file://../../dependency_charts/mongodb" condition: mongodb.enabled - name: elasticsearch version: "7.16.2" - repository: "https://helm.elastic.co" + repository: "file://../../dependency_charts/elasticsearch" condition: elasticsearch.enabled diff --git a/charts/clearml/README.md b/charts/clearml/README.md index 6b706cf..3fcd92a 100644 --- a/charts/clearml/README.md +++ b/charts/clearml/README.md @@ -1,6 +1,6 @@ # ClearML Ecosystem for Kubernetes -![Version: 3.10.2](https://img.shields.io/badge/Version-3.10.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.4.0](https://img.shields.io/badge/AppVersion-1.4.0-informational?style=flat-square) +![Version: 3.10.3](https://img.shields.io/badge/Version-3.10.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.4.0](https://img.shields.io/badge/AppVersion-1.4.0-informational?style=flat-square) MLOps platform @@ -121,9 +121,9 @@ For detailed instructions, see the [Optional Configuration](https://github.com/a | Repository | Name | Version | |------------|------|---------| -| https://charts.bitnami.com/bitnami | mongodb | 10.3.4 | -| https://charts.bitnami.com/bitnami | redis | 10.9.0 | -| https://helm.elastic.co | elasticsearch | 7.16.2 | +| file://../../dependency_charts/elasticsearch | elasticsearch | 7.16.2 | +| file://../../dependency_charts/mongodb | mongodb | 10.3.4 | +| file://../../dependency_charts/redis | redis | 10.9.0 | ## Values @@ -181,7 +181,7 @@ For detailed instructions, see the [Optional Configuration](https://github.com/a | agentk8sglue.enabled | bool | `true` | | | agentk8sglue.id | string | `"k8s-agent"` | | | agentk8sglue.image.repository | string | `"allegroai/clearml-agent-k8s"` | | -| agentk8sglue.image.tag | string | `"latest"` | | +| agentk8sglue.image.tag | string | `"base-1.21"` | | | agentk8sglue.maxPods | int | `10` | | | agentk8sglue.podTemplate.env | list | `[]` | | | agentk8sglue.podTemplate.nodeSelector | object | `{}` | | diff --git a/charts/clearml/charts/elasticsearch-7.16.2.tgz b/charts/clearml/charts/elasticsearch-7.16.2.tgz index 39362cd3e0cc5b98cba8eafec50218a34ddc7b64..148c8329110b2611ecf223d49e2f18f927b1be30 100644 GIT binary patch literal 28586 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

8AAz8$NouO0X3UjK>&1~ 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 literal 27271 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 diff --git a/charts/clearml/charts/mongodb-10.3.4.tgz b/charts/clearml/charts/mongodb-10.3.4.tgz index 943e77a476cb2db1a08929a1ae555747f10b6314..8ab5473794edd640d526f3f26804ca3c9809ba67 100644 GIT binary patch delta 39819 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 Q00030|Am;`GXN$90CRZpcK`qY delta 39836 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

(e9J` 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 diff --git a/charts/clearml/charts/redis-10.9.0.tgz b/charts/clearml/charts/redis-10.9.0.tgz index 9bd7302ed264e66fd07a668e3fb42df646e79265..041c58dfb808666fc0589cd5bea5337b12740ff1 100644 GIT binary patch literal 60372 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 literal 60374 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 + + + + + +- [Requirements](#requirements) +- [Installing](#installing) + - [Install released version using Helm repository](#install-released-version-using-helm-repository) + - [Install development version from a branch](#install-development-version-from-a-branch) +- [Upgrading](#upgrading) +- [Usage notes](#usage-notes) +- [Configuration](#configuration) + - [Deprecated](#deprecated) +- [FAQ](#faq) + - [How to deploy this chart on a specific K8S distribution?](#how-to-deploy-this-chart-on-a-specific-k8s-distribution) + - [How to deploy dedicated nodes types?](#how-to-deploy-dedicated-nodes-types) + - [Clustering and Node Discovery](#clustering-and-node-discovery) + - [How to deploy clusters with security (authentication and TLS) enabled?](#how-to-deploy-clusters-with-security-authentication-and-tls-enabled) + - [How to migrate from helm/charts stable chart?](#how-to-migrate-from-helmcharts-stable-chart) + - [How to install plugins?](#how-to-install-plugins) + - [How to use the keystore?](#how-to-use-the-keystore) + - [Basic example](#basic-example) + - [Multiple keys](#multiple-keys) + - [Custom paths and keys](#custom-paths-and-keys) + - [How to enable snapshotting?](#how-to-enable-snapshotting) + - [How to configure templates post-deployment?](#how-to-configure-templates-post-deployment) +- [Contributing](#contributing) + + + + + + +## Requirements + +* Kubernetes >= 1.14 +* [Helm][] >= 2.17.0 +* Minimum cluster requirements include the following to run this chart with +default settings. All of these settings are configurable. + * Three Kubernetes nodes to respect the default "hard" affinity settings + * 1GB of RAM for the JVM heap + +See [supported configurations][] for more details. + +## Installing + +This chart is tested with the latest 7.16.2 version. + +### Install released version using Helm repository + +* Add the Elastic Helm charts repo: +`helm repo add elastic https://helm.elastic.co` + +* Install it: + - with Helm 3: `helm install elasticsearch --version elastic/elasticsearch` + - with Helm 2 (deprecated): `helm install --name elasticsearch --version elastic/elasticsearch` + +### Install development version from a branch + +* Clone the git repo: `git clone git@github.com:elastic/helm-charts.git` + +* Checkout the branch : `git checkout 7.16` + +* Install it: + - with Helm 3: `helm install elasticsearch ./helm-charts/elasticsearch --set imageTag=7.16.2` + - with Helm 2 (deprecated): `helm install --name elasticsearch ./helm-charts/elasticsearch --set imageTag=7.16.2` + + +## Upgrading + +Please always check [CHANGELOG.md][] and [BREAKING_CHANGES.md][] before +upgrading to a new chart version. + + +## Usage notes + +* This repo includes a number of [examples][] configurations which can be used +as a reference. They are also used in the automated testing of this chart. +* Automated testing of this chart is currently only run against GKE (Google +Kubernetes Engine). +* The chart deploys a StatefulSet and by default will do an automated rolling +update of your cluster. It does this by waiting for the cluster health to become +green after each instance is updated. If you prefer to update manually you can +set `OnDelete` [updateStrategy][]. +* It is important to verify that the JVM heap size in `esJavaOpts` and to set +the CPU/Memory `resources` to something suitable for your cluster. +* To simplify chart and maintenance each set of node groups is deployed as a +separate Helm release. Take a look at the [multi][] example to get an idea for +how this works. Without doing this it isn't possible to resize persistent +volumes in a StatefulSet. By setting it up this way it makes it possible to add +more nodes with a new storage size then drain the old ones. It also solves the +problem of allowing the user to determine which node groups to update first when +doing upgrades or changes. +* We have designed this chart to be very un-opinionated about how to configure +Elasticsearch. It exposes ways to set environment variables and mount secrets +inside of the container. Doing this makes it much easier for this chart to +support multiple versions with minimal changes. + + +## Configuration + +| Parameter | Description | Default | +|------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------| +| `antiAffinityTopologyKey` | The [anti-affinity][] topology key. By default this will prevent multiple Elasticsearch nodes from running on the same Kubernetes node | `kubernetes.io/hostname` | +| `antiAffinity` | Setting this to hard enforces the [anti-affinity][] rules. If it is set to soft it will be done "best effort". Other values will be ignored | `hard` | +| `clusterHealthCheckParams` | The [Elasticsearch cluster health status params][] that will be used by readiness [probe][] command | `wait_for_status=green&timeout=1s` | +| `clusterName` | This will be used as the Elasticsearch [cluster.name][] and should be unique per cluster in the namespace | `elasticsearch` | +| `clusterDeprecationIndexing` | Enable or disable deprecation logs to be indexed (should be disabled when deploying master only node groups) | `false` | +| `enableServiceLinks` | Set to false to disabling service links, which can cause slow pod startup times when there are many services in the current namespace. | `true` | +| `envFrom` | Templatable string to be passed to the [environment from variables][] which will be appended to the `envFrom:` definition for the container | `[]` | +| `esConfig` | Allows you to add any config files in `/usr/share/elasticsearch/config/` such as `elasticsearch.yml` and `log4j2.properties`. See [values.yaml][] for an example of the formatting | `{}` | +| `esJavaOpts` | [Java options][] for Elasticsearch. This is where you could configure the [jvm heap size][] | `""` | +| `esMajorVersion` | Deprecated. Instead, use the version of the chart corresponding to your ES minor version. Used to set major version specific configuration. If you are using a custom image and not running the default Elasticsearch version you will need to set this to the version you are running (e.g. `esMajorVersion: 6`) | `""` | +| `extraContainers` | Templatable string of additional `containers` to be passed to the `tpl` function | `""` | +| `extraEnvs` | Extra [environment variables][] which will be appended to the `env:` definition for the container | `[]` | +| `extraInitContainers` | Templatable string of additional `initContainers` to be passed to the `tpl` function | `""` | +| `extraVolumeMounts` | Templatable string of additional `volumeMounts` to be passed to the `tpl` function | `""` | +| `extraVolumes` | Templatable string of additional `volumes` to be passed to the `tpl` function | `""` | +| `fullnameOverride` | Overrides the `clusterName` and `nodeGroup` when used in the naming of resources. This should only be used when using a single `nodeGroup`, otherwise you will have name conflicts | `""` | +| `healthNameOverride` | Overrides `test-elasticsearch-health` pod name | `""` | +| `hostAliases` | Configurable [hostAliases][] | `[]` | +| `httpPort` | The http port that Kubernetes will use for the healthchecks and the service. If you change this you will also need to set [http.port][] in `extraEnvs` | `9200` | +| `imagePullPolicy` | The Kubernetes [imagePullPolicy][] value | `IfNotPresent` | +| `imagePullSecrets` | Configuration for [imagePullSecrets][] so that you can use a private registry for your image | `[]` | +| `imageTag` | The Elasticsearch Docker image tag | `7.16.2` | +| `image` | The Elasticsearch Docker image | `docker.elastic.co/elasticsearch/elasticsearch` | +| `ingress` | Configurable [ingress][] to expose the Elasticsearch service. See [values.yaml][] for an example | see [values.yaml][] | +| `initResources` | Allows you to set the [resources][] for the `initContainer` in the StatefulSet | `{}` | +| `keystore` | Allows you map Kubernetes secrets into the keystore. See the [config example][] and [how to use the keystore][] | `[]` | +| `labels` | Configurable [labels][] applied to all Elasticsearch pods | `{}` | +| `lifecycle` | Allows you to add [lifecycle hooks][]. See [values.yaml][] for an example of the formatting | `{}` | +| `masterService` | The service name used to connect to the masters. You only need to set this if your master `nodeGroup` is set to something other than `master`. See [Clustering and Node Discovery][] for more information | `""` | +| `maxUnavailable` | The [maxUnavailable][] value for the pod disruption budget. By default this will prevent Kubernetes from having more than 1 unhealthy pod in the node group | `1` | +| `minimumMasterNodes` | The value for [discovery.zen.minimum_master_nodes][]. Should be set to `(master_eligible_nodes / 2) + 1`. Ignored in Elasticsearch versions >= 7 | `2` | +| `nameOverride` | Overrides the `clusterName` when used in the naming of resources | `""` | +| `networkHost` | Value for the [network.host Elasticsearch setting][] | `0.0.0.0` | +| `networkPolicy` | The [NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) to set. See [`values.yaml`](./values.yaml) for an example | `{http.enabled: false,transport.enabled: false}` | +| `nodeAffinity` | Value for the [node affinity settings][] | `{}` | +| `nodeGroup` | This is the name that will be used for each group of nodes in the cluster. The name will be `clusterName-nodeGroup-X` , `nameOverride-nodeGroup-X` if a `nameOverride` is specified, and `fullnameOverride-X` if a `fullnameOverride` is specified | `master` | +| `nodeSelector` | Configurable [nodeSelector][] so that you can target specific nodes for your Elasticsearch cluster | `{}` | +| `persistence` | Enables a persistent volume for Elasticsearch data. Can be disabled for nodes that only have [roles][] which don't require persistent data | see [values.yaml][] | +| `podAnnotations` | Configurable [annotations][] applied to all Elasticsearch pods | `{}` | +| `podManagementPolicy` | By default Kubernetes [deploys StatefulSets serially][]. This deploys them in parallel so that they can discover each other | `Parallel` | +| `podSecurityContext` | Allows you to set the [securityContext][] for the pod | see [values.yaml][] | +| `podSecurityPolicy` | Configuration for create a pod security policy with minimal permissions to run this Helm chart with `create: true`. Also can be used to reference an external pod security policy with `name: "externalPodSecurityPolicy"` | see [values.yaml][] | +| `priorityClassName` | The name of the [PriorityClass][]. No default is supplied as the PriorityClass must be created first | `""` | +| `protocol` | The protocol that will be used for the readiness [probe][]. Change this to `https` if you have `xpack.security.http.ssl.enabled` set | `http` | +| `rbac` | Configuration for creating a role, role binding and ServiceAccount as part of this Helm chart with `create: true`. Also can be used to reference an external ServiceAccount with `serviceAccountName: "externalServiceAccountName"`, or automount the service account token | see [values.yaml][] | +| `readinessProbe` | Configuration fields for the readiness [probe][] | see [values.yaml][] | +| `replicas` | Kubernetes replica count for the StatefulSet (i.e. how many pods) | `3` | +| `resources` | Allows you to set the [resources][] for the StatefulSet | see [values.yaml][] | +| `roles` | A hash map with the specific [roles][] for the `nodeGroup` | see [values.yaml][] | +| `schedulerName` | Name of the [alternate scheduler][] | `""` | +| `secretMounts` | Allows you easily mount a secret as a file inside the StatefulSet. Useful for mounting certificates and other secrets. See [values.yaml][] for an example | `[]` | +| `securityContext` | Allows you to set the [securityContext][] for the container | see [values.yaml][] | +| `service.annotations` | [LoadBalancer annotations][] that Kubernetes will use for the service. This will configure load balancer if `service.type` is `LoadBalancer` | `{}` | +| `service.enabled` | Enable non-headless service | `true` | +| `service.externalTrafficPolicy` | Some cloud providers allow you to specify the [LoadBalancer externalTrafficPolicy][]. Kubernetes will use this to preserve the client source IP. This will configure load balancer if `service.type` is `LoadBalancer` | `""` | +| `service.httpPortName` | The name of the http port within the service | `http` | +| `service.labelsHeadless` | Labels to be added to headless service | `{}` | +| `service.labels` | Labels to be added to non-headless service | `{}` | +| `service.loadBalancerIP` | Some cloud providers allow you to specify the [loadBalancer][] IP. If the `loadBalancerIP` field is not specified, the IP is dynamically assigned. If you specify a `loadBalancerIP` but your cloud provider does not support the feature, it is ignored. | `""` | +| `service.loadBalancerSourceRanges` | The IP ranges that are allowed to access | `[]` | +| `service.nodePort` | Custom [nodePort][] port that can be set if you are using `service.type: nodePort` | `""` | +| `service.transportPortName` | The name of the transport port within the service | `transport` | +| `service.type` | Elasticsearch [Service Types][] | `ClusterIP` | +| `sysctlInitContainer` | Allows you to disable the `sysctlInitContainer` if you are setting [sysctl vm.max_map_count][] with another method | `enabled: true` | +| `sysctlVmMaxMapCount` | Sets the [sysctl vm.max_map_count][] needed for Elasticsearch | `262144` | +| `terminationGracePeriod` | The [terminationGracePeriod][] in seconds used when trying to stop the pod | `120` | +| `tests.enabled` | Enable creating test related resources when running `helm template` or `helm test` | `true` | +| `tolerations` | Configurable [tolerations][] | `[]` | +| `transportPort` | The transport port that Kubernetes will use for the service. If you change this you will also need to set [transport port configuration][] in `extraEnvs` | `9300` | +| `updateStrategy` | The [updateStrategy][] for the StatefulSet. By default Kubernetes will wait for the cluster to be green after upgrading each pod. Setting this to `OnDelete` will allow you to manually delete each pod during upgrades | `RollingUpdate` | +| `volumeClaimTemplate` | Configuration for the [volumeClaimTemplate for StatefulSets][]. You will want to adjust the storage (default `30Gi` ) and the `storageClassName` if you are using a different storage class | see [values.yaml][] | + +### Deprecated + +| Parameter | Description | Default | +|-----------|---------------------------------------------------------------------------------------------------------------|---------| +| `fsGroup` | The Group ID (GID) for [securityContext][] so that the Elasticsearch user can read from the persistent volume | `""` | + + +## FAQ + +### How to deploy this chart on a specific K8S distribution? + +This chart is designed to run on production scale Kubernetes clusters with +multiple nodes, lots of memory and persistent storage. For that reason it can be +a bit tricky to run them against local Kubernetes environments such as +[Minikube][]. + +This chart is highly tested with [GKE][], but some K8S distribution also +requires specific configurations. + +We provide examples of configuration for the following K8S providers: + +- [Docker for Mac][] +- [KIND][] +- [Minikube][] +- [MicroK8S][] +- [OpenShift][] + +### How to deploy dedicated nodes types? + +All the Elasticsearch pods deployed share the same configuration. If you need to +deploy dedicated [nodes types][] (for example dedicated master and data nodes), +you can deploy multiple releases of this chart with different configurations +while they share the same `clusterName` value. + +For each Helm release, the nodes types can then be defined using `roles` value. + +An example of Elasticsearch cluster using 2 different Helm releases for master +and data nodes can be found in [examples/multi][]. + +#### Clustering and Node Discovery + +This chart facilitates Elasticsearch node discovery and services by creating two +`Service` definitions in Kubernetes, one with the name `$clusterName-$nodeGroup` +and another named `$clusterName-$nodeGroup-headless`. +Only `Ready` pods are a part of the `$clusterName-$nodeGroup` service, while all +pods ( `Ready` or not) are a part of `$clusterName-$nodeGroup-headless`. + +If your group of master nodes has the default `nodeGroup: master` then you can +just add new groups of nodes with a different `nodeGroup` and they will +automatically discover the correct master. If your master nodes have a different +`nodeGroup` name then you will need to set `masterService` to +`$clusterName-$masterNodeGroup`. + +The chart value for `masterService` is used to populate +`discovery.zen.ping.unicast.hosts` , which Elasticsearch nodes will use to +contact master nodes and form a cluster. +Therefore, to add a group of nodes to an existing cluster, setting +`masterService` to the desired `Service` name of the related cluster is +sufficient. + +### How to deploy clusters with security (authentication and TLS) enabled? + +This Helm chart can use existing [Kubernetes secrets][] to setup +credentials or certificates for examples. These secrets should be created +outside of this chart and accessed using [environment variables][] and volumes. + +An example of Elasticsearch cluster using security can be found in +[examples/security][]. + +### How to migrate from helm/charts stable chart? + +If you currently have a cluster deployed with the [helm/charts stable][] chart +you can follow the [migration guide][]. + +### How to install plugins? + +The recommended way to install plugins into our Docker images is to create a +[custom Docker image][]. + +The Dockerfile would look something like: + +``` +ARG elasticsearch_version +FROM docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch_version} + +RUN bin/elasticsearch-plugin install --batch repository-gcs +``` + +And then updating the `image` in values to point to your custom image. + +There are a couple reasons we recommend this. + +1. Tying the availability of Elasticsearch to the download service to install +plugins is not a great idea or something that we recommend. Especially in +Kubernetes where it is normal and expected for a container to be moved to +another host at random times. +2. Mutating the state of a running Docker image (by installing plugins) goes +against best practices of containers and immutable infrastructure. + +### How to use the keystore? + +#### Basic example + +Create the secret, the key name needs to be the keystore key path. In this +example we will create a secret from a file and from a literal string. + +``` +kubectl create secret generic encryption-key --from-file=xpack.watcher.encryption_key=./watcher_encryption_key +kubectl create secret generic slack-hook --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' +``` + +To add these secrets to the keystore: + +``` +keystore: + - secretName: encryption-key + - secretName: slack-hook +``` + +#### Multiple keys + +All keys in the secret will be added to the keystore. To create the previous +example in one secret you could also do: + +``` +kubectl create secret generic keystore-secrets --from-file=xpack.watcher.encryption_key=./watcher_encryption_key --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' +``` + +``` +keystore: + - secretName: keystore-secrets +``` + +#### Custom paths and keys + +If you are using these secrets for other applications (besides the Elasticsearch +keystore) then it is also possible to specify the keystore path and which keys +you want to add. Everything specified under each `keystore` item will be passed +through to the `volumeMounts` section for mounting the [secret][]. In this +example we will only add the `slack_hook` key from a secret that also has other +keys. Our secret looks like this: + +``` +kubectl create secret generic slack-secrets --from-literal=slack_channel='#general' --from-literal=slack_hook='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' +``` + +We only want to add the `slack_hook` key to the keystore at path +`xpack.notification.slack.account.monitoring.secure_url`: + +``` +keystore: + - secretName: slack-secrets + items: + - key: slack_hook + path: xpack.notification.slack.account.monitoring.secure_url +``` + +You can also take a look at the [config example][] which is used as part of the +automated testing pipeline. + +### How to enable snapshotting? + +1. Install your [snapshot plugin][] into a custom Docker image following the +[how to install plugins guide][]. +2. Add any required secrets or credentials into an Elasticsearch keystore +following the [how to use the keystore][] guide. +3. Configure the [snapshot repository][] as you normally would. +4. To automate snapshots you can use [Snapshot Lifecycle Management][] or a tool +like [curator][]. + +### How to configure templates post-deployment? + +You can use `postStart` [lifecycle hooks][] to run code triggered after a +container is created. + +Here is an example of `postStart` hook to configure templates: + +```yaml +lifecycle: + postStart: + exec: + command: + - bash + - -c + - | + #!/bin/bash + # Add a template to adjust number of shards/replicas + TEMPLATE_NAME=my_template + INDEX_PATTERN="logstash-*" + SHARD_COUNT=8 + REPLICA_COUNT=1 + ES_URL=http://localhost:9200 + while [[ "$(curl -s -o /dev/null -w '%{http_code}\n' $ES_URL)" != "200" ]]; do sleep 1; done + curl -XPUT "$ES_URL/_template/$TEMPLATE_NAME" -H 'Content-Type: application/json' -d'{"index_patterns":['\""$INDEX_PATTERN"\"'],"settings":{"number_of_shards":'$SHARD_COUNT',"number_of_replicas":'$REPLICA_COUNT'}}' +``` + + +## Contributing + +Please check [CONTRIBUTING.md][] before any contribution or for any questions +about our development and testing process. + +[7.16]: https://github.com/elastic/helm-charts/releases +[#63]: https://github.com/elastic/helm-charts/issues/63 +[BREAKING_CHANGES.md]: https://github.com/elastic/helm-charts/blob/master/BREAKING_CHANGES.md +[CHANGELOG.md]: https://github.com/elastic/helm-charts/blob/master/CHANGELOG.md +[CONTRIBUTING.md]: https://github.com/elastic/helm-charts/blob/master/CONTRIBUTING.md +[alternate scheduler]: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/#specify-schedulers-for-pods +[annotations]: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +[anti-affinity]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +[cluster.name]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/important-settings.html#cluster-name +[clustering and node discovery]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/README.md#clustering-and-node-discovery +[config example]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/config/values.yaml +[curator]: https://www.elastic.co/guide/en/elasticsearch/client/curator/7.9/snapshot.html +[custom docker image]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/docker.html#_c_customized_image +[deploys statefulsets serially]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies +[discovery.zen.minimum_master_nodes]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/discovery-settings.html#minimum_master_nodes +[docker for mac]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/docker-for-mac +[elasticsearch cluster health status params]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/cluster-health.html#request-params +[elasticsearch docker image]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/docker.html +[environment variables]: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/#using-environment-variables-inside-of-your-config +[environment from variables]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables +[examples]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/ +[examples/multi]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi +[examples/security]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/security +[gke]: https://cloud.google.com/kubernetes-engine +[helm]: https://helm.sh +[helm/charts stable]: https://github.com/helm/charts/tree/master/stable/elasticsearch/ +[how to install plugins guide]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/README.md#how-to-install-plugins +[how to use the keystore]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/README.md#how-to-use-the-keystore +[http.port]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-http.html#_settings +[imagePullPolicy]: https://kubernetes.io/docs/concepts/containers/images/#updating-images +[imagePullSecrets]: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret +[ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/ +[java options]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/jvm-options.html +[jvm heap size]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/heap-size.html +[hostAliases]: https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +[kind]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/kubernetes-kind +[kubernetes secrets]: https://kubernetes.io/docs/concepts/configuration/secret/ +[labels]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +[lifecycle hooks]: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ +[loadBalancer annotations]: https://kubernetes.io/docs/concepts/services-networking/service/#ssl-support-on-aws +[loadBalancer externalTrafficPolicy]: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip +[loadBalancer]: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer +[maxUnavailable]: https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget +[migration guide]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/migration/README.md +[minikube]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/minikube +[microk8s]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/microk8s +[multi]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi/ +[network.host elasticsearch setting]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/network.host.html +[node affinity settings]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity-beta-feature +[node-certificates]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/configuring-tls.html#node-certificates +[nodePort]: https://kubernetes.io/docs/concepts/services-networking/service/#nodeport +[nodes types]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-node.html +[nodeSelector]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector +[openshift]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/openshift +[priorityClass]: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass +[probe]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +[resources]: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ +[roles]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-node.html +[secret]: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets +[securityContext]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +[service types]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types +[snapshot lifecycle management]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/snapshot-lifecycle-management.html +[snapshot plugin]: https://www.elastic.co/guide/en/elasticsearch/plugins/7.16/repository.html +[snapshot repository]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-snapshots.html +[supported configurations]: https://github.com/elastic/helm-charts/tree/7.16/README.md#supported-configurations +[sysctl vm.max_map_count]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/vm-max-map-count.html#vm-max-map-count +[terminationGracePeriod]: https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods +[tolerations]: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +[transport port configuration]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-transport.html#_transport_settings +[updateStrategy]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +[values.yaml]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/values.yaml +[volumeClaimTemplate for statefulsets]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-storage diff --git a/dependency_charts/elasticsearch/examples/config/Makefile b/dependency_charts/elasticsearch/examples/config/Makefile new file mode 100644 index 0000000..9ae9c37 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/config/Makefile @@ -0,0 +1,21 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-config +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +secrets: + kubectl delete secret elastic-config-credentials elastic-config-secret elastic-config-slack elastic-config-custom-path || true + kubectl create secret generic elastic-config-credentials --from-literal=password=changeme --from-literal=username=elastic + kubectl create secret generic elastic-config-slack --from-literal=xpack.notification.slack.account.monitoring.secure_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' + kubectl create secret generic elastic-config-secret --from-file=xpack.watcher.encryption_key=./watcher_encryption_key + kubectl create secret generic elastic-config-custom-path --from-literal=slack_url='https://hooks.slack.com/services/asdasdasd/asdasdas/asdasd' --from-literal=thing_i_don_tcare_about=test + +test: secrets install goss + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/config/README.md b/dependency_charts/elasticsearch/examples/config/README.md new file mode 100644 index 0000000..3a548ab --- /dev/null +++ b/dependency_charts/elasticsearch/examples/config/README.md @@ -0,0 +1,27 @@ +# Config + +This example deploy a single node Elasticsearch 7.16.2 with authentication and +custom [values][]. + + +## Usage + +* Create the required secrets: `make secrets` + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/config-master 9200 + curl -u elastic:changeme http://localhost:9200/_cat/indices + ``` + + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/config/test/goss.yaml +[values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/config/values.yaml diff --git a/dependency_charts/elasticsearch/examples/config/test/goss.yaml b/dependency_charts/elasticsearch/examples/config/test/goss.yaml new file mode 100644 index 0000000..752db8d --- /dev/null +++ b/dependency_charts/elasticsearch/examples/config/test/goss.yaml @@ -0,0 +1,29 @@ +http: + http://localhost:9200/_cluster/health: + status: 200 + timeout: 2000 + username: elastic + password: "{{ .Env.ELASTIC_PASSWORD }}" + body: + - "green" + - '"number_of_nodes":1' + - '"number_of_data_nodes":1' + + http://localhost:9200: + status: 200 + timeout: 2000 + username: elastic + password: "{{ .Env.ELASTIC_PASSWORD }}" + body: + - '"cluster_name" : "config"' + - "You Know, for Search" + +command: + "elasticsearch-keystore list": + exit-status: 0 + stdout: + - keystore.seed + - bootstrap.password + - xpack.notification.slack.account.monitoring.secure_url + - xpack.notification.slack.account.otheraccount.secure_url + - xpack.watcher.encryption_key diff --git a/dependency_charts/elasticsearch/examples/config/values.yaml b/dependency_charts/elasticsearch/examples/config/values.yaml new file mode 100644 index 0000000..13cff2c --- /dev/null +++ b/dependency_charts/elasticsearch/examples/config/values.yaml @@ -0,0 +1,27 @@ +--- + +clusterName: "config" +replicas: 1 + +extraEnvs: + - name: ELASTIC_PASSWORD + valueFrom: + secretKeyRef: + name: elastic-config-credentials + key: password + +# This is just a dummy file to make sure that +# the keystore can be mounted at the same time +# as a custom elasticsearch.yml +esConfig: + elasticsearch.yml: | + xpack.security.enabled: true + path.data: /usr/share/elasticsearch/data + +keystore: + - secretName: elastic-config-secret + - secretName: elastic-config-slack + - secretName: elastic-config-custom-path + items: + - key: slack_url + path: xpack.notification.slack.account.otheraccount.secure_url diff --git a/dependency_charts/elasticsearch/examples/config/watcher_encryption_key b/dependency_charts/elasticsearch/examples/config/watcher_encryption_key new file mode 100644 index 0000000..b5f9078 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/config/watcher_encryption_key @@ -0,0 +1 @@ +supersecret diff --git a/dependency_charts/elasticsearch/examples/default/Makefile b/dependency_charts/elasticsearch/examples/default/Makefile new file mode 100644 index 0000000..389bf99 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/default/Makefile @@ -0,0 +1,14 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-default +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install $(RELEASE) ../../ + +test: install goss + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/default/README.md b/dependency_charts/elasticsearch/examples/default/README.md new file mode 100644 index 0000000..341fdae --- /dev/null +++ b/dependency_charts/elasticsearch/examples/default/README.md @@ -0,0 +1,25 @@ +# Default + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster using +[default values][]. + + +## Usage + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/default/test/goss.yaml +[default values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/values.yaml diff --git a/dependency_charts/elasticsearch/examples/default/rolling_upgrade.sh b/dependency_charts/elasticsearch/examples/default/rolling_upgrade.sh new file mode 100644 index 0000000..c5a2a88 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/default/rolling_upgrade.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash -x + +kubectl proxy || true & + +make & +PROC_ID=$! + +while kill -0 "$PROC_ID" >/dev/null 2>&1; do + echo "PROCESS IS RUNNING" + if curl --fail 'http://localhost:8001/api/v1/proxy/namespaces/default/services/elasticsearch-master:9200/_search' ; then + echo "cluster is healthy" + else + echo "cluster not healthy!" + exit 1 + fi + sleep 1 +done +echo "PROCESS TERMINATED" +exit 0 diff --git a/dependency_charts/elasticsearch/examples/default/test/goss.yaml b/dependency_charts/elasticsearch/examples/default/test/goss.yaml new file mode 100644 index 0000000..74fcfe6 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/default/test/goss.yaml @@ -0,0 +1,38 @@ +kernel-param: + vm.max_map_count: + value: "262144" + +http: + http://elasticsearch-master:9200/_cluster/health: + status: 200 + timeout: 2000 + body: + - "green" + - '"number_of_nodes":3' + - '"number_of_data_nodes":3' + + http://localhost:9200: + status: 200 + timeout: 2000 + body: + - '"number" : "7.16.2"' + - '"cluster_name" : "elasticsearch"' + - "You Know, for Search" + +file: + /usr/share/elasticsearch/data: + exists: true + mode: "2775" + owner: root + group: elasticsearch + filetype: directory + +mount: + /usr/share/elasticsearch/data: + exists: true + +user: + elasticsearch: + exists: true + uid: 1000 + gid: 1000 diff --git a/dependency_charts/elasticsearch/examples/docker-for-mac/Makefile b/dependency_charts/elasticsearch/examples/docker-for-mac/Makefile new file mode 100644 index 0000000..18fd053 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/docker-for-mac/Makefile @@ -0,0 +1,13 @@ +default: test + +RELEASE := helm-es-docker-for-mac +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/docker-for-mac/README.md b/dependency_charts/elasticsearch/examples/docker-for-mac/README.md new file mode 100644 index 0000000..1eab221 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/docker-for-mac/README.md @@ -0,0 +1,23 @@ +# Docker for Mac + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster on [Docker for Mac][] +using [custom values][]. + +Note that this configuration should be used for test only and isn't recommended +for production. + + +## Usage + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + + +[custom values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/docker-for-mac/values.yaml +[docker for mac]: https://docs.docker.com/docker-for-mac/kubernetes/ diff --git a/dependency_charts/elasticsearch/examples/docker-for-mac/values.yaml b/dependency_charts/elasticsearch/examples/docker-for-mac/values.yaml new file mode 100644 index 0000000..f7deba6 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/docker-for-mac/values.yaml @@ -0,0 +1,23 @@ +--- +# Permit co-located instances for solitary minikube virtual machines. +antiAffinity: "soft" + +# Shrink default JVM heap. +esJavaOpts: "-Xmx128m -Xms128m" + +# Allocate smaller chunks of memory per pod. +resources: + requests: + cpu: "100m" + memory: "512M" + limits: + cpu: "1000m" + memory: "512M" + +# Request smaller persistent volumes. +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "hostpath" + resources: + requests: + storage: 100M diff --git a/dependency_charts/elasticsearch/examples/kubernetes-kind/Makefile b/dependency_charts/elasticsearch/examples/kubernetes-kind/Makefile new file mode 100644 index 0000000..9e5602d --- /dev/null +++ b/dependency_charts/elasticsearch/examples/kubernetes-kind/Makefile @@ -0,0 +1,17 @@ +default: test + +RELEASE := helm-es-kind +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +install-local-path: + kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values-local-path.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/kubernetes-kind/README.md b/dependency_charts/elasticsearch/examples/kubernetes-kind/README.md new file mode 100644 index 0000000..59c12ac --- /dev/null +++ b/dependency_charts/elasticsearch/examples/kubernetes-kind/README.md @@ -0,0 +1,36 @@ +# KIND + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster on [Kind][] +using [custom values][]. + +Note that this configuration should be used for test only and isn't recommended +for production. + +Note that Kind < 0.7.0 are affected by a [kind issue][] with mount points +created from PVCs not writable by non-root users. [kubernetes-sigs/kind#1157][] +fix it in Kind 0.7.0. + +The workaround for Kind < 0.7.0 is to install manually +[Rancher Local Path Provisioner][] and use `local-path` storage class for +Elasticsearch volumes (see [Makefile][] instructions). + + +## Usage + +* For Kind >= 0.7.0: Deploy Elasticsearch chart with the default values: `make install` +* For Kind < 0.7.0: Deploy Elasticsearch chart with `local-path` storage class: `make install-local-path` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + + +[custom values]: https://github.com/elastic/helm-charts/blob/7.16/elasticsearch/examples/kubernetes-kind/values.yaml +[kind]: https://kind.sigs.k8s.io/ +[kind issue]: https://github.com/kubernetes-sigs/kind/issues/830 +[kubernetes-sigs/kind#1157]: https://github.com/kubernetes-sigs/kind/pull/1157 +[rancher local path provisioner]: https://github.com/rancher/local-path-provisioner +[Makefile]: https://github.com/elastic/helm-charts/blob/7.16/elasticsearch/examples/kubernetes-kind/Makefile#L5 diff --git a/dependency_charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml b/dependency_charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml new file mode 100644 index 0000000..500ad4b --- /dev/null +++ b/dependency_charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml @@ -0,0 +1,23 @@ +--- +# Permit co-located instances for solitary minikube virtual machines. +antiAffinity: "soft" + +# Shrink default JVM heap. +esJavaOpts: "-Xmx128m -Xms128m" + +# Allocate smaller chunks of memory per pod. +resources: + requests: + cpu: "100m" + memory: "512M" + limits: + cpu: "1000m" + memory: "512M" + +# Request smaller persistent volumes. +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "local-path" + resources: + requests: + storage: 100M diff --git a/dependency_charts/elasticsearch/examples/kubernetes-kind/values.yaml b/dependency_charts/elasticsearch/examples/kubernetes-kind/values.yaml new file mode 100644 index 0000000..500ad4b --- /dev/null +++ b/dependency_charts/elasticsearch/examples/kubernetes-kind/values.yaml @@ -0,0 +1,23 @@ +--- +# Permit co-located instances for solitary minikube virtual machines. +antiAffinity: "soft" + +# Shrink default JVM heap. +esJavaOpts: "-Xmx128m -Xms128m" + +# Allocate smaller chunks of memory per pod. +resources: + requests: + cpu: "100m" + memory: "512M" + limits: + cpu: "1000m" + memory: "512M" + +# Request smaller persistent volumes. +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "local-path" + resources: + requests: + storage: 100M diff --git a/dependency_charts/elasticsearch/examples/microk8s/Makefile b/dependency_charts/elasticsearch/examples/microk8s/Makefile new file mode 100644 index 0000000..2d0012d --- /dev/null +++ b/dependency_charts/elasticsearch/examples/microk8s/Makefile @@ -0,0 +1,13 @@ +default: test + +RELEASE := helm-es-microk8s +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/microk8s/README.md b/dependency_charts/elasticsearch/examples/microk8s/README.md new file mode 100644 index 0000000..a79dfa7 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/microk8s/README.md @@ -0,0 +1,32 @@ +# MicroK8S + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster on [MicroK8S][] +using [custom values][]. + +Note that this configuration should be used for test only and isn't recommended +for production. + + +## Requirements + +The following MicroK8S [addons][] need to be enabled: +- `dns` +- `helm` +- `storage` + + +## Usage + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + + +[addons]: https://microk8s.io/docs/addons +[custom values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/microk8s/values.yaml +[MicroK8S]: https://microk8s.io diff --git a/dependency_charts/elasticsearch/examples/microk8s/values.yaml b/dependency_charts/elasticsearch/examples/microk8s/values.yaml new file mode 100644 index 0000000..2627ecb --- /dev/null +++ b/dependency_charts/elasticsearch/examples/microk8s/values.yaml @@ -0,0 +1,32 @@ +--- +# Disable privileged init Container creation. +sysctlInitContainer: + enabled: false + +# Restrict the use of the memory-mapping when sysctlInitContainer is disabled. +esConfig: + elasticsearch.yml: | + node.store.allow_mmap: false + +# Permit co-located instances for solitary minikube virtual machines. +antiAffinity: "soft" + +# Shrink default JVM heap. +esJavaOpts: "-Xmx128m -Xms128m" + +# Allocate smaller chunks of memory per pod. +resources: + requests: + cpu: "100m" + memory: "512M" + limits: + cpu: "1000m" + memory: "512M" + +# Request smaller persistent volumes. +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "microk8s-hostpath" + resources: + requests: + storage: 100M diff --git a/dependency_charts/elasticsearch/examples/migration/Makefile b/dependency_charts/elasticsearch/examples/migration/Makefile new file mode 100644 index 0000000..020906f --- /dev/null +++ b/dependency_charts/elasticsearch/examples/migration/Makefile @@ -0,0 +1,10 @@ +PREFIX := helm-es-migration + +data: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values data.yaml $(PREFIX)-data ../../ + +master: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values master.yaml $(PREFIX)-master ../../ + +client: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values client.yaml $(PREFIX)-client ../../ diff --git a/dependency_charts/elasticsearch/examples/migration/README.md b/dependency_charts/elasticsearch/examples/migration/README.md new file mode 100644 index 0000000..02d6ee8 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/migration/README.md @@ -0,0 +1,167 @@ +# Migration Guide from helm/charts + +There are two viable options for migrating from the community Elasticsearch Helm +chart from the [helm/charts][] repo. + +1. Restoring from Snapshot to a fresh cluster +2. Live migration by joining a new cluster to the existing cluster. + +## Restoring from Snapshot + +This is the recommended and preferred option. The downside is that it will +involve a period of write downtime during the migration. If you have a way to +temporarily stop writes to your cluster then this is the way to go. This is also +a lot simpler as it just involves launching a fresh cluster and restoring a +snapshot following the [restoring to a different cluster guide][]. + +## Live migration + +If restoring from a snapshot is not possible due to the write downtime then a +live migration is also possible. It is very important to first test this in a +testing environment to make sure you are comfortable with the process and fully +understand what is happening. + +This process will involve joining a new set of master, data and client nodes to +an existing cluster that has been deployed using the [helm/charts][] community +chart. Nodes will then be replaced one by one in a controlled fashion to +decommission the old cluster. + +This example will be using the default values for the existing helm/charts +release and for the Elastic helm-charts release. If you have changed any of the +default values then you will need to first make sure that your values are +configured in a compatible way before starting the migration. + +The process will involve a re-sync and a rolling restart of all of your data +nodes. Therefore it is important to disable shard allocation and perform a synced +flush like you normally would during any other rolling upgrade. See the +[rolling upgrades guide][] for more information. + +* The default image for this chart is +`docker.elastic.co/elasticsearch/elasticsearch` which contains the default +distribution of Elasticsearch with a [basic license][]. Make sure to update the +`image` and `imageTag` values to the correct Docker image and Elasticsearch +version that you currently have deployed. + +* Convert your current helm/charts configuration into something that is +compatible with this chart. + +* Take a fresh snapshot of your cluster. If something goes wrong you want to be +able to restore your data no matter what. + +* Check that your clusters health is green. If not abort and make sure your +cluster is healthy before continuing: + + ``` + curl localhost:9200/_cluster/health + ``` + +* Deploy new data nodes which will join the existing cluster. Take a look at the +configuration in [data.yaml][]: + + ``` + make data + ``` + +* Check that the new nodes have joined the cluster (run this and any other curl +commands from within one of your pods): + + ``` + curl localhost:9200/_cat/nodes + ``` + +* Check that your cluster is still green. If so we can now start to scale down +the existing data nodes. Assuming you have the default amount of data nodes (2) +we now want to scale it down to 1: + + ``` + kubectl scale statefulsets my-release-elasticsearch-data --replicas=1 + ``` + +* Wait for your cluster to become green again: + + ``` + watch 'curl -s localhost:9200/_cluster/health' + ``` + +* Once the cluster is green we can scale down again: + + ``` + kubectl scale statefulsets my-release-elasticsearch-data --replicas=0 + ``` + +* Wait for the cluster to be green again. +* OK. We now have all data nodes running in the new cluster. Time to replace the +masters by firstly scaling down the masters from 3 to 2. Between each step make +sure to wait for the cluster to become green again, and check with +`curl localhost:9200/_cat/nodes` that you see the correct amount of master +nodes. During this process we will always make sure to keep at least 2 master +nodes as to not lose quorum: + + ``` + kubectl scale statefulsets my-release-elasticsearch-master --replicas=2 + ``` + +* Now deploy a single new master so that we have 3 masters again. See +[master.yaml][] for the configuration: + + ``` + make master + ``` + +* Scale down old masters to 1: + + ``` + kubectl scale statefulsets my-release-elasticsearch-master --replicas=1 + ``` + +* Edit the masters in [masters.yaml][] to 2 and redeploy: + + ``` + make master + ``` + +* Scale down the old masters to 0: + + ``` + kubectl scale statefulsets my-release-elasticsearch-master --replicas=0 + ``` + +* Edit the [masters.yaml][] to have 3 replicas and remove the +`discovery.zen.ping.unicast.hosts` entry from `extraEnvs` then redeploy the +masters. This will make sure all 3 masters are running in the new cluster and +are pointing at each other for discovery: + + ``` + make master + ``` + +* Remove the `discovery.zen.ping.unicast.hosts` entry from `extraEnvs` then +redeploy the data nodes to make sure they are pointing at the new masters: + + ``` + make data + ``` + +* Deploy the client nodes: + + ``` + make client + ``` + +* Update any processes that are talking to the existing client nodes and point +them to the new client nodes. Once this is done you can scale down the old +client nodes: + + ``` + kubectl scale deployment my-release-elasticsearch-client --replicas=0 + ``` + +* The migration should now be complete. After verifying that everything is +working correctly you can cleanup leftover resources from your old cluster. + +[basic license]: https://www.elastic.co/subscriptions +[data.yaml]: https://github.com/elastic/helm-charts/blob/7.16/elasticsearch/examples/migration/data.yaml +[helm/charts]: https://github.com/helm/charts/tree/7.16/stable/elasticsearch +[master.yaml]: https://github.com/elastic/helm-charts/blob/7.16/elasticsearch/examples/migration/master.yaml +[restoring to a different cluster guide]: https://www.elastic.co/guide/en/elasticsearch/reference/6.8/modules-snapshots.html#_restoring_to_a_different_cluster +[rolling upgrades guide]: https://www.elastic.co/guide/en/elasticsearch/reference/6.8/rolling-upgrades.html diff --git a/dependency_charts/elasticsearch/examples/migration/client.yaml b/dependency_charts/elasticsearch/examples/migration/client.yaml new file mode 100644 index 0000000..30ee700 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/migration/client.yaml @@ -0,0 +1,23 @@ +--- + +replicas: 2 + +clusterName: "elasticsearch" +nodeGroup: "client" + +esMajorVersion: 6 + +roles: + master: "false" + ingest: "false" + data: "false" + +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "standard" + resources: + requests: + storage: 1Gi # Currently needed till pvcs are made optional + +persistence: + enabled: false diff --git a/dependency_charts/elasticsearch/examples/migration/data.yaml b/dependency_charts/elasticsearch/examples/migration/data.yaml new file mode 100644 index 0000000..eedcbb0 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/migration/data.yaml @@ -0,0 +1,17 @@ +--- + +replicas: 2 + +esMajorVersion: 6 + +extraEnvs: + - name: discovery.zen.ping.unicast.hosts + value: "my-release-elasticsearch-discovery" + +clusterName: "elasticsearch" +nodeGroup: "data" + +roles: + master: "false" + ingest: "false" + data: "true" diff --git a/dependency_charts/elasticsearch/examples/migration/master.yaml b/dependency_charts/elasticsearch/examples/migration/master.yaml new file mode 100644 index 0000000..3e3a2f1 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/migration/master.yaml @@ -0,0 +1,26 @@ +--- + +# Temporarily set to 3 so we can scale up/down the old a new cluster +# one at a time whilst always keeping 3 masters running +replicas: 1 + +esMajorVersion: 6 + +extraEnvs: + - name: discovery.zen.ping.unicast.hosts + value: "my-release-elasticsearch-discovery" + +clusterName: "elasticsearch" +nodeGroup: "master" + +roles: + master: "true" + ingest: "false" + data: "false" + +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "standard" + resources: + requests: + storage: 4Gi diff --git a/dependency_charts/elasticsearch/examples/minikube/Makefile b/dependency_charts/elasticsearch/examples/minikube/Makefile new file mode 100644 index 0000000..1021d98 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/minikube/Makefile @@ -0,0 +1,13 @@ +default: test + +RELEASE := helm-es-minikube +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/minikube/README.md b/dependency_charts/elasticsearch/examples/minikube/README.md new file mode 100644 index 0000000..d5d4b76 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/minikube/README.md @@ -0,0 +1,38 @@ +# Minikube + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster on [Minikube][] +using [custom values][]. + +If helm or kubectl timeouts occur, you may consider creating a minikube VM with +more CPU cores or memory allocated. + +Note that this configuration should be used for test only and isn't recommended +for production. + + +## Requirements + +In order to properly support the required persistent volume claims for the +Elasticsearch StatefulSet, the `default-storageclass` and `storage-provisioner` +minikube addons must be enabled. + +``` +minikube addons enable default-storageclass +minikube addons enable storage-provisioner +``` + + +## Usage + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + + +[custom values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/minikube/values.yaml +[minikube]: https://minikube.sigs.k8s.io/docs/ diff --git a/dependency_charts/elasticsearch/examples/minikube/values.yaml b/dependency_charts/elasticsearch/examples/minikube/values.yaml new file mode 100644 index 0000000..ccceb3a --- /dev/null +++ b/dependency_charts/elasticsearch/examples/minikube/values.yaml @@ -0,0 +1,23 @@ +--- +# Permit co-located instances for solitary minikube virtual machines. +antiAffinity: "soft" + +# Shrink default JVM heap. +esJavaOpts: "-Xmx128m -Xms128m" + +# Allocate smaller chunks of memory per pod. +resources: + requests: + cpu: "100m" + memory: "512M" + limits: + cpu: "1000m" + memory: "512M" + +# Request smaller persistent volumes. +volumeClaimTemplate: + accessModes: [ "ReadWriteOnce" ] + storageClassName: "standard" + resources: + requests: + storage: 100M diff --git a/dependency_charts/elasticsearch/examples/multi/Makefile b/dependency_charts/elasticsearch/examples/multi/Makefile new file mode 100644 index 0000000..243e504 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/Makefile @@ -0,0 +1,19 @@ +default: test + +include ../../../helpers/examples.mk + +PREFIX := helm-es-multi +RELEASE := helm-es-multi-master +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values master.yaml $(PREFIX)-master ../../ + helm upgrade --wait --timeout=$(TIMEOUT) --install --values data.yaml $(PREFIX)-data ../../ + helm upgrade --wait --timeout=$(TIMEOUT) --install --values client.yaml $(PREFIX)-client ../../ + +test: install goss + +purge: + helm del $(PREFIX)-master + helm del $(PREFIX)-data + helm del $(PREFIX)-client diff --git a/dependency_charts/elasticsearch/examples/multi/README.md b/dependency_charts/elasticsearch/examples/multi/README.md new file mode 100644 index 0000000..3b476ed --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/README.md @@ -0,0 +1,29 @@ +# Multi + +This example deploy an Elasticsearch 7.16.2 cluster composed of 3 different Helm +releases: + +- `helm-es-multi-master` for the 3 master nodes using [master values][] +- `helm-es-multi-data` for the 3 data nodes using [data values][] +- `helm-es-multi-client` for the 3 client nodes using [client values][] + +## Usage + +* Deploy the 3 Elasticsearch releases: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/multi-master 9200 + curl -u elastic:changeme http://localhost:9200/_cat/indices + ``` + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[client values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi/client.yaml +[data values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi/data.yaml +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi/test/goss.yaml +[master values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/multi/master.yaml diff --git a/dependency_charts/elasticsearch/examples/multi/client.yaml b/dependency_charts/elasticsearch/examples/multi/client.yaml new file mode 100644 index 0000000..dbe5b05 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/client.yaml @@ -0,0 +1,14 @@ +--- + +clusterName: "multi" +nodeGroup: "client" + +roles: + master: "false" + ingest: "false" + data: "false" + ml: "false" + remote_cluster_client: "false" + +persistence: + enabled: false diff --git a/dependency_charts/elasticsearch/examples/multi/data.yaml b/dependency_charts/elasticsearch/examples/multi/data.yaml new file mode 100644 index 0000000..2e3a909 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/data.yaml @@ -0,0 +1,11 @@ +--- + +clusterName: "multi" +nodeGroup: "data" + +roles: + master: "false" + ingest: "true" + data: "true" + ml: "false" + remote_cluster_client: "false" diff --git a/dependency_charts/elasticsearch/examples/multi/master.yaml b/dependency_charts/elasticsearch/examples/multi/master.yaml new file mode 100644 index 0000000..6b8c082 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/master.yaml @@ -0,0 +1,11 @@ +--- + +clusterName: "multi" +nodeGroup: "master" + +roles: + master: "true" + ingest: "false" + data: "false" + ml: "false" + remote_cluster_client: "false" diff --git a/dependency_charts/elasticsearch/examples/multi/test/goss.yaml b/dependency_charts/elasticsearch/examples/multi/test/goss.yaml new file mode 100644 index 0000000..794416b --- /dev/null +++ b/dependency_charts/elasticsearch/examples/multi/test/goss.yaml @@ -0,0 +1,9 @@ +http: + http://localhost:9200/_cluster/health: + status: 200 + timeout: 2000 + body: + - 'green' + - '"cluster_name":"multi"' + - '"number_of_nodes":9' + - '"number_of_data_nodes":3' diff --git a/dependency_charts/elasticsearch/examples/networkpolicy/Makefile b/dependency_charts/elasticsearch/examples/networkpolicy/Makefile new file mode 100644 index 0000000..e7b20c5 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/networkpolicy/Makefile @@ -0,0 +1,14 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-networkpolicy +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: install goss + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/networkpolicy/values.yaml b/dependency_charts/elasticsearch/examples/networkpolicy/values.yaml new file mode 100644 index 0000000..1963d20 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/networkpolicy/values.yaml @@ -0,0 +1,37 @@ +networkPolicy: + http: + enabled: true + explicitNamespacesSelector: + # Accept from namespaces with all those different rules (from whitelisted Pods) + matchLabels: + role: frontend-http + matchExpressions: + - {key: role, operator: In, values: [frontend-http]} + additionalRules: + - podSelector: + matchLabels: + role: frontend-http + - podSelector: + matchExpressions: + - key: role + operator: In + values: + - frontend-http + transport: + enabled: true + allowExternal: true + explicitNamespacesSelector: + matchLabels: + role: frontend-transport + matchExpressions: + - {key: role, operator: In, values: [frontend-transport]} + additionalRules: + - podSelector: + matchLabels: + role: frontend-transport + - podSelector: + matchExpressions: + - key: role + operator: In + values: + - frontend-transport diff --git a/dependency_charts/elasticsearch/examples/openshift/Makefile b/dependency_charts/elasticsearch/examples/openshift/Makefile new file mode 100644 index 0000000..078c33c --- /dev/null +++ b/dependency_charts/elasticsearch/examples/openshift/Makefile @@ -0,0 +1,13 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := elasticsearch + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: install goss + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/openshift/README.md b/dependency_charts/elasticsearch/examples/openshift/README.md new file mode 100644 index 0000000..61790f0 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/openshift/README.md @@ -0,0 +1,24 @@ +# OpenShift + +This example deploy a 3 nodes Elasticsearch 7.16.2 cluster on [OpenShift][] +using [custom values][]. + +## Usage + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/elasticsearch-master 9200 + curl localhost:9200/_cat/indices + ``` + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[custom values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/openshift/values.yaml +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/openshift/test/goss.yaml +[openshift]: https://www.openshift.com/ diff --git a/dependency_charts/elasticsearch/examples/openshift/test/goss.yaml b/dependency_charts/elasticsearch/examples/openshift/test/goss.yaml new file mode 100644 index 0000000..d6ab78b --- /dev/null +++ b/dependency_charts/elasticsearch/examples/openshift/test/goss.yaml @@ -0,0 +1,16 @@ +http: + http://localhost:9200/_cluster/health: + status: 200 + timeout: 2000 + body: + - "green" + - '"number_of_nodes":3' + - '"number_of_data_nodes":3' + + http://localhost:9200: + status: 200 + timeout: 2000 + body: + - '"number" : "7.16.2"' + - '"cluster_name" : "elasticsearch"' + - "You Know, for Search" diff --git a/dependency_charts/elasticsearch/examples/openshift/values.yaml b/dependency_charts/elasticsearch/examples/openshift/values.yaml new file mode 100644 index 0000000..8a21126 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/openshift/values.yaml @@ -0,0 +1,11 @@ +--- + +securityContext: + runAsUser: null + +podSecurityContext: + fsGroup: null + runAsUser: null + +sysctlInitContainer: + enabled: false diff --git a/dependency_charts/elasticsearch/examples/security/Makefile b/dependency_charts/elasticsearch/examples/security/Makefile new file mode 100644 index 0000000..beddbef --- /dev/null +++ b/dependency_charts/elasticsearch/examples/security/Makefile @@ -0,0 +1,38 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-security +ELASTICSEARCH_IMAGE := docker.elastic.co/elasticsearch/elasticsearch:$(STACK_VERSION) +TIMEOUT := 1200s + +install: + helm upgrade --wait --timeout=$(TIMEOUT) --install --values values.yaml $(RELEASE) ../../ + +test: secrets install goss + +purge: + kubectl delete secrets elastic-credentials elastic-certificates elastic-certificate-pem elastic-certificate-crt|| true + helm del $(RELEASE) + +pull-elasticsearch-image: + docker pull $(ELASTICSEARCH_IMAGE) + +secrets: + docker rm -f elastic-helm-charts-certs || true + rm -f elastic-certificates.p12 elastic-certificate.pem elastic-certificate.crt elastic-stack-ca.p12 || true + password=$$([ ! -z "$$ELASTIC_PASSWORD" ] && echo $$ELASTIC_PASSWORD || echo $$(docker run --rm busybox:1.31.1 /bin/sh -c "< /dev/urandom tr -cd '[:alnum:]' | head -c20")) && \ + docker run --name elastic-helm-charts-certs -i -w /app \ + $(ELASTICSEARCH_IMAGE) \ + /bin/sh -c " \ + elasticsearch-certutil ca --out /app/elastic-stack-ca.p12 --pass '' && \ + elasticsearch-certutil cert --name security-master --dns security-master --ca /app/elastic-stack-ca.p12 --pass '' --ca-pass '' --out /app/elastic-certificates.p12" && \ + docker cp elastic-helm-charts-certs:/app/elastic-certificates.p12 ./ && \ + docker rm -f elastic-helm-charts-certs && \ + openssl pkcs12 -nodes -passin pass:'' -in elastic-certificates.p12 -out elastic-certificate.pem && \ + openssl x509 -outform der -in elastic-certificate.pem -out elastic-certificate.crt && \ + kubectl create secret generic elastic-certificates --from-file=elastic-certificates.p12 && \ + kubectl create secret generic elastic-certificate-pem --from-file=elastic-certificate.pem && \ + kubectl create secret generic elastic-certificate-crt --from-file=elastic-certificate.crt && \ + kubectl create secret generic elastic-credentials --from-literal=password=$$password --from-literal=username=elastic && \ + rm -f elastic-certificates.p12 elastic-certificate.pem elastic-certificate.crt elastic-stack-ca.p12 diff --git a/dependency_charts/elasticsearch/examples/security/README.md b/dependency_charts/elasticsearch/examples/security/README.md new file mode 100644 index 0000000..7231786 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/security/README.md @@ -0,0 +1,29 @@ +# Security + +This example deploy a 3 nodes Elasticsearch 7.16.2 with authentication and +autogenerated certificates for TLS (see [values][]). + +Note that this configuration should be used for test only. For a production +deployment you should generate SSL certificates following the [official docs][]. + +## Usage + +* Create the required secrets: `make secrets` + +* Deploy Elasticsearch chart with the default values: `make install` + +* You can now setup a port forward to query Elasticsearch API: + + ``` + kubectl port-forward svc/security-master 9200 + curl -u elastic:changeme https://localhost:9200/_cat/indices + ``` + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/security/test/goss.yaml +[official docs]: https://www.elastic.co/guide/en/elasticsearch/reference/7.16/configuring-tls.html#node-certificates +[values]: https://github.com/elastic/helm-charts/tree/7.16/elasticsearch/examples/security/values.yaml diff --git a/dependency_charts/elasticsearch/examples/security/test/goss.yaml b/dependency_charts/elasticsearch/examples/security/test/goss.yaml new file mode 100644 index 0000000..c52e05f --- /dev/null +++ b/dependency_charts/elasticsearch/examples/security/test/goss.yaml @@ -0,0 +1,44 @@ +http: + https://security-master:9200/_cluster/health: + status: 200 + timeout: 2000 + allow-insecure: true + username: elastic + password: "{{ .Env.ELASTIC_PASSWORD }}" + body: + - "green" + - '"number_of_nodes":3' + - '"number_of_data_nodes":3' + + https://localhost:9200/: + status: 200 + timeout: 2000 + allow-insecure: true + username: elastic + password: "{{ .Env.ELASTIC_PASSWORD }}" + body: + - '"cluster_name" : "security"' + - "You Know, for Search" + + https://localhost:9200/_xpack/license: + status: 200 + timeout: 2000 + allow-insecure: true + username: elastic + password: "{{ .Env.ELASTIC_PASSWORD }}" + body: + - "active" + - "basic" + +file: + /usr/share/elasticsearch/config/elasticsearch.yml: + exists: true + contains: + - "xpack.security.enabled: true" + - "xpack.security.transport.ssl.enabled: true" + - "xpack.security.transport.ssl.verification_mode: certificate" + - "xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12" + - "xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12" + - "xpack.security.http.ssl.enabled: true" + - "xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12" + - "xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12" diff --git a/dependency_charts/elasticsearch/examples/security/values.yaml b/dependency_charts/elasticsearch/examples/security/values.yaml new file mode 100644 index 0000000..ac26231 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/security/values.yaml @@ -0,0 +1,33 @@ +--- +clusterName: "security" +nodeGroup: "master" + +roles: + master: "true" + ingest: "true" + data: "true" + +protocol: https + +esConfig: + elasticsearch.yml: | + xpack.security.enabled: true + xpack.security.transport.ssl.enabled: true + xpack.security.transport.ssl.verification_mode: certificate + xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12 + xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12 + xpack.security.http.ssl.enabled: true + xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12 + xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12 + +extraEnvs: + - name: ELASTIC_PASSWORD + valueFrom: + secretKeyRef: + name: elastic-credentials + key: password + +secretMounts: + - name: elastic-certificates + secretName: elastic-certificates + path: /usr/share/elasticsearch/config/certs diff --git a/dependency_charts/elasticsearch/examples/upgrade/Makefile b/dependency_charts/elasticsearch/examples/upgrade/Makefile new file mode 100644 index 0000000..9251d3b --- /dev/null +++ b/dependency_charts/elasticsearch/examples/upgrade/Makefile @@ -0,0 +1,16 @@ +default: test + +include ../../../helpers/examples.mk + +CHART := elasticsearch +RELEASE := helm-es-upgrade +FROM := 7.4.0 # versions before 7.4.O aren't compatible with Kubernetes >= 1.16.0 + +install: + ../../../helpers/upgrade.sh --chart $(CHART) --release $(RELEASE) --from $(FROM) + kubectl rollout status statefulset upgrade-master + +test: install goss + +purge: + helm del $(RELEASE) diff --git a/dependency_charts/elasticsearch/examples/upgrade/README.md b/dependency_charts/elasticsearch/examples/upgrade/README.md new file mode 100644 index 0000000..85977f5 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/upgrade/README.md @@ -0,0 +1,17 @@ +# Upgrade + +This example will deploy a 3 node Elasticsearch cluster chart using an old chart +version, then upgrade it. + + +## Usage + +* Deploy and upgrade Elasticsearch chart with the default values: `make install` + + +## Testing + +You can also run [goss integration tests][] using `make test`. + + +[goss integration tests]: https://github.com/elastic/helm-charts/tree/master/elasticsearch/examples/upgrade/test/goss.yaml diff --git a/dependency_charts/elasticsearch/examples/upgrade/scripts/upgrade.sh b/dependency_charts/elasticsearch/examples/upgrade/scripts/upgrade.sh new file mode 100644 index 0000000..59853e0 --- /dev/null +++ b/dependency_charts/elasticsearch/examples/upgrade/scripts/upgrade.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<-EOF + USAGE: + $0 [--release ] [--from ] + $0 --help + + OPTIONS: + --release + Name of the Helm release to install + --from + Elasticsearch version to use for first install + EOF + exit 1 +} + +RELEASE="helm-es-upgrade" +FROM="" + +while [[ $# -gt 0 ]] +do + key="$1" + + case $key in + --help) + usage + ;; + --release) + RELEASE="$2" + shift 2 + ;; + --from) + FROM="$2" + shift 2 + ;; + *) + log "Unrecognized argument: '$key'" + usage + ;; + esac +done + +if ! command -v jq > /dev/null +then + echo 'jq is required to use this script' + echo 'please check https://stedolan.github.io/jq/download/ to install it' + exit 1 +fi + +# Elasticsearch chart < 7.4.0 are not compatible with K8S >= 1.16) +if [[ -z $FROM ]] +then + KUBE_MINOR_VERSION=$(kubectl version -o json | jq --raw-output --exit-status '.serverVersion.minor' | sed 's/[^0-9]*//g') + + if [ "$KUBE_MINOR_VERSION" -lt 16 ] + then + FROM="7.0.0-alpha1" + else + FROM="7.4.0" + fi +fi + +helm repo add elastic https://helm.elastic.co + +# Initial install +printf "Installing Elasticsearch chart %s\n" "$FROM" +helm upgrade --wait --timeout=600s --install "$RELEASE" elastic/elasticsearch --version "$FROM" --set clusterName=upgrade +kubectl rollout status sts/upgrade-master --timeout=600s + +# Upgrade +printf "Upgrading Elasticsearch chart\n" +helm upgrade --wait --timeout=600s --set terminationGracePeriod=121 --install "$RELEASE" ../../ --set clusterName=upgrade +kubectl rollout status sts/upgrade-master --timeout=600s diff --git a/dependency_charts/elasticsearch/examples/upgrade/test/goss.yaml b/dependency_charts/elasticsearch/examples/upgrade/test/goss.yaml new file mode 100644 index 0000000..db8405f --- /dev/null +++ b/dependency_charts/elasticsearch/examples/upgrade/test/goss.yaml @@ -0,0 +1,16 @@ +http: + http://localhost:9200/_cluster/health: + status: 200 + timeout: 2000 + body: + - "green" + - '"number_of_nodes":3' + - '"number_of_data_nodes":3' + + http://localhost:9200: + status: 200 + timeout: 2000 + body: + - '"number" : "7.16.2"' + - '"cluster_name" : "upgrade"' + - "You Know, for Search" diff --git a/dependency_charts/elasticsearch/examples/upgrade/values.yaml b/dependency_charts/elasticsearch/examples/upgrade/values.yaml new file mode 100644 index 0000000..de0283a --- /dev/null +++ b/dependency_charts/elasticsearch/examples/upgrade/values.yaml @@ -0,0 +1,2 @@ +--- +clusterName: upgrade diff --git a/dependency_charts/elasticsearch/templates/NOTES.txt b/dependency_charts/elasticsearch/templates/NOTES.txt new file mode 100644 index 0000000..88b5dd5 --- /dev/null +++ b/dependency_charts/elasticsearch/templates/NOTES.txt @@ -0,0 +1,6 @@ +1. Watch all cluster members come up. + $ kubectl get pods --namespace={{ .Release.Namespace }} -l app={{ template "elasticsearch.uname" . }} -w +{{- if .Values.tests.enabled -}} +2. Test cluster health using Helm test. + $ helm --namespace={{ .Release.Namespace }} test {{ .Release.Name }} +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/_helpers.tpl b/dependency_charts/elasticsearch/templates/_helpers.tpl new file mode 100644 index 0000000..d373f2a --- /dev/null +++ b/dependency_charts/elasticsearch/templates/_helpers.tpl @@ -0,0 +1,65 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "elasticsearch.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "elasticsearch.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "elasticsearch.uname" -}} +{{- if empty .Values.fullnameOverride -}} +{{- if empty .Values.nameOverride -}} +{{ .Values.clusterName }}-{{ .Values.nodeGroup }} +{{- else -}} +{{ .Values.nameOverride }}-{{ .Values.nodeGroup }} +{{- end -}} +{{- else -}} +{{ .Values.fullnameOverride }} +{{- end -}} +{{- end -}} + +{{- define "elasticsearch.masterService" -}} +{{- if empty .Values.masterService -}} +{{- if empty .Values.fullnameOverride -}} +{{- if empty .Values.nameOverride -}} +{{ .Values.clusterName }}-master +{{- else -}} +{{ .Values.nameOverride }}-master +{{- end -}} +{{- else -}} +{{ .Values.fullnameOverride }} +{{- end -}} +{{- else -}} +{{ .Values.masterService }} +{{- end -}} +{{- end -}} + +{{- define "elasticsearch.endpoints" -}} +{{- $replicas := int (toString (.Values.replicas)) }} +{{- $uname := (include "elasticsearch.uname" .) }} + {{- range $i, $e := untilStep 0 $replicas 1 -}} +{{ $uname }}-{{ $i }}, + {{- end -}} +{{- end -}} + +{{- define "elasticsearch.esMajorVersion" -}} +{{- if .Values.esMajorVersion -}} +{{ .Values.esMajorVersion }} +{{- else -}} +{{- $version := int (index (.Values.imageTag | splitList ".") 0) -}} + {{- if and (contains "docker.elastic.co/elasticsearch/elasticsearch" .Values.image) (not (eq $version 0)) -}} +{{ $version }} + {{- else -}} +7 + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/configmap.yaml b/dependency_charts/elasticsearch/templates/configmap.yaml new file mode 100644 index 0000000..4274d8b --- /dev/null +++ b/dependency_charts/elasticsearch/templates/configmap.yaml @@ -0,0 +1,16 @@ +{{- if .Values.esConfig }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "elasticsearch.uname" . }}-config + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" +data: +{{- range $path, $config := .Values.esConfig }} + {{ $path }}: | +{{ $config | indent 4 -}} +{{- end -}} +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/ingress.yaml b/dependency_charts/elasticsearch/templates/ingress.yaml new file mode 100644 index 0000000..e60cebf --- /dev/null +++ b/dependency_charts/elasticsearch/templates/ingress.yaml @@ -0,0 +1,64 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "elasticsearch.uname" . -}} +{{- $httpPort := .Values.httpPort -}} +{{- $pathtype := .Values.ingress.pathtype -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className | quote }} + {{- end }} +{{- if .Values.ingress.tls }} + tls: + {{- if .ingressPath }} + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- else }} +{{ toYaml .Values.ingress.tls | indent 4 }} + {{- end }} +{{- end}} + rules: + {{- range .Values.ingress.hosts }} + {{- if $ingressPath }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + pathType: {{ $pathtype }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $httpPort }} + {{- else }} + - host: {{ .host }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ $pathtype }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ .servicePort | default $httpPort }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} diff --git a/dependency_charts/elasticsearch/templates/networkpolicy.yaml b/dependency_charts/elasticsearch/templates/networkpolicy.yaml new file mode 100644 index 0000000..62bb1bd --- /dev/null +++ b/dependency_charts/elasticsearch/templates/networkpolicy.yaml @@ -0,0 +1,61 @@ +{{- if (or .Values.networkPolicy.http.enabled .Values.networkPolicy.transport.enabled) }} +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: {{ template "elasticsearch.uname" . }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" +spec: + podSelector: + matchLabels: + app: "{{ template "elasticsearch.uname" . }}" + ingress: # Allow inbound connections + +{{- if .Values.networkPolicy.http.enabled }} + # For HTTP access + - ports: + - port: {{ .Values.httpPort }} + from: + # From authorized Pods (having the correct label) + - podSelector: + matchLabels: + {{ template "elasticsearch.uname" . }}-http-client: "true" +{{- with .Values.networkPolicy.http.explicitNamespacesSelector }} + # From authorized namespaces + namespaceSelector: +{{ toYaml . | indent 12 }} +{{- end }} +{{- with .Values.networkPolicy.http.additionalRules }} + # Or from custom additional rules +{{ toYaml . | indent 8 }} +{{- end }} +{{- end }} + +{{- if .Values.networkPolicy.transport.enabled }} + # For transport access + - ports: + - port: {{ .Values.transportPort }} + from: + # From authorized Pods (having the correct label) + - podSelector: + matchLabels: + {{ template "elasticsearch.uname" . }}-transport-client: "true" +{{- with .Values.networkPolicy.transport.explicitNamespacesSelector }} + # From authorized namespaces + namespaceSelector: +{{ toYaml . | indent 12 }} +{{- end }} +{{- with .Values.networkPolicy.transport.additionalRules }} + # Or from custom additional rules +{{ toYaml . | indent 8 }} +{{- end }} + # Or from other ElasticSearch Pods + - podSelector: + matchLabels: + app: "{{ template "elasticsearch.uname" . }}" +{{- end }} + +{{- end }} diff --git a/dependency_charts/elasticsearch/templates/poddisruptionbudget.yaml b/dependency_charts/elasticsearch/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..df6c74e --- /dev/null +++ b/dependency_charts/elasticsearch/templates/poddisruptionbudget.yaml @@ -0,0 +1,12 @@ +--- +{{- if .Values.maxUnavailable }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: "{{ template "elasticsearch.uname" . }}-pdb" +spec: + maxUnavailable: {{ .Values.maxUnavailable }} + selector: + matchLabels: + app: "{{ template "elasticsearch.uname" . }}" +{{- end }} diff --git a/dependency_charts/elasticsearch/templates/podsecuritypolicy.yaml b/dependency_charts/elasticsearch/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000..d8b3545 --- /dev/null +++ b/dependency_charts/elasticsearch/templates/podsecuritypolicy.yaml @@ -0,0 +1,14 @@ +{{- if .Values.podSecurityPolicy.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ default $fullName .Values.podSecurityPolicy.name | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +spec: +{{ toYaml .Values.podSecurityPolicy.spec | indent 2 }} +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/role.yaml b/dependency_charts/elasticsearch/templates/role.yaml new file mode 100644 index 0000000..d3a7ee3 --- /dev/null +++ b/dependency_charts/elasticsearch/templates/role.yaml @@ -0,0 +1,25 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $fullName | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +rules: + - apiGroups: + - extensions + resources: + - podsecuritypolicies + resourceNames: + {{- if eq .Values.podSecurityPolicy.name "" }} + - {{ $fullName | quote }} + {{- else }} + - {{ .Values.podSecurityPolicy.name | quote }} + {{- end }} + verbs: + - use +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/rolebinding.yaml b/dependency_charts/elasticsearch/templates/rolebinding.yaml new file mode 100644 index 0000000..7a529d9 --- /dev/null +++ b/dependency_charts/elasticsearch/templates/rolebinding.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $fullName | quote }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +subjects: + - kind: ServiceAccount + {{- if eq .Values.rbac.serviceAccountName "" }} + name: {{ $fullName | quote }} + {{- else }} + name: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} + namespace: {{ .Release.Namespace | quote }} +roleRef: + kind: Role + name: {{ $fullName | quote }} + apiGroup: rbac.authorization.k8s.io +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/service.yaml b/dependency_charts/elasticsearch/templates/service.yaml new file mode 100644 index 0000000..1da6951 --- /dev/null +++ b/dependency_charts/elasticsearch/templates/service.yaml @@ -0,0 +1,77 @@ +--- +{{- if .Values.service.enabled -}} +kind: Service +apiVersion: v1 +metadata: +{{- if eq .Values.nodeGroup "master" }} + name: {{ template "elasticsearch.masterService" . }} +{{- else }} + name: {{ template "elasticsearch.uname" . }} +{{- end }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4}} +{{- end }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" + ports: + - name: {{ .Values.service.httpPortName | default "http" }} + protocol: TCP + port: {{ .Values.httpPort }} +{{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} +{{- end }} + - name: {{ .Values.service.transportPortName | default "transport" }} + protocol: TCP + port: {{ .Values.transportPort }} +{{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} +{{- end }} +{{- with .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml . | indent 4 }} +{{- end }} +{{- if .Values.service.externalTrafficPolicy }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }} +{{- end }} +{{- end }} +--- +kind: Service +apiVersion: v1 +metadata: +{{- if eq .Values.nodeGroup "master" }} + name: {{ template "elasticsearch.masterService" . }}-headless +{{- else }} + name: {{ template "elasticsearch.uname" . }}-headless +{{- end }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" +{{- if .Values.service.labelsHeadless }} +{{ toYaml .Values.service.labelsHeadless | indent 4 }} +{{- end }} + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" +spec: + clusterIP: None # This is needed for statefulset hostnames like elasticsearch-0 to resolve + # Create endpoints also if the related pod isn't ready + publishNotReadyAddresses: true + selector: + app: "{{ template "elasticsearch.uname" . }}" + ports: + - name: {{ .Values.service.httpPortName | default "http" }} + port: {{ .Values.httpPort }} + - name: {{ .Values.service.transportPortName | default "transport" }} + port: {{ .Values.transportPort }} diff --git a/dependency_charts/elasticsearch/templates/serviceaccount.yaml b/dependency_charts/elasticsearch/templates/serviceaccount.yaml new file mode 100644 index 0000000..801d1cf --- /dev/null +++ b/dependency_charts/elasticsearch/templates/serviceaccount.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + {{- if eq .Values.rbac.serviceAccountName "" }} + name: {{ $fullName | quote }} + {{- else }} + name: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} + annotations: + {{- with .Values.rbac.serviceAccountAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ $fullName | quote }} +{{- end -}} diff --git a/dependency_charts/elasticsearch/templates/statefulset.yaml b/dependency_charts/elasticsearch/templates/statefulset.yaml new file mode 100644 index 0000000..fcebe0e --- /dev/null +++ b/dependency_charts/elasticsearch/templates/statefulset.yaml @@ -0,0 +1,380 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "elasticsearch.uname" . }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" + {{- range $key, $value := .Values.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + annotations: + esMajorVersion: "{{ include "elasticsearch.esMajorVersion" . }}" +spec: + serviceName: {{ template "elasticsearch.uname" . }}-headless + selector: + matchLabels: + app: "{{ template "elasticsearch.uname" . }}" + replicas: {{ .Values.replicas }} + podManagementPolicy: {{ .Values.podManagementPolicy }} + updateStrategy: + type: {{ .Values.updateStrategy }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: {{ template "elasticsearch.uname" . }} + {{- if .Values.persistence.labels.enabled }} + labels: + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" + {{- range $key, $value := .Values.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- with .Values.persistence.annotations }} + annotations: +{{ toYaml . | indent 8 }} + {{- end }} + spec: +{{ toYaml .Values.volumeClaimTemplate | indent 6 }} + {{- end }} + template: + metadata: + name: "{{ template "elasticsearch.uname" . }}" + labels: + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}" + app: "{{ template "elasticsearch.uname" . }}" + {{- range $key, $value := .Values.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + annotations: + {{- range $key, $value := .Values.podAnnotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{/* This forces a restart if the configmap has changed */}} + {{- if .Values.esConfig }} + configchecksum: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum | trunc 63 }} + {{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} + securityContext: +{{ toYaml .Values.podSecurityContext | indent 8 }} + {{- if .Values.fsGroup }} + fsGroup: {{ .Values.fsGroup }} # Deprecated value, please use .Values.podSecurityContext.fsGroup + {{- end }} + {{- if .Values.rbac.create }} + serviceAccountName: "{{ template "elasticsearch.uname" . }}" + {{- else if not (eq .Values.rbac.serviceAccountName "") }} + serviceAccountName: {{ .Values.rbac.serviceAccountName | quote }} + {{- end }} + automountServiceAccountToken: {{ .Values.rbac.automountToken }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 6 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if or (eq .Values.antiAffinity "hard") (eq .Values.antiAffinity "soft") .Values.nodeAffinity }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + affinity: + {{- end }} + {{- if eq .Values.antiAffinity "hard" }} + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - "{{ template "elasticsearch.uname" .}}" + topologyKey: {{ .Values.antiAffinityTopologyKey }} + {{- else if eq .Values.antiAffinity "soft" }} + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + topologyKey: {{ .Values.antiAffinityTopologyKey }} + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - "{{ template "elasticsearch.uname" . }}" + {{- end }} + {{- with .Values.nodeAffinity }} + nodeAffinity: +{{ toYaml . | indent 10 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriod }} + volumes: + {{- range .Values.secretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- if .defaultMode }} + defaultMode: {{ .defaultMode }} + {{- end }} + {{- end }} + {{- if .Values.esConfig }} + - name: esconfig + configMap: + name: {{ template "elasticsearch.uname" . }}-config + {{- end }} +{{- if .Values.keystore }} + - name: keystore + emptyDir: {} + {{- range .Values.keystore }} + - name: keystore-{{ .secretName }} + secret: {{ toYaml . | nindent 12 }} + {{- end }} +{{ end }} + {{- if .Values.extraVolumes }} + # Currently some extra blocks accept strings + # to continue with backwards compatibility this is being kept + # whilst also allowing for yaml to be specified too. + {{- if eq "string" (printf "%T" .Values.extraVolumes) }} +{{ tpl .Values.extraVolumes . | indent 8 }} + {{- else }} +{{ toYaml .Values.extraVolumes | indent 8 }} + {{- end }} + {{- end }} + {{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} + enableServiceLinks: {{ .Values.enableServiceLinks }} + {{- if .Values.hostAliases }} + hostAliases: {{ toYaml .Values.hostAliases | nindent 8 }} + {{- end }} + {{- if or (.Values.extraInitContainers) (.Values.sysctlInitContainer.enabled) (.Values.keystore) }} + initContainers: + {{- if .Values.sysctlInitContainer.enabled }} + - name: configure-sysctl + securityContext: + runAsUser: 0 + privileged: true + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + command: ["sysctl", "-w", "vm.max_map_count={{ .Values.sysctlVmMaxMapCount}}"] + resources: +{{ toYaml .Values.initResources | indent 10 }} + {{- end }} +{{ if .Values.keystore }} + - name: keystore + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + command: + - bash + - -c + - | + set -euo pipefail + + elasticsearch-keystore create + + for i in /tmp/keystoreSecrets/*/*; do + key=$(basename $i) + echo "Adding file $i to keystore key $key" + elasticsearch-keystore add-file "$key" "$i" + done + + # Add the bootstrap password since otherwise the Elasticsearch entrypoint tries to do this on startup + if [ ! -z ${ELASTIC_PASSWORD+x} ]; then + echo 'Adding env $ELASTIC_PASSWORD to keystore as key bootstrap.password' + echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x bootstrap.password + fi + + cp -a /usr/share/elasticsearch/config/elasticsearch.keystore /tmp/keystore/ + env: {{ toYaml .Values.extraEnvs | nindent 10 }} + envFrom: {{ toYaml .Values.envFrom | nindent 10 }} + resources: {{ toYaml .Values.initResources | nindent 10 }} + volumeMounts: + - name: keystore + mountPath: /tmp/keystore + {{- range .Values.keystore }} + - name: keystore-{{ .secretName }} + mountPath: /tmp/keystoreSecrets/{{ .secretName }} + {{- end }} +{{ end }} + {{- if .Values.extraInitContainers }} + # Currently some extra blocks accept strings + # to continue with backwards compatibility this is being kept + # whilst also allowing for yaml to be specified too. + {{- if eq "string" (printf "%T" .Values.extraInitContainers) }} +{{ tpl .Values.extraInitContainers . | indent 6 }} + {{- else }} +{{ toYaml .Values.extraInitContainers | indent 6 }} + {{- end }} + {{- end }} + {{- end }} + containers: + - name: "{{ template "elasticsearch.name" . }}" + securityContext: +{{ toYaml .Values.securityContext | indent 10 }} + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + readinessProbe: + exec: + command: + - bash + - -c + - | + set -e + # If the node is starting up wait for the cluster to be ready (request params: "{{ .Values.clusterHealthCheckParams }}" ) + # Once it has started only check that the node itself is responding + START_FILE=/tmp/.es_start_file + + # Disable nss cache to avoid filling dentry cache when calling curl + # This is required with Elasticsearch Docker using nss < 3.52 + export NSS_SDB_USE_CACHE=no + + http () { + local path="${1}" + local args="${2}" + set -- -XGET -s + + if [ "$args" != "" ]; then + set -- "$@" $args + fi + + if [ -n "${ELASTIC_PASSWORD}" ]; then + set -- "$@" -u "elastic:${ELASTIC_PASSWORD}" + fi + + curl --output /dev/null -k "$@" "{{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}${path}" + } + + if [ -f "${START_FILE}" ]; then + echo 'Elasticsearch is already running, lets check the node is healthy' + HTTP_CODE=$(http "/" "-w %{http_code}") + RC=$? + if [[ ${RC} -ne 0 ]]; then + echo "curl --output /dev/null -k -XGET -s -w '%{http_code}' \${BASIC_AUTH} {{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}/ failed with RC ${RC}" + exit ${RC} + fi + # ready if HTTP code 200, 503 is tolerable if ES version is 6.x + if [[ ${HTTP_CODE} == "200" ]]; then + exit 0 + elif [[ ${HTTP_CODE} == "503" && "{{ include "elasticsearch.esMajorVersion" . }}" == "6" ]]; then + exit 0 + else + echo "curl --output /dev/null -k -XGET -s -w '%{http_code}' \${BASIC_AUTH} {{ .Values.protocol }}://127.0.0.1:{{ .Values.httpPort }}/ failed with HTTP code ${HTTP_CODE}" + exit 1 + fi + + else + echo 'Waiting for elasticsearch cluster to become ready (request params: "{{ .Values.clusterHealthCheckParams }}" )' + if http "/_cluster/health?{{ .Values.clusterHealthCheckParams }}" "--fail" ; then + touch ${START_FILE} + exit 0 + else + echo 'Cluster is not yet ready (request params: "{{ .Values.clusterHealthCheckParams }}" )' + exit 1 + fi + fi +{{ toYaml .Values.readinessProbe | indent 10 }} + ports: + - name: http + containerPort: {{ .Values.httpPort }} + - name: transport + containerPort: {{ .Values.transportPort }} + resources: +{{ toYaml .Values.resources | indent 10 }} + env: + - name: node.name + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if eq .Values.roles.master "true" }} + {{- if ge (int (include "elasticsearch.esMajorVersion" .)) 7 }} + - name: cluster.initial_master_nodes + value: "{{ template "elasticsearch.endpoints" . }}" + {{- else }} + - name: discovery.zen.minimum_master_nodes + value: "{{ .Values.minimumMasterNodes }}" + {{- end }} + {{- end }} + {{- if lt (int (include "elasticsearch.esMajorVersion" .)) 7 }} + - name: discovery.zen.ping.unicast.hosts + value: "{{ template "elasticsearch.masterService" . }}-headless" + {{- else }} + - name: discovery.seed_hosts + value: "{{ template "elasticsearch.masterService" . }}-headless" + {{- end }} + - name: cluster.name + value: "{{ .Values.clusterName }}" + - name: network.host + value: "{{ .Values.networkHost }}" + - name: cluster.deprecation_indexing.enabled + value: "{{ .Values.clusterDeprecationIndexing }}" + {{- if .Values.esJavaOpts }} + - name: ES_JAVA_OPTS + value: "{{ .Values.esJavaOpts }}" + {{- end }} + {{- range $role, $enabled := .Values.roles }} + - name: node.{{ $role }} + value: "{{ $enabled }}" + {{- end }} +{{- if .Values.extraEnvs }} +{{ toYaml .Values.extraEnvs | indent 10 }} +{{- end }} +{{- if .Values.envFrom }} + envFrom: +{{ toYaml .Values.envFrom | indent 10 }} +{{- end }} + volumeMounts: + {{- if .Values.persistence.enabled }} + - name: "{{ template "elasticsearch.uname" . }}" + mountPath: /usr/share/elasticsearch/data + {{- end }} +{{ if .Values.keystore }} + - name: keystore + mountPath: /usr/share/elasticsearch/config/elasticsearch.keystore + subPath: elasticsearch.keystore +{{ end }} + {{- range .Values.secretMounts }} + - name: {{ .name }} + mountPath: {{ .path }} + {{- if .subPath }} + subPath: {{ .subPath }} + {{- end }} + {{- end }} + {{- range $path, $config := .Values.esConfig }} + - name: esconfig + mountPath: /usr/share/elasticsearch/config/{{ $path }} + subPath: {{ $path }} + {{- end -}} + {{- if .Values.extraVolumeMounts }} + # Currently some extra blocks accept strings + # to continue with backwards compatibility this is being kept + # whilst also allowing for yaml to be specified too. + {{- if eq "string" (printf "%T" .Values.extraVolumeMounts) }} +{{ tpl .Values.extraVolumeMounts . | indent 10 }} + {{- else }} +{{ toYaml .Values.extraVolumeMounts | indent 10 }} + {{- end }} + {{- end }} +{{- if .Values.lifecycle }} + lifecycle: +{{ toYaml .Values.lifecycle | indent 10 }} +{{- end }} + {{- if .Values.extraContainers }} + # Currently some extra blocks accept strings + # to continue with backwards compatibility this is being kept + # whilst also allowing for yaml to be specified too. + {{- if eq "string" (printf "%T" .Values.extraContainers) }} +{{ tpl .Values.extraContainers . | indent 6 }} + {{- else }} +{{ toYaml .Values.extraContainers | indent 6 }} + {{- end }} + {{- end }} diff --git a/dependency_charts/elasticsearch/templates/test/test-elasticsearch-health.yaml b/dependency_charts/elasticsearch/templates/test/test-elasticsearch-health.yaml new file mode 100644 index 0000000..704cd3d --- /dev/null +++ b/dependency_charts/elasticsearch/templates/test/test-elasticsearch-health.yaml @@ -0,0 +1,36 @@ +--- +{{- if .Values.tests.enabled -}} +apiVersion: v1 +kind: Pod +metadata: +{{- if .Values.healthNameOverride }} + name: {{ .Values.healthNameOverride | quote }} +{{- else }} + name: "{{ .Release.Name }}-{{ randAlpha 5 | lower }}-test" +{{- end }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": hook-succeeded +spec: + securityContext: +{{ toYaml .Values.podSecurityContext | indent 4 }} + containers: +{{- if .Values.healthNameOverride }} + - name: {{ .Values.healthNameOverride | quote }} +{{- else }} + - name: "{{ .Release.Name }}-{{ randAlpha 5 | lower }}-test" +{{- end }} + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + command: + - "sh" + - "-c" + - | + #!/usr/bin/env bash -e + curl -XGET --fail '{{ template "elasticsearch.uname" . }}:{{ .Values.httpPort }}/_cluster/health?{{ .Values.clusterHealthCheckParams }}' + {{- if .Values.imagePullSecrets }} + imagePullSecrets: +{{ toYaml .Values.imagePullSecrets | indent 4 }} + {{- end }} + restartPolicy: Never +{{- end -}} diff --git a/dependency_charts/elasticsearch/values.yaml b/dependency_charts/elasticsearch/values.yaml new file mode 100644 index 0000000..a20b987 --- /dev/null +++ b/dependency_charts/elasticsearch/values.yaml @@ -0,0 +1,348 @@ +--- +clusterName: "elasticsearch" +nodeGroup: "master" + +# The service that non master groups will try to connect to when joining the cluster +# This should be set to clusterName + "-" + nodeGroup for your master group +masterService: "" + +# Elasticsearch roles that will be applied to this nodeGroup +# These will be set as environment variables. E.g. node.master=true +roles: + master: "true" + ingest: "true" + data: "true" + remote_cluster_client: "true" + ml: "true" + +replicas: 3 +minimumMasterNodes: 2 + +esMajorVersion: "" + +clusterDeprecationIndexing: "false" + +# Allows you to add any config files in /usr/share/elasticsearch/config/ +# such as elasticsearch.yml and log4j2.properties +esConfig: {} +# elasticsearch.yml: | +# key: +# nestedkey: value +# log4j2.properties: | +# key = value + +# Extra environment variables to append to this nodeGroup +# This will be appended to the current 'env:' key. You can use any of the kubernetes env +# syntax here +extraEnvs: [] +# - name: MY_ENVIRONMENT_VAR +# value: the_value_goes_here + +# Allows you to load environment variables from kubernetes secret or config map +envFrom: [] +# - secretRef: +# name: env-secret +# - configMapRef: +# name: config-map + +# A list of secrets and their paths to mount inside the pod +# This is useful for mounting certificates for security and for mounting +# the X-Pack license +secretMounts: [] +# - name: elastic-certificates +# secretName: elastic-certificates +# path: /usr/share/elasticsearch/config/certs +# defaultMode: 0755 + +hostAliases: [] +#- ip: "127.0.0.1" +# hostnames: +# - "foo.local" +# - "bar.local" + +image: "docker.elastic.co/elasticsearch/elasticsearch" +imageTag: "7.16.2" +imagePullPolicy: "IfNotPresent" + +podAnnotations: + {} + # iam.amazonaws.com/role: es-cluster + +# additionals labels +labels: {} + +esJavaOpts: "" # example: "-Xmx1g -Xms1g" + +resources: + requests: + cpu: "1000m" + memory: "2Gi" + limits: + cpu: "1000m" + memory: "2Gi" + +initResources: + {} + # limits: + # cpu: "25m" + # # memory: "128Mi" + # requests: + # cpu: "25m" + # memory: "128Mi" + +networkHost: "0.0.0.0" + +volumeClaimTemplate: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 30Gi + +rbac: + create: false + serviceAccountAnnotations: {} + serviceAccountName: "" + automountToken: true + +podSecurityPolicy: + create: false + name: "" + spec: + privileged: true + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - secret + - configMap + - persistentVolumeClaim + - emptyDir + +persistence: + enabled: true + labels: + # Add default labels for the volumeClaimTemplate of the StatefulSet + enabled: false + annotations: {} + +extraVolumes: + [] + # - name: extras + # emptyDir: {} + +extraVolumeMounts: + [] + # - name: extras + # mountPath: /usr/share/extras + # readOnly: true + +extraContainers: + [] + # - name: do-something + # image: busybox + # command: ['do', 'something'] + +extraInitContainers: + [] + # - name: do-something + # image: busybox + # command: ['do', 'something'] + +# This is the PriorityClass settings as defined in +# https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass +priorityClassName: "" + +# By default this will make sure two pods don't end up on the same node +# Changing this to a region would allow you to spread pods across regions +antiAffinityTopologyKey: "kubernetes.io/hostname" + +# Hard means that by default pods will only be scheduled if there are enough nodes for them +# and that they will never end up on the same node. Setting this to soft will do this "best effort" +antiAffinity: "hard" + +# This is the node affinity settings as defined in +# https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity-beta-feature +nodeAffinity: {} + +# The default is to deploy all pods serially. By setting this to parallel all pods are started at +# the same time when bootstrapping the cluster +podManagementPolicy: "Parallel" + +# The environment variables injected by service links are not used, but can lead to slow Elasticsearch boot times when +# there are many services in the current namespace. +# If you experience slow pod startups you probably want to set this to `false`. +enableServiceLinks: true + +protocol: http +httpPort: 9200 +transportPort: 9300 + +service: + enabled: true + labels: {} + labelsHeadless: {} + type: ClusterIP + nodePort: "" + annotations: {} + httpPortName: http + transportPortName: transport + loadBalancerIP: "" + loadBalancerSourceRanges: [] + externalTrafficPolicy: "" + +updateStrategy: RollingUpdate + +# This is the max unavailable setting for the pod disruption budget +# The default value of 1 will make sure that kubernetes won't allow more than 1 +# of your pods to be unavailable during maintenance +maxUnavailable: 1 + +podSecurityContext: + fsGroup: 1000 + runAsUser: 1000 + +securityContext: + capabilities: + drop: + - ALL + # readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +# How long to wait for elasticsearch to stop gracefully +terminationGracePeriod: 120 + +sysctlVmMaxMapCount: 262144 + +readinessProbe: + failureThreshold: 3 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 3 + timeoutSeconds: 5 + +# https://www.elastic.co/guide/en/elasticsearch/reference/7.16/cluster-health.html#request-params wait_for_status +clusterHealthCheckParams: "wait_for_status=green&timeout=1s" + +## Use an alternate scheduler. +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" + +imagePullSecrets: [] +nodeSelector: {} +tolerations: [] + +# Enabling this will publicly expose your Elasticsearch instance. +# Only enable this if you have security enabled on your cluster +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + className: "nginx" + pathtype: ImplementationSpecific + hosts: + - host: chart-example.local + paths: + - path: / + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +nameOverride: "" +fullnameOverride: "" +healthNameOverride: "" + +lifecycle: + {} + # preStop: + # exec: + # command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"] + # postStart: + # exec: + # command: + # - bash + # - -c + # - | + # #!/bin/bash + # # Add a template to adjust number of shards/replicas + # TEMPLATE_NAME=my_template + # INDEX_PATTERN="logstash-*" + # SHARD_COUNT=8 + # REPLICA_COUNT=1 + # ES_URL=http://localhost:9200 + # while [[ "$(curl -s -o /dev/null -w '%{http_code}\n' $ES_URL)" != "200" ]]; do sleep 1; done + # curl -XPUT "$ES_URL/_template/$TEMPLATE_NAME" -H 'Content-Type: application/json' -d'{"index_patterns":['\""$INDEX_PATTERN"\"'],"settings":{"number_of_shards":'$SHARD_COUNT',"number_of_replicas":'$REPLICA_COUNT'}}' + +sysctlInitContainer: + enabled: true + +keystore: [] + +networkPolicy: + ## Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. + ## In order for a Pod to access Elasticsearch, it needs to have the following label: + ## {{ template "uname" . }}-client: "true" + ## Example for default configuration to access HTTP port: + ## elasticsearch-master-http-client: "true" + ## Example for default configuration to access transport port: + ## elasticsearch-master-transport-client: "true" + + http: + enabled: false + ## if explicitNamespacesSelector is not set or set to {}, only client Pods being in the networkPolicy's namespace + ## and matching all criteria can reach the DB. + ## But sometimes, we want the Pods to be accessible to clients from other namespaces, in this case, we can use this + ## parameter to select these namespaces + ## + # explicitNamespacesSelector: + # # Accept from namespaces with all those different rules (only from whitelisted Pods) + # matchLabels: + # role: frontend + # matchExpressions: + # - {key: role, operator: In, values: [frontend]} + ## Additional NetworkPolicy Ingress "from" rules to set. Note that all rules are OR-ed. + ## + # additionalRules: + # - podSelector: + # matchLabels: + # role: frontend + # - podSelector: + # matchExpressions: + # - key: role + # operator: In + # values: + # - frontend + + transport: + ## Note that all Elasticsearch Pods can talk to themselves using transport port even if enabled. + enabled: false + # explicitNamespacesSelector: + # matchLabels: + # role: frontend + # matchExpressions: + # - {key: role, operator: In, values: [frontend]} + # additionalRules: + # - podSelector: + # matchLabels: + # role: frontend + # - podSelector: + # matchExpressions: + # - key: role + # operator: In + # values: + # - frontend + +tests: + enabled: true + +# Deprecated +# please use the above podSecurityContext.fsGroup instead +fsGroup: "" diff --git a/dependency_charts/mongodb/.helmignore b/dependency_charts/mongodb/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/dependency_charts/mongodb/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/dependency_charts/mongodb/Chart.lock b/dependency_charts/mongodb/Chart.lock new file mode 100644 index 0000000..cdf7baa --- /dev/null +++ b/dependency_charts/mongodb/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.1.3 +digest: sha256:3422f8c1139c2f0f0d816e7ee7280705d2010e83c3e2367b28dcb4f7dd911135 +generated: "2020-12-22T11:27:07.137984504Z" diff --git a/dependency_charts/mongodb/Chart.yaml b/dependency_charts/mongodb/Chart.yaml new file mode 100644 index 0000000..17dd255 --- /dev/null +++ b/dependency_charts/mongodb/Chart.yaml @@ -0,0 +1,29 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 4.4.3 +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + tags: + - bitnami-common + version: 1.x.x +description: NoSQL document-oriented database that stores JSON-like documents with + dynamic schemas, simplifying the integration of data in content-driven applications. +home: https://github.com/bitnami/charts/tree/master/bitnami/mongodb +icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png +keywords: +- mongodb +- database +- nosql +- cluster +- replicaset +- replication +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: mongodb +sources: +- https://github.com/bitnami/bitnami-docker-mongodb +- https://mongodb.org +version: 10.3.4 diff --git a/dependency_charts/mongodb/README.md b/dependency_charts/mongodb/README.md new file mode 100644 index 0000000..f6b2af3 --- /dev/null +++ b/dependency_charts/mongodb/README.md @@ -0,0 +1,663 @@ +# MongoDB + +[MongoDB](https://www.mongodb.com/) is a cross-platform document-oriented database. Classified as a NoSQL database, MongoDB eschews the traditional table-based relational database structure in favor of JSON-like documents with dynamic schemas, making the integration of data in certain types of applications easier and faster. + +## TL;DR + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/mongodb +``` + +## Introduction + +This chart bootstraps a [MongoDB](https://github.com/bitnami/bitnami-docker-mongodb) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This chart has been tested to work with NGINX Ingress, cert-manager, fluentd and Prometheus on top of the [BKPR](https://kubeprod.io/). + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.0-beta3+ +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +$ helm install my-release bitnami/mongodb +``` + +The command deploys MongoDB on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Architecture + +This charts allows you install MongoDB using two different architecture setups: "standalone" or "replicaset". You can use the `architecture` parameter to choose the one to use: + +```console +architecture="standalone" +architecture="replicaset" +``` + +The standalone architecture installs a deployment (or statefulset) with one MongoDB server (it cannot be scaled): + +``` + ┌────────────────┐ + │ MongoDB │ + | svc │ + └───────┬────────┘ + │ + ▼ + ┌──────────┐ + │ MongoDB │ + │ Server │ + │ Pod │ + └──────────┘ +``` + +The chart supports the replicaset architecture with and without a [MongoDB Arbiter](https://docs.mongodb.com/manual/core/replica-set-arbiter/): + +- When the MongoDB Arbiter is enabled, the chart installs two statefulsets: A statefulset with N MongoDB servers (organised with one primary and N-1 secondary nodes), and a statefulset with one MongoDB arbiter node (it cannot be scaled). + + ``` + ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌─────────────┐ + │ MongoDB 0 │ │ MongoDB 1 │ │ MongoDB N │ │ Arbiter │ + | external svc │ | external svc │ | external svc │ | svc │ + └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ └──────┬──────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ + │ MongoDB 0 │ │ MongoDB 1 │ │ MongoDB N │ │ MongoDB │ + │ Server │ │ Server │ .... │ Server │ │ Arbiter │ + │ Pod │ │ Pod │ │ Pod │ │ Pod │ + └───────────┘ └───────────┘ └───────────┘ └───────────┘ + primary secondary secondary + ``` + + The PSA model is useful when the third Availability Zone cannot hold a full MongoDB instance. The MongoDB Arbiter as decision maker is lightweight and can run alongside other workloads. + + _Note:_ An update takes your MongoDB replicaset offline if the Arbiter is enabled and the number of MongoDB replicas is two. Helm applies updates to the statefulsets for the MongoDB instance _and_ the Arbiter at the same time so you loose two out of three quorum votes. + +- Without the Arbiter, the chart deploys a single statefulset with N MongoDB servers (organised with one primary and N-1 secondary nodes) + + ``` + ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ + │ MongoDB 0 │ │ MongoDB 1 │ │ MongoDB N │ + | external svc │ | external svc │ | external svc │ + └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ + │ │ │ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────┐ + │ MongoDB 0 │ │ MongoDB 1 │ │ MongoDB N │ + │ Server │ │ Server │ .... │ Server │ + │ Pod │ │ Pod │ │ Pod │ + └───────────┘ └───────────┘ └───────────┘ + primary secondary secondary + ``` + +There are no services load balancing requests between MongoDB nodes, instead each node has an associated service to access them individually. + +> Note: although the 1st replica is initially assigned the "primary" role, any of the "secondary" nodes can become the "primary" if it is down, or during upgrades. Do not make any assumption about what replica has the "primary" role, instead configure your Mongo client with the list of MongoDB hostnames so it can dynamically choose the node to send requests. + +## Parameters + +The following tables lists the configurable parameters of the MongoDB chart and their default values per section/component: + +### Global parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `global.imageRegistry` | Global Docker image registry | `nil` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| `global.namespaceOverride` | Global string to override the release namespace | `nil` | + +### Common parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `nameOverride` | String to partially override mongodb.fullname | `nil` | +| `fullnameOverride` | String to fully override mongodb.fullname | `nil` | +| `clusterDomain` | Default Kubernetes cluster domain | `cluster.local` | +| `schedulerName` | Name of the scheduler (other than default) to dispatch pods | `nil` | +| `image.registry` | MongoDB image registry | `docker.io` | +| `image.repository` | MongoDB image name | `bitnami/mongodb` | +| `image.tag` | MongoDB image tag | `{TAG_NAME}` | +| `image.pullPolicy` | MongoDB image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `image.debug` | Set to true if you would like to see extra information on logs | `false` | + +### MongoDB parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `architecture` | MongoDB architecture (`standalone` or `replicaset`) | `standalone` | +| `useStatefulSet` | Set to true to use a StatefulSet instead of a Deployment (only when `architecture=standalone`) | `false` | +| `auth.enabled` | Enable authentication | `true` | +| `auth.rootPassword` | MongoDB admin password | _random 10 character long alphanumeric string_ | +| `auth.username` | MongoDB custom user (mandatory if `auth.database` is set) | `nil` | +| `auth.password` | MongoDB custom user password | _random 10 character long alphanumeric string_ | +| `auth.database` | MongoDB custom database | `nil` | +| `auth.replicaSetKey` | Key used for authentication in the replicaset (only when `architecture=replicaset`) | _random 10 character long alphanumeric string_ | +| `auth.existingSecret` | Existing secret with MongoDB credentials (keys: `mongodb-password`, `mongodb-root-password`, ` mongodb-replica-set-key`) | `nil` | +| `replicaSetName` | Name of the replica set (only when `architecture=replicaset`) | `rs0` | +| `replicaSetHostnames` | Enable DNS hostnames in the replicaset config (only when `architecture=replicaset`) | `true` | +| `enableIPv6` | Switch to enable/disable IPv6 on MongoDB | `false` | +| `directoryPerDB` | Switch to enable/disable DirectoryPerDB on MongoDB | `false` | +| `systemLogVerbosity` | MongoDB system log verbosity level | `0` | +| `disableSystemLog` | Switch to enable/disable MongoDB system log | `false` | +| `configuration` | MongoDB configuration file to be used | `{}` | +| `existingConfigmap` | Name of existing ConfigMap with MongoDB configuration | `nil` | +| `initdbScripts` | Dictionary of initdb scripts | `nil` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts | `nil` | +| `command` | Override default container command (useful when using custom images) | `nil` | +| `args` | Override default container args (useful when using custom images) | `nil` | +| `extraFlags` | MongoDB additional command line flags | `[]` | +| `extraEnvVars` | Extra environment variables to add to MongoDB pods | `[]` | +| `extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars | `nil` | +| `extraEnvVarsSecret` | Name of existing Secret containing extra env vars (in case of sensitive data) | `nil` | +| `tls.enabled` | Enable MongoDB TLS support between nodes in the cluster as well as between mongo clients and nodes | `false` | +| `tls.existingSecret` | Existing secret with TLS certificates (keys: `mongodb-ca-cert`, `mongodb-ca-key`, `client-pem`) | `nil` | +| `tls.caCert` | Custom CA certificated (base64 encoded) | `nil` | +| `tls.caKey` | CA certificate private key (base64 encoded) | `nil` | +| `tls.image.registry` | Init container TLS certs setup image registry (nginx) | `docker.io` | +| `tls.image.repository` | Init container TLS certs setup image name (nginx) | `bitnami/nginx` | +| `tls.image.tag` | Init container TLS certs setup image tag (nginx) | `{TAG_NAME}` | +| `tls.image.pullPolicy` | Init container TLS certs setup image pull policy (nginx) | `Always` | + + +### MongoDB statefulset parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `replicaCount` | Number of MongoDB nodes (only when `architecture=replicaset`) | `2` | +| `labels` | Annotations to be added to the MongoDB statefulset | `{}` (evaluated as a template) | +| `annotations` | Additional labels to be added to the MongoDB statefulset | `{}` (evaluated as a template) | +| `podManagementPolicy` | Pod management policy for MongoDB | `OrderedReady` | +| `strategyType` | StrategyType for MongoDB statefulset | `RollingUpdate` | +| `podLabels` | MongoDB pod labels | `{}` (evaluated as a template) | +| `podAnnotations` | MongoDB Pod annotations | `{}` (evaluated as a template) | +| `priorityClassName` | Name of the existing priority class to be used by MongoDB pod(s) | `""` | +| `podAffinityPreset` | MongoDB Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `podAntiAffinityPreset` | MongoDB Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `nodeAffinityPreset.type` | MongoDB Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `nodeAffinityPreset.key` | MongoDB Node label key to match Ignored if `affinity` is set. | `""` | +| `nodeAffinityPreset.values` | MongoDB Node label values to match. Ignored if `affinity` is set. | `[]` | +| `affinity` | MongoDB Affinity for pod assignment | `{}` (evaluated as a template) | +| `nodeSelector` | MongoDB Node labels for pod assignment | `{}` (evaluated as a template) | +| `tolerations` | MongoDB Tolerations for pod assignment | `[]` (evaluated as a template) | +| `podSecurityContext` | MongoDB pod(s)' Security Context | Check `values.yaml` file | +| `containerSecurityContext` | MongoDB containers' Security Context | Check `values.yaml` file | +| `resources.limits` | The resources limits for MongoDB containers | `{}` | +| `resources.requests` | The requested resources for MongoDB containers | `{}` | +| `livenessProbe` | Liveness probe configuration for MongoDB | Check `values.yaml` file | +| `readinessProbe` | Readiness probe configuration for MongoDB | Check `values.yaml` file | +| `customLivenessProbe` | Override default liveness probe for MongoDB containers | `nil` | +| `customReadinessProbe` | Override default readiness probe for MongoDB containers | `nil` | +| `pdb.create` | Enable/disable a Pod Disruption Budget creation for MongoDB pod(s) | `false` | +| `pdb.minAvailable` | Minimum number/percentage of MongoDB pods that should remain scheduled | `1` | +| `pdb.maxUnavailable` | Maximum number/percentage of MongoDB pods that may be made unavailable | `nil` | +| `initContainers` | Add additional init containers for the MongoDB pod(s) | `{}` (evaluated as a template) | +| `sidecars` | Add additional sidecar containers for the MongoDB pod(s) | `{}` (evaluated as a template) | +| `extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the MongoDB container(s) | `{}` | +| `extraVolumes` | Optionally specify extra list of additional volumes to the MongoDB statefulset | `{}` | + +### Exposure parameters + +| Parameter | Description | Default | +|---------------------------------------------------|----------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | MongoDB service port | `27017` | +| `service.portName` | MongoDB service port name | `mongodb` | +| `service.nodePort` | Port to bind to for NodePort and LoadBalancer service types | `""` | +| `service.clusterIP` | MongoDB service cluster IP | `nil` | +| `service.loadBalancerIP` | loadBalancerIP for MongoDB Service | `nil` | +| `service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | `[]` | +| `service.annotations` | Service annotations | `{}` (evaluated as a template) | +| `externalAccess.enabled` | Enable Kubernetes external cluster access to MongoDB nodes | `false` | +| `externalAccess.autoDiscovery.enabled` | Enable using an init container to auto-detect external IPs by querying the K8s API | `false` | +| `externalAccess.autoDiscovery.image.registry` | Init container auto-discovery image registry (kubectl) | `docker.io` | +| `externalAccess.autoDiscovery.image.repository` | Init container auto-discovery image name (kubectl) | `bitnami/kubectl` | +| `externalAccess.autoDiscovery.image.tag` | Init container auto-discovery image tag (kubectl) | `{TAG_NAME}` | +| `externalAccess.autoDiscovery.image.pullPolicy` | Init container auto-discovery image pull policy (kubectl) | `Always` | +| `externalAccess.autoDiscovery.resources.limits` | Init container auto-discovery resource limits | `{}` | +| `externalAccess.autoDiscovery.resources.requests` | Init container auto-discovery resource requests | `{}` | +| `externalAccess.service.type` | Kubernetes Service type for external access. It can be NodePort or LoadBalancer | `LoadBalancer` | +| `externalAccess.service.port` | MongoDB port used for external access when service type is LoadBalancer | `27017` | +| `externalAccess.service.loadBalancerIPs` | Array of load balancer IPs for MongoDB nodes | `[]` | +| `externalAccess.service.loadBalancerSourceRanges` | Address(es) that are allowed when service is LoadBalancer | `[]` | +| `externalAccess.service.domain` | Domain or external IP used to configure MongoDB advertised hostname when service type is NodePort | `nil` | +| `externalAccess.service.nodePorts` | Array of node ports used to configure MongoDB advertised hostname when service type is NodePort | `[]` | +| `externalAccess.service.annotations` | Service annotations for external access | `{}`(evaluated as a template) | + +### Persistence parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `persistence.enabled` | Enable MongoDB data persistence using PVC | `true` | +| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim` (only when `architecture=standalone`) | `nil` (evaluated as a template) | +| `persistence.storageClass` | PVC Storage Class for MongoDB data volume | `nil` | +| `persistence.accessMode` | PVC Access Mode for MongoDB data volume | `ReadWriteOnce` | +| `persistence.size` | PVC Storage Request for MongoDB data volume | `8Gi` | +| `persistence.mountPath` | Path to mount the volume at | `/bitnami/mongodb` | +| `persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `persistence.volumeClaimTemplates.selector` | A label query over volumes to consider for binding (e.g. when using local volumes) | `` | + +### RBAC parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `serviceAccount.create` | Enable creation of ServiceAccount for MongoDB pods | `true` | +| `serviceAccount.name` | Name of the created serviceAccount | Generated using the `mongodb.fullname` template | +| `rbac.create` | Weather to create & use RBAC resources or not | `false` | + +### Volume Permissions parameters + +| Parameter | Description | Default | +|-------------------------------------------|----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `volumePermissions.enabled` | Enable init container that changes the owner and group of the persistent volume(s) mountpoint to `runAsUser:fsGroup` | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `buster` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | +| `volumePermissions.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `volumePermissions.resources.limits` | Init container volume-permissions resource limits | `{}` | +| `volumePermissions.resources.requests` | Init container volume-permissions resource requests | `{}` | +| `volumePermissions.securityContext` | Security context of the init container | Check `values.yaml` file | + +### Arbiter parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `arbiter.enabled` | Enable deploying the arbiter | `true` | +| `arbiter.configuration` | Arbiter configuration file to be used | `{}` | +| `arbiter.existingConfigmap` | Name of existing ConfigMap with Arbiter configuration | `nil` | +| `arbiter.command` | Override default container command (useful when using custom images) | `nil` | +| `arbiter.args` | Override default container args (useful when using custom images) | `nil` | +| `arbiter.extraFlags` | Arbiter additional command line flags | `[]` | +| `arbiter.extraEnvVars` | Extra environment variables to add to Arbiter pods | `[]` | +| `arbiter.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars | `nil` | +| `arbiter.extraEnvVarsSecret` | Name of existing Secret containing extra env vars (in case of sensitive data) | `nil` | +| `arbiter.labels` | Annotations to be added to the Arbiter statefulset | `{}` (evaluated as a template) | +| `arbiter.annotations` | Additional labels to be added to the Arbiter statefulset | `{}` (evaluated as a template) | +| `arbiter.podLabels` | Arbiter pod labels | `{}` (evaluated as a template) | +| `arbiter.podAnnotations` | Arbiter Pod annotations | `{}` (evaluated as a template) | +| `arbiter.priorityClassName` | Name of the existing priority class to be used by Arbiter pod(s) | `""` | +| `arbiter.podAffinityPreset` | Arbiter Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `arbiter.podAntiAffinityPreset` | Arbiter Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `arbiter.nodeAffinityPreset.type` | Arbiter Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `arbiter.nodeAffinityPreset.key` | Arbiter Node label key to match Ignored if `affinity` is set. | `""` | +| `arbiter.nodeAffinityPreset.values` | Arbiter Node label values to match. Ignored if `affinity` is set. | `[]` | +| `arbiter.affinity` | Arbiter Affinity for pod assignment | `{}` (evaluated as a template) | +| `arbiter.nodeSelector` | Arbiter Node labels for pod assignment | `{}` (evaluated as a template) | +| `arbiter.tolerations` | Arbiter Tolerations for pod assignment | `[]` (evaluated as a template) | +| `arbiter.podSecurityContext` | Arbiter pod(s)' Security Context | Check `values.yaml` file | +| `arbiter.containerSecurityContext` | Arbiter containers' Security Context | Check `values.yaml` file | +| `arbiter.resources.limits` | The resources limits for Arbiter containers | `{}` | +| `arbiter.resources.requests` | The requested resources for Arbiter containers | `{}` | +| `arbiter.livenessProbe` | Liveness probe configuration for Arbiter | Check `values.yaml` file | +| `arbiter.readinessProbe` | Readiness probe configuration for Arbiter | Check `values.yaml` file | +| `arbiter.customLivenessProbe` | Override default liveness probe for Arbiter containers | `nil` | +| `arbiter.customReadinessProbe` | Override default readiness probe for Arbiter containers | `nil` | +| `arbiter.pdb.create` | Enable/disable a Pod Disruption Budget creation for Arbiter pod(s) | `false` | +| `arbiter.pdb.minAvailable` | Minimum number/percentage of Arbiter pods that should remain scheduled | `1` | +| `arbiter.pdb.maxUnavailable` | Maximum number/percentage of Arbiter pods that may be made unavailable | `nil` | +| `arbiter.initContainers` | Add additional init containers for the Arbiter pod(s) | `{}` (evaluated as a template) | +| `arbiter.sidecars` | Add additional sidecar containers for the Arbiter pod(s) | `{}` (evaluated as a template) | +| `arbiter.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Arbiter container(s) | `{}` | +| `arbiter.extraVolumes` | Optionally specify extra list of additional volumes to the Arbiter statefulset | `{}` | + +### Metrics parameters + +| Parameter | Description | Default | +|-------------------------------------------|------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `metrics.enabled` | Enable using a sidecar Prometheus exporter | `false` | +| `metrics.image.registry` | MongoDB Prometheus exporter image registry | `docker.io` | +| `metrics.image.repository` | MongoDB Prometheus exporter image name | `bitnami/mongodb-exporter` | +| `metrics.image.tag` | MongoDB Prometheus exporter image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | MongoDB Prometheus exporter image pull policy | `Always` | +| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `metrics.extraFlags` | Additional command line flags | `""` | +| `metrics.extraUri` | Additional URI options of the metrics service | `""` | +| `metrics.service.type` | Type of the Prometheus metrics service | `ClusterIP file` | +| `metrics.service.port` | Port of the Prometheus metrics service | `9216` | +| `metrics.service.annotations` | Annotations for Prometheus metrics service | Check `values.yaml` file | +| `metrics.resources.limits` | The resources limits for Prometheus exporter containers | `{}` | +| `metrics.resources.requests` | The requested resources for Prometheus exporter containers | `{}` | +| `metrics.livenessProbe` | Liveness probe configuration for Prometheus exporter | Check `values.yaml` file | +| `metrics.readinessProbe` | Readiness probe configuration for Prometheus exporter | Check `values.yaml` file | +| `metrics.serviceMonitor.enabled` | Create ServiceMonitor Resource for scraping metrics using Prometheus Operator | `false` | +| `metrics.serviceMonitor.namespace` | Namespace which Prometheus is running in | `monitoring` | +| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped | `30s` | +| `metrics.serviceMonitor.scrapeTimeout` | Specify the timeout after which the scrape is ended | `nil` | +| `metrics.serviceMonitor.additionalLabels` | Used to pass Labels that are required by the Installed Prometheus Operator | `{}` | +| `metrics.prometheusRule.enabled` | Set this to true to create prometheusRules for Prometheus operator | `false` | +| `metrics.prometheusRule.namespace` | namespace where prometheusRules resource should be created | `monitoring` | +| `metrics.prometheusRule.rules` | Rules to be created, check values for an example. | `[]` | +| `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so prometheusRules will be discovered by Prometheus | `{}` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install my-release \ + --set auth.rootPassword=secretpassword,auth.username=my-user,auth.password=my-password,auth.database=my-database \ + bitnami/mongodb +``` + +The above command sets the MongoDB `root` account password to `secretpassword`. Additionally, it creates a standard database user named `my-user`, with the password `my-password`, who has access to a database named `my-database`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install my-release -f values.yaml bitnami/mongodb +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +## Configuration and installation details + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Production configuration and horizontal scaling + +This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. You can use this file instead of the default one. + +- Switch to enable/disable replica set configuration: + +```diff +- architecture: standalone ++ architecture: replicaset +``` + +- Increase the number of MongoDB nodes: + +```diff +- replicaCount: 2 ++ replicaCount: 4 +``` + +- Enable Pod Disruption Budget: + +```diff +- pdb.create: false ++ pdb.create: true +``` + +- Enable using a sidecar Prometheus exporter: + +```diff +- metrics.enabled: false ++ metrics.enabled: true +``` + +To horizontally scale this chart, you can use the `--replicaCount` flag to modify the number of secondary nodes in your MongoDB replica set. + +### Initialize a fresh instance + +The [Bitnami MongoDB](https://github.com/bitnami/bitnami-docker-mongodb) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, you can specify them using the `initdbScripts` parameter as dict. + +You can also set an external ConfigMap with all the initialization scripts. This is done by setting the `initdbScriptsConfigMap` parameter. Note that this will override the previous option. + +The allowed extensions are `.sh`, and `.js`. + +### Replicaset: Accessing MongoDB nodes from outside the cluster + +In order to access MongoDB nodes from outside the cluster when using a replicaset architecture, a specific service per MongoDB pod will be created. There are two ways of configuring external access: + +- Using LoadBalancer services +- Using NodePort services. + +#### Using LoadBalancer services + +You have two alternatives to use LoadBalancer services: + +- Option A) Use random load balancer IPs using an **initContainer** that waits for the IPs to be ready and discover them automatically. + +```console +architecture=replicaset +replicaCount=2 +externalAccess.enabled=true +externalAccess.service.type=LoadBalancer +externalAccess.service.port=27017 +externalAccess.autoDiscovery.enabled=true +serviceAccount.create=true +rbac.create=true +``` + +> Note: This option requires creating RBAC rules on clusters where RBAC policies are enabled. + +- Option B) Manually specify the load balancer IPs: + +```console +architecture=replicaset +replicaCount=2 +externalAccess.enabled=true +externalAccess.service.type=LoadBalancer +externalAccess.service.port=27017 +externalAccess.service.loadBalancerIPs[0]='external-ip-1' +externalAccess.service.loadBalancerIPs[1]='external-ip-2'} +``` + +> Note: You need to know in advance the load balancer IPs so each MongoDB node advertised hostname is configured with it. + +#### Using NodePort services + +Manually specify the node ports to use: + +```console +architecture=replicaset +replicaCount=2 +externalAccess.enabled=true +externalAccess.service.type=NodePort +externalAccess.service.nodePorts[0]='node-port-1' +externalAccess.service.nodePorts[1]='node-port-2' +``` + +> Note: You need to know in advance the node ports that will be exposed so each MongoDB node advertised hostname is configured with it. + +The pod will try to get the external ip of the node using `curl -s https://ipinfo.io/ip` unless `externalAccess.service.domain` is provided. + +### Adding extra environment variables + +In case you want to add extra environment variables (useful for advanced operations like custom init scripts), you can use the `extraEnvVars` property. + +```yaml +extraEnvVars: + - name: LOG_LEVEL + value: error +``` + +Alternatively, you can use a ConfigMap or a Secret with the environment variables. To do so, use the `extraEnvVarsCM` or the `extraEnvVarsSecret` properties. + +### Sidecars and Init Containers + +If you have a need for additional containers to run within the same pod as MongoDB (e.g. an additional metrics or logging exporter), you can do so via the `sidecars` config parameter. Simply define your container according to the Kubernetes container spec. + +```yaml +sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +Similarly, you can add extra init containers using the `initContainers` parameter. + +```yaml +initContainers: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +## Persistence + +The [Bitnami MongoDB](https://github.com/bitnami/bitnami-docker-mongodb) image stores the MongoDB data and configurations at the `/bitnami/mongodb` path of the container. + +The chart mounts a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) at this location. The volume is created using dynamic volume provisioning. + +### Adjust permissions of persistent volume mountpoint + +As the image run as non-root by default, it is necessary to adjust the ownership of the persistent volume so that the container can write data into it. By default, the chart is configured to use Kubernetes Security Context to automatically change the ownership of the volume. However, this feature does not work in all Kubernetes distributions. + +As an alternative, this chart supports using an initContainer to change the ownership of the volume before mounting it in the final destination. You can enable this initContainer by setting `volumePermissions.enabled` to `true`. + +## Using Prometheus rules + +You can use custom Prometheus rules for Prometheus operator by using the `prometheusRule` parameter, see below a basic configuration example: + +```yaml +metrics: + enabled: true + prometheusRule: + enabled: true + rules: + - name: rule1 + rules: + - alert: HighRequestLatency + expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 + for: 10m + labels: + severity: page + annotations: + summary: High request latency +``` + +## Enabling SSL/TLS + +This container supports enabling SSL/TLS between nodes in the cluster, as well as between mongo clients and nodes, by setting the MONGODB_EXTRA_FLAGS and MONGODB_CLIENT_EXTRA_FLAGS environment variables, together with the correct MONGODB_ADVERTISED_HOSTNAME. +To enable full TLS encryption set tls.enabled to true + +### Generating the self-signed cert via pre install helm hooks + +The secrets-ca.yaml utilizes the helm "pre-install" hook to ensure that the certs will only be generated on chart install. The genCA function will create a new self-signed x509 certificate authority, the genSignedCert function creates an object with a pair of items in it — the Cert and Key which we base64 encode and use in a yaml like object. With the genSignedCert function, we pass the CN, an empty IP list (the nil part), the validity and the CA created previously. +To hold the signed certificate created above, we can use a kubernetes Secret object and the initContainer sets up the rest. +Using Helm’s hook annotations ensures that the certs will only be generated on chart install. This will prevent overriding the certs anytime we upgrade the chart’s released instance. + +### Using your own CA + +To use your own CA set `tls.caCert` and `tls.caKey` with appropriate base64 encoded data. The secrets-ca.yaml will utilize this data to create secret. Please Note:- Currently only RSA private keys are supported. + +### Accessing the cluster + +To access the cluster you will need to enable the initContainer which generates the MongoDB server/client pem needed to access the cluster. Please ensure that you include the $my_hostname section with your actual hostname and alternative hostnames section should contain the hostnames you want to allow access to the MongoDB replicaset. Additionally, if the [external access](#replicaset-accessing-mongodb-nodes-from-outside-the-cluster) is enabled, the `loadBalancerIPs` are added to the alternative names list. +Note: You will be generating self signed certs for the MongoDB deployment. With the initContainer it will generate a new MongoDB private key which will be used to create your own Certificate Authority (CA),the public cert for the CA will be created, the Certificate Signing Request will be created as well and signed using the private key of the CA previously created. Finally the PEM bundle will be created using the private key and public certificate. The process will be repeated for each node in the cluster. + +### Starting the cluster + +After the certs have been generated and made available to the containers at the correct mount points, the mongod server will be started with TLS enabled. The options for the TLS mode will be (disabled|allowTLS|preferTLS|requireTLS). This value can be changed via the MONGODB_EXTRA_FLAGS field using the tlsMode. The client should now be able to connect to the TLS enabled cluster with the provided certs. + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` parameter(s). Find more information about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/master/bitnami/common#affinities) chart. To do so, set the `XXX.podAffinityPreset`, `XXX.podAntiAffinityPreset`, or `XXX.nodeAffinityPreset` parameters. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami’s Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +If authentication is enabled, it's necessary to set the `auth.rootPassword` (also `auth.replicaSetKey` when using a replicaset architecture) when upgrading for readiness/liveness probes to work properly. When you install this chart for the first time, some notes will be displayed providing the credentials you must use under the 'Credentials' section. Please note down the password, and run the command below to upgrade your chart: + +```bash +$ helm upgrade my-release bitnami/mongodb --set auth.rootPassword=[PASSWORD] (--set auth.replicaSetKey=[REPLICASETKEY]) +``` + +> Note: you need to substitute the placeholders [PASSWORD] and [REPLICASETKEY] with the values obtained in the installation notes. + +### To 10.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Move dependency information from the *requirements.yaml* to the *Chart.yaml* +- After running `helm dependency update`, a *Chart.lock* file is generated containing the same structure used in the previous *requirements.lock* +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ + +### To 9.0.0 + +MongoDB container images were updated to `4.4.x` and it can affect compatibility with older versions of MongoDB. Refer to the following guides to upgrade your applications: + +- [Standalone](https://docs.mongodb.com/manual/release-notes/4.4-upgrade-standalone/) +- [Replica Set](https://docs.mongodb.com/manual/release-notes/4.4-upgrade-replica-set/) + +### To 8.0.0 + +- Architecture used to configure MongoDB as a replicaset was completely refactored. Now, both primary and secondary nodes are part of the same statefulset. +- Chart labels were adapted to follow the Helm charts best practices. +- This version introduces `bitnami/common`, a [library chart](https://helm.sh/docs/topics/library_charts/#helm) as a dependency. More documentation about this new utility could be found [here](https://github.com/bitnami/charts/tree/master/bitnami/common#bitnami-common-library-chart). Please, make sure that you have updated the chart dependencies before executing any upgrade. +- Several parameters were renamed or disappeared in favor of new ones on this major version. These are the most important ones: + - `replicas` is renamed to `replicaCount`. + - Authentication parameters are reorganized under the `auth.*` parameter: + - `usePassword` is renamed to `auth.enabled`. + - `mongodbRootPassword`, `mongodbUsername`, `mongodbPassword`, `mongodbDatabase`, and `replicaSet.key` are now `auth.rootPassword`, `auth.username`, `auth.password`, `auth.database`, and `auth.replicaSetKey` respectively. + - `securityContext.*` is deprecated in favor of `podSecurityContext` and `containerSecurityContext`. + - Parameters prefixed with `mongodb` are renamed removing the prefix. E.g. `mongodbEnableIPv6` is renamed to `enableIPv6`. + - Parameters affecting Arbiter nodes are reorganized under the `arbiter.*` parameter. + +Consequences: + +- Backwards compatibility is not guaranteed. To upgrade to `8.0.0`, install a new release of the MongoDB chart, and migrate your data by creating a backup of the database, and restoring it on the new release. + +### To 7.0.0 + +From this version, the way of setting the ingress rules has changed. Instead of using `ingress.paths` and `ingress.hosts` as separate objects, you should now define the rules as objects inside the `ingress.hosts` value, for example: + +```yaml +ingress: + hosts: + - name: mongodb.local + path: / +``` + +### To 6.0.0 + +From this version, `mongodbEnableIPv6` is set to `false` by default in order to work properly in most k8s clusters, if you want to use IPv6 support, you need to set this variable to `true` by adding `--set mongodbEnableIPv6=true` to your `helm` command. +You can find more information in the [`bitnami/mongodb` image README](https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md). + +### To 5.0.0 + +When enabling replicaset configuration, backwards compatibility is not guaranteed unless you modify the labels used on the chart's statefulsets. +Use the workaround below to upgrade from versions previous to 5.0.0. The following example assumes that the release name is `my-release`: + +```console +$ kubectl delete statefulset my-release-mongodb-arbiter my-release-mongodb-primary my-release-mongodb-secondary --cascade=false +``` diff --git a/dependency_charts/mongodb/charts/common/.helmignore b/dependency_charts/mongodb/charts/common/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/dependency_charts/mongodb/charts/common/Chart.yaml b/dependency_charts/mongodb/charts/common/Chart.yaml new file mode 100644 index 0000000..aa7526c --- /dev/null +++ b/dependency_charts/mongodb/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.1.1 +description: A Library Helm Chart for grouping common logic between bitnami charts. + This chart is not deployable by itself. +home: https://github.com/bitnami/charts/tree/master/bitnami/common +icon: https://bitnami.com/downloads/logos/bitnami-mark.png +keywords: +- common +- helper +- template +- function +- bitnami +maintainers: +- email: containers@bitnami.com + name: Bitnami +name: common +sources: +- https://github.com/bitnami/charts +- http://www.bitnami.com/ +type: library +version: 1.1.3 diff --git a/dependency_charts/mongodb/charts/common/README.md b/dependency_charts/mongodb/charts/common/README.md new file mode 100644 index 0000000..d9964fe --- /dev/null +++ b/dependency_charts/mongodb/charts/common/README.md @@ -0,0 +1,309 @@ +# Bitnami Common Library Chart + +A [Helm Library Chart](https://helm.sh/docs/topics/library_charts/#helm) for grouping common logic between bitnami charts. + +## TL;DR + +```yaml +dependencies: + - name: common + version: 0.x.x + repository: https://charts.bitnami.com/bitnami +``` + +```bash +$ helm dependency update +``` + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }} +data: + myvalue: "Hello World" +``` + +## Introduction + +This chart provides a common template helpers which can be used to develop new charts using [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This Helm chart has been tested on top of [Bitnami Kubernetes Production Runtime](https://kubeprod.io/) (BKPR). Deploy BKPR to get automated TLS certificates, logging and monitoring for your applications. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.0-beta3+ + +## Parameters + +The following table lists the helpers available in the library which are scoped in different sections. + +### Affinities + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.node.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pod.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pod.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.capabilities.deployment.apiVersion` | Return the appropriate apiVersion for deployment. | `.` Chart context | +| `common.capabilities.statefulset.apiVersion` | Return the appropriate apiVersion for statefulset. | `.` Chart context | +| `common.capabilities.ingress.apiVersion` | Return the appropriate apiVersion for ingress. | `.` Chart context | + +### Errors + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.errors.upgrade.passwords.empty` | It will ensure required passwords are given when we are upgrading a chart. If `validationErrors` is not empty it will throw an error and will stop the upgrade action. | `dict "validationErrors" (list $validationError00 $validationError01) "context" $` | + +### Images + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.images.image` | Return the proper and full image name | `dict "imageRoot" .Values.path.to.the.image "global" $`, see [ImageRoot](#imageroot) for the structure. | +| `common.images.pullSecrets` | Return the proper Docker Image Registry Secret Names | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | + +### Labels + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Return the proper Docker Image Registry Secret Names | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Inpput | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.names.name` | Expand the name of the chart or use `.Values.nameOverride` | `.` Chart context | +| `common.names.fullname` | Create a default fully qualified app name. | `.` Chart context | +| `common.names.chart` | Chart name plus version | `.` Chart context | + +### Secrets + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.secrets.name` | Generate the name of the secret. | `dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $` see [ExistingSecret](#existingsecret) for the structure. | +| `common.secrets.key` | Generate secret key. | `dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName"` see [ExistingSecret](#existingsecret) for the structure. | + +### Storage + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.affinities.node.soft` | Return a soft nodeAffinity definition | `dict "persistence" .Values.path.to.the.persistence "global" $`, see [Persistence](#persistence) for the structure. | + +### TplValues + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.tplvalues.render` | Renders a value that contains template | `dict "value" .Values.path.to.the.Value "context" $`, value is the value should rendered as template, context frequently is the chart context `$` or `.` | + +### Utils + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.utils.fieldToEnvVar` | Build environment variable name given a field. | `dict "field" "my-password"` | +| `common.utils.secret.getvalue` | Print instructions to get a secret value. | `dict "secret" "secret-name" "field" "secret-value-field" "context" $` | +| `common.utils.getValueFromKey` | Gets a value from `.Values` object given its key path | `dict "key" "path.to.key" "context" $` | + +### Validations + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.validations.values.single.empty` | Validate a value must not be empty. | `dict "valueKey" "path.to.value" "secret" "secret.name" "field" "my-password" "context" $` secret and field are optional. In case they are given, the helper will generate a how to get instruction. See [ValidateValue](#validatevalue) | +| `common.validations.values.multiple.empty` | Validate a multiple values must not be empty. It returns a shared error for all the values. | `dict "required" (list $validateValueConf00 $validateValueConf01) "context" $`. See [ValidateValue](#validatevalue) | +| `common.validations.values.mariadb.passwords` | This helper will ensure required password for MariaDB are not empty. It returns a shared error for all the values. | `dict "secret" "mariadb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mariadb chart and the helper. | +| `common.validations.values.postgresql.passwords` | This helper will ensure required password for PostgreSQL are not empty. It returns a shared error for all the values. | `dict "secret" "postgresql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use postgresql chart and the helper. | +| `common.validations.values.redis.passwords` | This helper will ensure required password for Redis are not empty. It returns a shared error for all the values. | `dict "secret" "redis-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use redis chart and the helper. | +| `common.validations.values.cassandra.passwords` | This helper will ensure required password for Cassandra are not empty. It returns a shared error for all the values. | `dict "secret" "cassandra-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use cassandra chart and the helper. | +| `common.validations.values.mongodb.passwords` | This helper will ensure required password for MongoDB are not empty. It returns a shared error for all the values. | `dict "secret" "mongodb-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mongodb chart and the helper. | + +### Warnings + +| Helper identifier | Description | Expected Input | +|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.warnings.rollingTag` | Warning about using rolling tag. | `ImageRoot` see [ImageRoot](#imageroot) for the structure. | + +## Special input schemas + +### ImageRoot + +```yaml +registry: + type: string + description: Docker registry where the image is located + example: docker.io + +repository: + type: string + description: Repository and image name + example: bitnami/nginx + +tag: + type: string + description: image tag + example: 1.16.1-debian-10-r63 + +pullPolicy: + type: string + description: Specify a imagePullPolicy. Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + +pullSecrets: + type: array + items: + type: string + description: Optionally specify an array of imagePullSecrets. + +debug: + type: boolean + description: Set to true if you would like to see extra information on logs + example: false + +## An instance would be: +# registry: docker.io +# repository: bitnami/nginx +# tag: 1.16.1-debian-10-r63 +# pullPolicy: IfNotPresent +# debug: false +``` + +### Persistence + +```yaml +enabled: + type: boolean + description: Whether enable persistence. + example: true + +storageClass: + type: string + description: Ghost data Persistent Volume Storage Class, If set to "-", storageClassName: "" which disables dynamic provisioning. + example: "-" + +accessMode: + type: string + description: Access mode for the Persistent Volume Storage. + example: ReadWriteOnce + +size: + type: string + description: Size the Persistent Volume Storage. + example: 8Gi + +path: + type: string + description: Path to be persisted. + example: /bitnami + +## An instance would be: +# enabled: true +# storageClass: "-" +# accessMode: ReadWriteOnce +# size: 8Gi +# path: /bitnami +``` + +### ExistingSecret + +```yaml +name: + type: string + description: Name of the existing secret. + example: mySecret +keyMapping: + description: Mapping between the expected key name and the name of the key in the existing secret. + type: object + +## An instance would be: +# name: mySecret +# keyMapping: +# password: myPasswordKey +``` + +#### Example of use + +When we store sensitive data for a deployment in a secret, some times we want to give to users the possibility of using theirs existing secrets. + +```yaml +# templates/secret.yaml +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }} + labels: + app: {{ include "common.names.fullname" . }} +type: Opaque +data: + password: {{ .Values.password | b64enc | quote }} + +# templates/dpl.yaml +--- +... + env: + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "common.secrets.name" (dict "existingSecret" .Values.existingSecret "context" $) }} + key: {{ include "common.secrets.key" (dict "existingSecret" .Values.existingSecret "key" "password") }} +... + +# values.yaml +--- +name: mySecret +keyMapping: + password: myPasswordKey +``` + +### ValidateValue + +#### NOTES.txt + +```console +{{- $validateValueConf00 := (dict "valueKey" "path.to.value00" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value01" "secret" "secretName" "field" "password-01") -}} + +{{ include "common.validations.values.multiple.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} +``` + +If we force those values to be empty we will see some alerts + +```console +$ helm install test mychart --set path.to.value00="",path.to.value01="" + 'path.to.value00' must not be empty, please add '--set path.to.value00=$PASSWORD_00' to the command. To get the current value: + + export PASSWORD_00=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-00}" | base64 --decode) + + 'path.to.value01' must not be empty, please add '--set path.to.value01=$PASSWORD_01' to the command. To get the current value: + + export PASSWORD_01=$(kubectl get secret --namespace default secretName -o jsonpath="{.data.password-01}" | base64 --decode) +``` + +## Upgrading + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +**What changes were introduced in this major version?** + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Use `type: library`. [Here](https://v3.helm.sh/docs/faq/#library-chart-support) you can find more information. +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Charts + +**Considerations when upgrading to this version** + +- If you want to upgrade to this version from a previous one installed with Helm v3, you shouldn't face any issues +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version doesn't support Helm v2 anymore +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3 + +**Useful links** + +- https://docs.bitnami.com/tutorials/resolve-helm2-helm3-post-migration-issues/ +- https://helm.sh/docs/topics/v2_v3_migration/ +- https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3/ diff --git a/dependency_charts/mongodb/charts/common/templates/_affinities.tpl b/dependency_charts/mongodb/charts/common/templates/_affinities.tpl new file mode 100644 index 0000000..1ff26d5 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_affinities.tpl @@ -0,0 +1,94 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a soft nodeAffinity definition +{{ include "common.affinities.nodes.soft" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.soft" -}} +preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} + weight: 1 +{{- end -}} + +{{/* +Return a hard nodeAffinity definition +{{ include "common.affinities.nodes.hard" (dict "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes.hard" -}} +requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: {{ .key }} + operator: In + values: + {{- range .values }} + - {{ . }} + {{- end }} +{{- end -}} + +{{/* +Return a nodeAffinity definition +{{ include "common.affinities.nodes" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.nodes" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.nodes.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.nodes.hard" . -}} + {{- end -}} +{{- end -}} + +{{/* +Return a soft podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.soft" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace }} + topologyKey: kubernetes.io/hostname +{{- end -}} + +{{/* +Return a podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods" (dict "type" "soft" "key" "FOO" "values" (list "BAR" "BAZ")) -}} +*/}} +{{- define "common.affinities.pods" -}} + {{- if eq .type "soft" }} + {{- include "common.affinities.pods.soft" . -}} + {{- else if eq .type "hard" }} + {{- include "common.affinities.pods.hard" . -}} + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_capabilities.tpl b/dependency_charts/mongodb/charts/common/templates/_capabilities.tpl new file mode 100644 index 0000000..3c54bc4 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_capabilities.tpl @@ -0,0 +1,35 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "common.capabilities.statefulset.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_errors.tpl b/dependency_charts/mongodb/charts/common/templates/_errors.tpl new file mode 100644 index 0000000..d6d3ec6 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_errors.tpl @@ -0,0 +1,20 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Through error when upgrading using empty passwords values that must not be empty. + +Usage: +{{- $validationError00 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password00" "secret" "secretName" "field" "password-00") -}} +{{- $validationError01 := include "common.validations.values.single.empty" (dict "valueKey" "path.to.password01" "secret" "secretName" "field" "password-01") -}} +{{ include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $validationError00 $validationError01) "context" $) }} + +Required password params: + - validationErrors - String - Required. List of validation strings to be return, if it is empty it won't throw error. + - context - Context - Required. Parent context. +*/}} +{{- define "common.errors.upgrade.passwords.empty" -}} + {{- $validationErrors := join "" .validationErrors -}} + {{- if and $validationErrors .context.Release.IsUpgrade -}} + {{- $errorString := "\nPASSWORDS ERROR: you must provide your current passwords when upgrade the release%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_images.tpl b/dependency_charts/mongodb/charts/common/templates/_images.tpl new file mode 100644 index 0000000..aafde9f --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_images.tpl @@ -0,0 +1,43 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper image name +{{ include "common.images.image" ( dict "imageRoot" .Values.path.to.the.image "global" $) }} +*/}} +{{- define "common.images.image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.imageRegistry }} + {{- $registryName = .global.imageRegistry -}} + {{- end -}} +{{- end -}} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +{{ include "common.images.pullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global) }} +*/}} +{{- define "common.images.pullSecrets" -}} + {{- $pullSecrets := list }} + + {{- if .global }} + {{- range .global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_labels.tpl b/dependency_charts/mongodb/charts/common/templates/_labels.tpl new file mode 100644 index 0000000..252066c --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_labels.tpl @@ -0,0 +1,18 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Kubernetes standard labels +*/}} +{{- define "common.labels.standard" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +helm.sh/chart: {{ include "common.names.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Labels to use on deploy.spec.selector.matchLabels and svc.spec.selector +*/}} +{{- define "common.labels.matchLabels" -}} +app.kubernetes.io/name: {{ include "common.names.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_names.tpl b/dependency_charts/mongodb/charts/common/templates/_names.tpl new file mode 100644 index 0000000..adf2a74 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_names.tpl @@ -0,0 +1,32 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "common.names.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "common.names.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "common.names.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_secrets.tpl b/dependency_charts/mongodb/charts/common/templates/_secrets.tpl new file mode 100644 index 0000000..ebfb5d4 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_secrets.tpl @@ -0,0 +1,57 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Generate secret name. + +Usage: +{{ include "common.secrets.name" (dict "existingSecret" .Values.path.to.the.existingSecret "defaultNameSuffix" "mySuffix" "context" $) }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - defaultNameSuffix - String - Optional. It is used only if we have several secrets in the same deployment. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.secrets.name" -}} +{{- $name := (include "common.names.fullname" .context) -}} + +{{- if .defaultNameSuffix -}} +{{- $name = printf "%s-%s" $name .defaultNameSuffix | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- with .existingSecret -}} +{{- if not (typeIs "string" .) -}} +{{- $name = .name -}} +{{- else -}} +{{- $name = . -}} +{{- end -}} +{{- end -}} + +{{- printf "%s" $name -}} +{{- end -}} + +{{/* +Generate secret key. + +Usage: +{{ include "common.secrets.key" (dict "existingSecret" .Values.path.to.the.existingSecret "key" "keyName") }} + +Params: + - existingSecret - ExistingSecret/String - Optional. The path to the existing secrets in the values.yaml given by the user + to be used instead of the default one. Allows for it to be of type String (just the secret name) for backwards compatibility. + +info: https://github.com/bitnami/charts/tree/master/bitnami/common#existingsecret + - key - String - Required. Name of the key in the secret. +*/}} +{{- define "common.secrets.key" -}} +{{- $key := .key -}} + +{{- if .existingSecret -}} + {{- if not (typeIs "string" .existingSecret) -}} + {{- if .existingSecret.keyMapping -}} + {{- $key = index .existingSecret.keyMapping $.key -}} + {{- end -}} + {{- end }} +{{- end -}} + +{{- printf "%s" $key -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_storage.tpl b/dependency_charts/mongodb/charts/common/templates/_storage.tpl new file mode 100644 index 0000000..60e2a84 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_storage.tpl @@ -0,0 +1,23 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Return the proper Storage Class +{{ include "common.storage.class" ( dict "persistence" .Values.path.to.the.persistence "global" $) }} +*/}} +{{- define "common.storage.class" -}} + +{{- $storageClass := .persistence.storageClass -}} +{{- if .global -}} + {{- if .global.storageClass -}} + {{- $storageClass = .global.storageClass -}} + {{- end -}} +{{- end -}} + +{{- if $storageClass -}} + {{- if (eq "-" $storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" $storageClass -}} + {{- end -}} +{{- end -}} + +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_tplvalues.tpl b/dependency_charts/mongodb/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000..2db1668 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_tplvalues.tpl @@ -0,0 +1,13 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Renders a value that contains template. +Usage: +{{ include "common.tplvalues.render" ( dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "common.tplvalues.render" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_utils.tpl b/dependency_charts/mongodb/charts/common/templates/_utils.tpl new file mode 100644 index 0000000..74774a3 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_utils.tpl @@ -0,0 +1,45 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Print instructions to get a secret value. +Usage: +{{ include "common.utils.secret.getvalue" (dict "secret" "secret-name" "field" "secret-value-field" "context" $) }} +*/}} +{{- define "common.utils.secret.getvalue" -}} +{{- $varname := include "common.utils.fieldToEnvVar" . -}} +export {{ $varname }}=$(kubectl get secret --namespace {{ .context.Release.Namespace }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 --decode) +{{- end -}} + +{{/* +Build env var name given a field +Usage: +{{ include "common.utils.fieldToEnvVar" dict "field" "my-password" }} +*/}} +{{- define "common.utils.fieldToEnvVar" -}} + {{- $fieldNameSplit := splitList "-" .field -}} + {{- $upperCaseFieldNameSplit := list -}} + + {{- range $fieldNameSplit -}} + {{- $upperCaseFieldNameSplit = append $upperCaseFieldNameSplit ( upper . ) -}} + {{- end -}} + + {{ join "_" $upperCaseFieldNameSplit }} +{{- end -}} + +{{/* +Gets a value from .Values given +Usage: +{{ include "common.utils.getValueFromKey" (dict "key" "path.to.key" "context" $) }} +*/}} +{{- define "common.utils.getValueFromKey" -}} +{{- $splitKey := splitList "." .key -}} +{{- $value := "" -}} +{{- $latestObj := $.context.Values -}} +{{- range $splitKey -}} + {{- if not $latestObj -}} + {{- printf "please review the entire path of '%s' exists in values" $.key | fail -}} + {{- end -}} + {{- $value = ( index $latestObj . ) -}} + {{- $latestObj = $value -}} +{{- end -}} +{{- printf "%v" (default "" $value) -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/_warnings.tpl b/dependency_charts/mongodb/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000..ae10fa4 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/_warnings.tpl @@ -0,0 +1,14 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Warning about using rolling tag. +Usage: +{{ include "common.warnings.rollingTag" .Values.path.to.the.imageRoot }} +*/}} +{{- define "common.warnings.rollingTag" -}} + +{{- if and (contains "bitnami/" .repository) (not (.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .repository }}:{{ .tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} + +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_cassandra.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000..8679ddf --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_cassandra.tpl @@ -0,0 +1,72 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Cassandra required passwords are not empty. + +Usage: +{{ include "common.validations.values.cassandra.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where Cassandra values are stored, e.g: "cassandra-passwords-secret" + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.cassandra.passwords" -}} + {{- $existingSecret := include "common.cassandra.values.existingSecret" . -}} + {{- $enabled := include "common.cassandra.values.enabled" . -}} + {{- $dbUserPrefix := include "common.cassandra.values.key.dbUser" . -}} + {{- $valueKeyPassword := printf "%s.password" $dbUserPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "cassandra-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.cassandra.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.cassandra.dbUser.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.dbUser.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled cassandra. + +Usage: +{{ include "common.cassandra.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.cassandra.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.cassandra.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key dbUser + +Usage: +{{ include "common.cassandra.values.key.dbUser" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Cassandra is used as subchart or not. Default: false +*/}} +{{- define "common.cassandra.values.key.dbUser" -}} + {{- if .subchart -}} + cassandra.dbUser + {{- else -}} + dbUser + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_mariadb.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000..bb5ed72 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_mariadb.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MariaDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mariadb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MariaDB values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mariadb.passwords" -}} + {{- $existingSecret := include "common.mariadb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mariadb.values.enabled" . -}} + {{- $architecture := include "common.mariadb.values.architecture" . -}} + {{- $authPrefix := include "common.mariadb.values.key.auth" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicationPassword := printf "%s.replicationPassword" $authPrefix -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mariadb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- if not (empty $valueUsername) -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mariadb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mariadb-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mariadb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mariadb. + +Usage: +{{ include "common.mariadb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mariadb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mariadb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mariadb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mariadb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mariadb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mariadb.values.key.auth" -}} + {{- if .subchart -}} + mariadb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_mongodb.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000..a786188 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_mongodb.tpl @@ -0,0 +1,108 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MongoDB required passwords are not empty. + +Usage: +{{ include "common.validations.values.mongodb.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MongoDB values are stored, e.g: "mongodb-passwords-secret" + - subchart - Boolean - Optional. Whether MongoDB is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mongodb.passwords" -}} + {{- $existingSecret := include "common.mongodb.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mongodb.values.enabled" . -}} + {{- $authPrefix := include "common.mongodb.values.key.auth" . -}} + {{- $architecture := include "common.mongodb.values.architecture" . -}} + {{- $valueKeyRootPassword := printf "%s.rootPassword" $authPrefix -}} + {{- $valueKeyUsername := printf "%s.username" $authPrefix -}} + {{- $valueKeyDatabase := printf "%s.database" $authPrefix -}} + {{- $valueKeyPassword := printf "%s.password" $authPrefix -}} + {{- $valueKeyReplicaSetKey := printf "%s.replicaSetKey" $authPrefix -}} + {{- $valueKeyAuthEnabled := printf "%s.enabled" $authPrefix -}} + + {{- $authEnabled := include "common.utils.getValueFromKey" (dict "key" $valueKeyAuthEnabled "context" .context) -}} + + {{- if and (not $existingSecret) (eq $enabled "true") (eq $authEnabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mongodb-root-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRootPassword -}} + + {{- $valueUsername := include "common.utils.getValueFromKey" (dict "key" $valueKeyUsername "context" .context) }} + {{- $valueDatabase := include "common.utils.getValueFromKey" (dict "key" $valueKeyDatabase "context" .context) }} + {{- if and $valueUsername $valueDatabase -}} + {{- $requiredPassword := dict "valueKey" $valueKeyPassword "secret" .secret "field" "mongodb-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replicaset") -}} + {{- $requiredReplicaSetKey := dict "valueKey" $valueKeyReplicaSetKey "secret" .secret "field" "mongodb-replica-set-key" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredReplicaSetKey -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.mongodb.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDb is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mongodb. + +Usage: +{{ include "common.mongodb.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mongodb.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mongodb.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mongodb.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MongoDB is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.key.auth" -}} + {{- if .subchart -}} + mongodb.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mongodb.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MariaDB is used as subchart or not. Default: false +*/}} +{{- define "common.mongodb.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mongodb.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_postgresql.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000..992bcd3 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,131 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate PostgreSQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.postgresql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where postgresql values are stored, e.g: "postgresql-passwords-secret" + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.postgresql.passwords" -}} + {{- $existingSecret := include "common.postgresql.values.existingSecret" . -}} + {{- $enabled := include "common.postgresql.values.enabled" . -}} + {{- $valueKeyPostgresqlPassword := include "common.postgresql.values.key.postgressPassword" . -}} + {{- $valueKeyPostgresqlReplicationEnabled := include "common.postgresql.values.key.replicationPassword" . -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredPostgresqlPassword := dict "valueKey" $valueKeyPostgresqlPassword "secret" .secret "field" "postgresql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlPassword -}} + + {{- $enabledReplication := include "common.postgresql.values.enabled.replication" . -}} + {{- if (eq $enabledReplication "true") -}} + {{- $requiredPostgresqlReplicationPassword := dict "valueKey" $valueKeyPostgresqlReplicationEnabled "secret" .secret "field" "postgresql-replication-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPostgresqlReplicationPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to decide whether evaluate global values. + +Usage: +{{ include "common.postgresql.values.use.global" (dict "key" "key-of-global" "context" $) }} +Params: + - key - String - Required. Field to be evaluated within global, e.g: "existingSecret" +*/}} +{{- define "common.postgresql.values.use.global" -}} + {{- if .context.Values.global -}} + {{- if .context.Values.global.postgresql -}} + {{- index .context.Values.global.postgresql .key | quote -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.postgresql.values.existingSecret" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.existingSecret" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "existingSecret" "context" .context) -}} + + {{- if .subchart -}} + {{- default (.context.Values.postgresql.existingSecret | quote) $globalValue -}} + {{- else -}} + {{- default (.context.Values.existingSecret | quote) $globalValue -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled postgresql. + +Usage: +{{ include "common.postgresql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.postgresql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key postgressPassword. + +Usage: +{{ include "common.postgresql.values.key.postgressPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.postgressPassword" -}} + {{- $globalValue := include "common.postgresql.values.use.global" (dict "key" "postgresqlUsername" "context" .context) -}} + + {{- if not $globalValue -}} + {{- if .subchart -}} + postgresql.postgresqlPassword + {{- else -}} + postgresqlPassword + {{- end -}} + {{- else -}} + global.postgresql.postgresqlPassword + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled.replication. + +Usage: +{{ include "common.postgresql.values.enabled.replication" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.enabled.replication" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.postgresql.replication.enabled -}} + {{- else -}} + {{- printf "%v" .context.Values.replication.enabled -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key replication.password. + +Usage: +{{ include "common.postgresql.values.key.replicationPassword" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether postgresql is used as subchart or not. Default: false +*/}} +{{- define "common.postgresql.values.key.replicationPassword" -}} + {{- if .subchart -}} + postgresql.replication.password + {{- else -}} + replication.password + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_redis.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000..cdcc452 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,72 @@ + +{{/* vim: set filetype=mustache: */}} +{{/* +Validate Redis required passwords are not empty. + +Usage: +{{ include "common.validations.values.redis.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where redis values are stored, e.g: "redis-passwords-secret" + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.redis.passwords" -}} + {{- $existingSecret := include "common.redis.values.existingSecret" . -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $valueKeyRedisPassword := printf "%s%s" $valueKeyPrefix "password" -}} + {{- $valueKeyRedisUsePassword := printf "%s%s" $valueKeyPrefix "usePassword" -}} + + {{- if and (not $existingSecret) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $usePassword := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUsePassword "context" .context) -}} + {{- if eq $usePassword "true" -}} + {{- $requiredRedisPassword := dict "valueKey" $valueKeyRedisPassword "secret" .secret "field" "redis-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredRedisPassword -}} + {{- end -}} + + {{- include "common.validations.values.multiple.empty" (dict "required" $requiredPasswords "context" .context) -}} + {{- end -}} +{{- end -}} + +{{/* +Redis Auxiliary function to get the right value for existingSecret. + +Usage: +{{ include "common.redis.values.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether Redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.redis.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled redis. + +Usage: +{{ include "common.redis.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.redis.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.redis.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right prefix path for the values + +Usage: +{{ include "common.redis.values.key.prefix" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether redis is used as subchart or not. Default: false +*/}} +{{- define "common.redis.values.keys.prefix" -}} + {{- if .subchart -}}redis.{{- else -}}{{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/templates/validations/_validations.tpl b/dependency_charts/mongodb/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000..d4cf32c --- /dev/null +++ b/dependency_charts/mongodb/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,44 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate values must not be empty. + +Usage: +{{- $validateValueConf00 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-00") -}} +{{- $validateValueConf01 := (dict "valueKey" "path.to.value" "secret" "secretName" "field" "password-01") -}} +{{ include "common.validations.values.empty" (dict "required" (list $validateValueConf00 $validateValueConf01) "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.multiple.empty" -}} + {{- range .required -}} + {{- include "common.validations.values.single.empty" (dict "valueKey" .valueKey "secret" .secret "field" .field "context" $.context) -}} + {{- end -}} +{{- end -}} + +{{/* +Validate a value must not be empty. + +Usage: +{{ include "common.validations.value.empty" (dict "valueKey" "mariadb.password" "secret" "secretName" "field" "my-password" "context" $) }} + +Validate value params: + - valueKey - String - Required. The path to the validating value in the values.yaml, e.g: "mysql.password" + - secret - String - Optional. Name of the secret where the validating value is generated/stored, e.g: "mysql-passwords-secret" + - field - String - Optional. Name of the field in the secret data, e.g: "mysql-password" +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + + {{- if not $value -}} + {{- $varname := "my-value" -}} + {{- $getCurrentValue := "" -}} + {{- if and .secret .field -}} + {{- $varname = include "common.utils.fieldToEnvVar" . -}} + {{- $getCurrentValue = printf " To get the current value:\n\n %s\n" (include "common.utils.secret.getvalue" .) -}} + {{- end -}} + {{- printf "\n '%s' must not be empty, please add '--set %s=$%s' to the command.%s" .valueKey .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/charts/common/values.yaml b/dependency_charts/mongodb/charts/common/values.yaml new file mode 100644 index 0000000..9ecdc93 --- /dev/null +++ b/dependency_charts/mongodb/charts/common/values.yaml @@ -0,0 +1,3 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +exampleValue: common-chart diff --git a/dependency_charts/mongodb/ci/values-replicaset-with-rbac.yaml b/dependency_charts/mongodb/ci/values-replicaset-with-rbac.yaml new file mode 100644 index 0000000..a8b62e2 --- /dev/null +++ b/dependency_charts/mongodb/ci/values-replicaset-with-rbac.yaml @@ -0,0 +1,8 @@ +architecture: replicaset +replicaCount: 3 +pdb: + create: true +rbac: + create: true +serviceAccount: + create: true diff --git a/dependency_charts/mongodb/templates/NOTES.txt b/dependency_charts/mongodb/templates/NOTES.txt new file mode 100644 index 0000000..30e027d --- /dev/null +++ b/dependency_charts/mongodb/templates/NOTES.txt @@ -0,0 +1,174 @@ +{{- $replicaCount := int .Values.replicaCount }} +{{- $portNumber := int .Values.service.port }} +{{- $fullname := include "mongodb.fullname" . }} +{{- $releaseNamespace := include "mongodb.namespace" . }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $loadBalancerIPListLength := len .Values.externalAccess.service.loadBalancerIPs }} +{{- $mongoList := list }} +{{- range $e, $i := until $replicaCount }} +{{- $mongoList = append $mongoList (printf "%s-%d.%s-headless.%s.svc.%s:%d" $fullname $i $fullname $releaseNamespace $clusterDomain $portNumber) }} +{{- end }} + +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $loadBalancerIPListLength )) (eq .Values.externalAccess.service.type "LoadBalancer") }} + +############################################################################### +### ERROR: You enabled external access to MongoDB nodes without specifying ### +### the array of load balancer IPs for MongoDB nodes. ### +############################################################################### + +This deployment will be incomplete until you configure the array of load balancer +IPs for MongoDB nodes. To complete your deployment follow the steps below: + +1. Wait for the load balancer IPs (it may take a few minutes for them to be available): + + kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "mongodb.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=mongodb" -w + +2. Obtain the load balancer IPs and upgrade your chart: + + {{- range $e, $i := until $replicaCount }} + LOAD_BALANCER_IP_{{ add $i 1 }}="$(kubectl get svc --namespace {{ $releaseNamespace }} {{ $fullname }}-{{ $i }}-external -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" + {{- end }} + +3. Upgrade you chart: + + helm upgrade {{ .Release.Name }} bitnami/{{ .Chart.Name }} \ + --set mongodb.replicaCount={{ $replicaCount }} \ + --set mongodb.externalAccess.enabled=true \ + {{- range $i, $e := until $replicaCount }} + --set mongodb.externalAccess.service.loadBalancerIPs[{{ $i }}]=$LOAD_BALANCER_IP_{{ add $i 1 }} \ + {{- end }} + --set mongodb.externalAccess.service.type=LoadBalancer + +{{- else }} + +{{- if and (or (and (eq .Values.architecture "standalone") (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort"))) (and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled)) (not .Values.auth.enabled) }} +------------------------------------------------------------------------------- + WARNING + + By not enabling "mongodb.auth.enabled" you have most likely exposed the + MongoDB service externally without any authentication mechanism. + + For security reasons, we strongly suggest that you enable authentiation + setting the "mongodb.auth.enabled" parameter to "true". + +------------------------------------------------------------------------------- +{{- end }} + +** Please be patient while the chart is being deployed ** + +MongoDB can be accessed on the following DNS name(s) and ports from within your cluster: + +{{- if eq .Values.architecture "replicaset" }} +{{ join "\n" $mongoList | nindent 4 }} +{{- else }} + + {{ $fullname }}.{{ $releaseNamespace }}.svc.{{ .Values.clusterDomain }} + +{{- end }} + +{{- if .Values.auth.enabled }} + +To get the root password run: + + export MONGODB_ROOT_PASSWORD=$(kubectl get secret --namespace {{ template "mongodb.namespace" . }} {{ template "mongodb.fullname" . }} -o jsonpath="{.data.mongodb-root-password}" | base64 --decode) + +{{- end }} +{{- if and .Values.auth.username .Values.auth.database .Values.auth.password }} + +To get the password for "{{ .Values.auth.username }}" run: + + export MONGODB_PASSWORD=$(kubectl get secret --namespace {{ template "mongodb.namespace" . }} {{ template "mongodb.fullname" . }} -o jsonpath="{.data.mongodb-password}" | base64 --decode) + +{{- end }} + +To connect to your database, create a MongoDB client container: + + kubectl run --namespace {{ template "mongodb.namespace" . }} {{ template "mongodb.fullname" . }}-client --rm --tty -i --restart='Never' --env="MONGODB_ROOT_PASSWORD=$MONGODB_ROOT_PASSWORD" --image {{ template "mongodb.image" . }} --command -- bash + +Then, run the following command: + + {{- if eq .Values.architecture "replicaset" }} + mongo admin --host "{{ join "," $mongoList }}" {{- if .Values.auth.enabled }} --authenticationDatabase admin -u root -p $MONGODB_ROOT_PASSWORD{{- end }} + {{- else }} + mongo admin --host "{{ template "mongodb.fullname" . }}" {{- if .Values.auth.enabled }} --authenticationDatabase admin -u root -p $MONGODB_ROOT_PASSWORD{{- end }} + {{- end }} + +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled }} + +To connect to your database nodes from outside, you need to add both primary and secondary nodes hostnames/IPs to your Mongo client. To obtain them, follow the instructions below: + +{{- if eq "NodePort" .Values.externalAccess.service.type }} +{{- if .Values.externalAccess.service.domain }} + + MongoDB nodes domain: Use your provided hostname to reach MongoDB nodes, {{ .Values.externalAccess.service.domain }} + +{{- else }} + + MongoDB nodes domain: you can reach MongoDB nodes on any of the K8s nodes external IPs. + + kubectl get nodes -o wide + +{{- end }} + + MongoDB nodes port: You will have a different node port for each MongoDB node. You can get the list of configured node ports using the command below: + + echo "$(kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "mongodb.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=mongodb,pod" -o jsonpath='{.items[*].spec.ports[0].nodePort}' | tr ' ' '\n')" + +{{- else if contains "LoadBalancer" .Values.externalAccess.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IPs to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "mongodb.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=mongodb,pod" -w' + + MongoDB nodes domain: You will have a different external IP for each MongoDB node. You can get the list of external IPs using the command below: + + echo "$(kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ template "mongodb.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=mongodb,pod" -o jsonpath='{.items[*].status.loadBalancer.ingress[0].ip}' | tr ' ' '\n')" + + MongoDB nodes port: {{ .Values.externalAccess.service.port }} + +{{- end }} + +{{- else if eq .Values.architecture "standalone" }} + +To connect to your database from outside the cluster execute the following commands: + +{{- if contains "NodePort" .Values.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ template "mongodb.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ template "mongodb.namespace" . }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "mongodb.fullname" . }}) + mongo --host $NODE_IP --port $NODE_PORT {{- if .Values.auth.enabled }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }} + +{{- else if contains "LoadBalancer" .Values.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ template "mongodb.namespace" . }} -w {{ template "mongodb.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ template "mongodb.namespace" . }} {{ template "mongodb.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + mongo --host $SERVICE_IP --port {{ $portNumber }} {{- if .Values.auth.enabled }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }} + +{{- else if contains "ClusterIP" .Values.service.type }} + + kubectl port-forward --namespace {{ template "mongodb.namespace" . }} svc/{{ template "mongodb.fullname" . }} {{ $portNumber }}:{{ $portNumber }} & + mongo --host 127.0.0.1 {{- if .Values.auth.enabled }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }} + +{{- end }} +{{- end }} +{{- end }} + +{{- if .Values.metrics.enabled }} + +To access the MongoDB Prometheus metrics, get the MongoDB Prometheus URL by running: + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "mongodb.fullname" . }}-metrics {{ .Values.metrics.service.port }}:{{ .Values.metrics.service.port }} & + echo "Prometheus Metrics URL: http://127.0.0.1:{{ .Values.metrics.service.port }}/metrics" + +Then, open the obtained URL in a browser. + +{{- end }} + +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.metrics.image }} +{{- include "common.warnings.rollingTag" .Values.externalAccess.autoDiscovery.image }} +{{- include "mongodb.validateValues" . }} +{{- $secretName := include "mongodb.fullname" . -}} +{{- $passwordValidationErrors := include "common.validations.values.mongodb.passwords" (dict "secret" $secretName "context" $) -}} +{{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $passwordValidationErrors) "context" $) -}} diff --git a/dependency_charts/mongodb/templates/_helpers.tpl b/dependency_charts/mongodb/templates/_helpers.tpl new file mode 100644 index 0000000..4c83cbd --- /dev/null +++ b/dependency_charts/mongodb/templates/_helpers.tpl @@ -0,0 +1,294 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "mongodb.name" -}} +{{- include "common.names.name" . -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "mongodb.fullname" -}} +{{- include "common.names.fullname" . -}} +{{- end -}} + +{{/* +Return the proper MongoDB image name +*/}} +{{- define "mongodb.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the metrics image) +*/}} +{{- define "mongodb.metrics.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.metrics.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "mongodb.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the init container auto-discovery image) +*/}} +{{- define "mongodb.externalAccess.autoDiscovery.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.externalAccess.autoDiscovery.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the TLS Certs image) +*/}} +{{- define "mongodb.tls.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.tls.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "mongodb.imagePullSecrets" -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image .Values.volumePermissions.image) "global" .Values.global) }} +{{- end -}} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "mongodb.namespace" -}} + {{- if .Values.global -}} + {{- if .Values.global.namespaceOverride }} + {{- .Values.global.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end }} +{{- end -}} +{{- define "mongodb.serviceMonitor.namespace" -}} + {{- if .Values.metrics.serviceMonitor.namespace -}} + {{- .Values.metrics.serviceMonitor.namespace -}} + {{- else -}} + {{- include "mongodb.namespace" . -}} + {{- end }} +{{- end -}} +{{- define "mongodb.prometheusRule.namespace" -}} + {{- if .Values.metrics.prometheusRule.namespace -}} + {{- .Values.metrics.prometheusRule.namespace -}} + {{- else -}} + {{- include "mongodb.namespace" . -}} + {{- end }} +{{- end -}} + +{{/* +Returns the proper service account name depending if an explicit service account name is set +in the values file. If the name is not set it will default to either mongodb.fullname if serviceAccount.create +is true or default otherwise. +*/}} +{{- define "mongodb.serviceAccountName" -}} + {{- if .Values.serviceAccount.create -}} + {{ default (include "mongodb.fullname" .) .Values.serviceAccount.name }} + {{- else -}} + {{ default "default" .Values.serviceAccount.name }} + {{- end -}} +{{- end -}} + +{{/* +Return the configmap with the MongoDB configuration +*/}} +{{- define "mongodb.configmapName" -}} +{{- if .Values.existingConfigmap -}} + {{- printf "%s" (tpl .Values.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s" (include "mongodb.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created for MongoDB +*/}} +{{- define "mongodb.createConfigmap" -}} +{{- if and .Values.configuration (not .Values.existingConfigmap) }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret with MongoDB credentials +*/}} +{{- define "mongodb.secretName" -}} + {{- if .Values.auth.existingSecret -}} + {{- printf "%s" .Values.auth.existingSecret -}} + {{- else -}} + {{- printf "%s" (include "mongodb.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created for MongoDB +*/}} +{{- define "mongodb.createSecret" -}} +{{- if and .Values.auth.enabled (not .Values.auth.existingSecret) }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created for MongoDB +*/}} +{{- define "mongodb.caSecretName" -}} +{{- if .Values.tls.existingSecret -}} + {{ .Values.tls.existingSecret }} +{{- else -}} + {{ include "mongodb.fullname" . }}-ca +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts ConfigMap name. +*/}} +{{- define "mongodb.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} +{{- printf "%s" .Values.initdbScriptsConfigMap -}} +{{- else -}} +{{- printf "%s-init-scripts" (include "mongodb.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if the Arbiter should be deployed +*/}} +{{- define "mongodb.arbiter.enabled" -}} +{{- if and (eq .Values.architecture "replicaset") .Values.arbiter.enabled }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Return the configmap with the MongoDB configuration for the Arbiter +*/}} +{{- define "mongodb.arbiter.configmapName" -}} +{{- if .Values.arbiter.existingConfigmap -}} + {{- printf "%s" (tpl .Values.arbiter.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s-arbiter" (include "mongodb.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created for MongoDB Arbiter +*/}} +{{- define "mongodb.arbiter.createConfigmap" -}} +{{- if and (eq .Values.architecture "replicaset") .Values.arbiter.enabled .Values.arbiter.configuration (not .Values.arbiter.existingConfigmap) }} + {{- true -}} +{{- else -}} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "mongodb.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "mongodb.validateValues.architecture" .) -}} +{{- $messages := append $messages (include "mongodb.validateValues.customDatabase" .) -}} +{{- $messages := append $messages (include "mongodb.validateValues.externalAccessServiceType" .) -}} +{{- $messages := append $messages (include "mongodb.validateValues.loadBalancerIPsListLength" .) -}} +{{- $messages := append $messages (include "mongodb.validateValues.nodePortListLength" .) -}} +{{- $messages := append $messages (include "mongodb.validateValues.externalAccessAutoDiscoveryRBAC" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of MongoDB - must provide a valid architecture */}} +{{- define "mongodb.validateValues.architecture" -}} +{{- if and (ne .Values.architecture "standalone") (ne .Values.architecture "replicaset") -}} +mongodb: architecture + Invalid architecture selected. Valid values are "standalone" and + "replicaset". Please set a valid architecture (--set mongodb.architecture="xxxx") +{{- end -}} +{{- end -}} + +{{/* +Validate values of MongoDB - both auth.username and auth.database are necessary +to create a custom user and database during 1st initialization +*/}} +{{- define "mongodb.validateValues.customDatabase" -}} +{{- if or (and .Values.auth.username (not .Values.auth.database)) (and (not .Values.auth.username) .Values.auth.database) }} +mongodb: auth.username, auth.database + Both auth.username and auth.database must be provided to create + a custom user and database during 1st initialization. + Please set both of them (--set auth.username="xxxx",auth.database="yyyy") +{{- end -}} +{{- end -}} + + +{{/* +Validate values of MongoDB - service type for external access +*/}} +{{- define "mongodb.validateValues.externalAccessServiceType" -}} +{{- if and (eq .Values.architecture "replicaset") (not (eq .Values.externalAccess.service.type "NodePort")) (not (eq .Values.externalAccess.service.type "LoadBalancer")) -}} +mongodb: externalAccess.service.type + Available service type for external access are NodePort or LoadBalancer. +{{- end -}} +{{- end -}} + +{{/* +Validate values of MongoDB - number of replicas must be the same than LoadBalancer IPs list +*/}} +{{- define "mongodb.validateValues.loadBalancerIPsListLength" -}} +{{- $replicaCount := int .Values.replicaCount }} +{{- $loadBalancerListLength := len .Values.externalAccess.service.loadBalancerIPs }} +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled (not .Values.externalAccess.autoDiscovery.enabled ) (eq .Values.externalAccess.service.type "LoadBalancer") (not (eq $replicaCount $loadBalancerListLength )) -}} +mongodb: .Values.externalAccess.service.loadBalancerIPs + Number of replicas and loadBalancerIPs array length must be the same. +{{- end -}} +{{- end -}} + +{{/* +Validate values of MongoDB - number of replicas must be the same than NodePort list +*/}} +{{- define "mongodb.validateValues.nodePortListLength" -}} +{{- $replicaCount := int .Values.replicaCount }} +{{- $nodePortListLength := len .Values.externalAccess.service.nodePorts }} +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled (eq .Values.externalAccess.service.type "NodePort") (not (eq $replicaCount $nodePortListLength )) -}} +mongodb: .Values.externalAccess.service.nodePorts + Number of replicas and nodePorts array length must be the same. +{{- end -}} +{{- end -}} + +{{/* +Validate values of MongoDB - RBAC should be enabled when autoDiscovery is enabled +*/}} +{{- define "mongodb.validateValues.externalAccessAutoDiscoveryRBAC" -}} +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (not .Values.rbac.create )}} +mongodb: rbac.create + By specifying "externalAccess.enabled=true" and "externalAccess.autoDiscovery.enabled=true" + an initContainer will be used to autodetect the external IPs/ports by querying the + K8s API. Please note this initContainer requires specific RBAC resources. You can create them + by specifying "--set rbac.create=true". +{{- end -}} +{{- end -}} + +{{/* +Validate values of MongoDB exporter URI string - auth.enabled and/or tls.enabled must be enabled or it defaults +*/}} +{{- define "mongodb.mongodb_exporter.uri" -}} + {{- $uriTlsArgs := ternary "tls=true&tlsCertificateKeyFile=/certs/mongodb.pem&tlsCAFile=/certs/mongodb-ca-cert" "" .Values.tls.enabled -}} + {{- $uriAuth := ternary "root:$(echo $MONGODB_ROOT_PASSWORD | sed -r \"s/@/%40/g;s/:/%3A/g\")@" "" .Values.auth.enabled -}} + + {{- printf "mongodb://%slocalhost:27017/admin?%s" $uriAuth $uriTlsArgs -}} +{{- end -}} diff --git a/dependency_charts/mongodb/templates/arbiter/configmap.yaml b/dependency_charts/mongodb/templates/arbiter/configmap.yaml new file mode 100644 index 0000000..1971200 --- /dev/null +++ b/dependency_charts/mongodb/templates/arbiter/configmap.yaml @@ -0,0 +1,12 @@ +{{- if (include "mongodb.arbiter.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mongodb.fullname" . }}-arbiter + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: arbiter +data: + mongodb.conf: |- + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.configuration "context" $) | nindent 4 }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/arbiter/headless-svc.yaml b/dependency_charts/mongodb/templates/arbiter/headless-svc.yaml new file mode 100644 index 0000000..85d8ac9 --- /dev/null +++ b/dependency_charts/mongodb/templates/arbiter/headless-svc.yaml @@ -0,0 +1,21 @@ +{{- if (include "mongodb.arbiter.enabled" .) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mongodb.fullname" . }}-arbiter-headless + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: arbiter + {{- if .Values.service.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: tcp-mongodb + port: {{ .Values.service.port }} + targetPort: mongodb + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: arbiter +{{- end }} diff --git a/dependency_charts/mongodb/templates/arbiter/pdb.yaml b/dependency_charts/mongodb/templates/arbiter/pdb.yaml new file mode 100644 index 0000000..6cc024c --- /dev/null +++ b/dependency_charts/mongodb/templates/arbiter/pdb.yaml @@ -0,0 +1,19 @@ +{{- if and (include "mongodb.arbiter.enabled" .) .Values.arbiter.pdb.create }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ include "mongodb.fullname" . }}-arbiter + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: arbiter +spec: + {{- if .Values.arbiter.pdb.minAvailable }} + minAvailable: {{ .Values.arbiter.pdb.minAvailable }} + {{- end }} + {{- if .Values.arbiter.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.arbiter.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: arbiter +{{- end }} diff --git a/dependency_charts/mongodb/templates/arbiter/statefulset.yaml b/dependency_charts/mongodb/templates/arbiter/statefulset.yaml new file mode 100644 index 0000000..ba64e4a --- /dev/null +++ b/dependency_charts/mongodb/templates/arbiter/statefulset.yaml @@ -0,0 +1,267 @@ +{{- if (include "mongodb.arbiter.enabled" .) }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "mongodb.fullname" . }}-arbiter + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: arbiter + {{- if .Values.arbiter.labels }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.labels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.arbiter.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + serviceName: {{ include "mongodb.fullname" . }}-arbiter-headless + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: arbiter + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: arbiter + {{- if .Values.arbiter.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.podLabels "context" $) | nindent 8 }} + {{- end }} + {{- if or (include "mongodb.arbiter.createConfigmap" .) .Values.arbiter.podAnnotations }} + annotations: + {{- if (include "mongodb.arbiter.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/arbiter/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.arbiter.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.podAnnotations "context" $) | nindent 8 }} + {{- end }} + {{- end }} + spec: + {{- include "mongodb.imagePullSecrets" . | nindent 6 }} + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} + serviceAccountName: {{ template "mongodb.serviceAccountName" . }} + {{- if .Values.arbiter.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.arbiter.podAffinityPreset "component" "arbiter" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.arbiter.podAntiAffinityPreset "component" "arbiter" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.arbiter.nodeAffinityPreset.type "key" .Values.arbiter.nodeAffinityPreset.key "values" .Values.arbiter.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.arbiter.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.arbiter.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.arbiter.priorityClassName }} + priorityClassName: {{ .Values.arbiter.priorityClassName }} + {{- end }} + {{- if .Values.arbiter.podSecurityContext.enabled }} + securityContext: {{- omit .Values.arbiter.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + initContainers: + {{- if .Values.arbiter.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.tls.enabled .Values.arbiter.enabled }} + - name: generate-client + image: {{ include "mongodb.tls.image" . }} + imagePullPolicy: {{ .Values.tls.image.pullPolicy | quote }} + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: certs-volume + mountPath: /certs/CAs + - name: certs + mountPath: /certs + command: + - sh + - "-c" + - | + /bin/bash <<'EOF' + my_hostname=$(hostname) + svc=$(echo -n "$my_hostname" | sed s/-[0-9]*$//)-headless + + cp /certs/CAs/* /certs/ + + cat >/certs/openssl.cnf < /certs/mongodb.pem + cd /certs/ + shopt -s extglob + rm -rf !(mongodb-ca-cert|mongodb.pem|CAs|openssl.cnf) + EOF + {{- end }} + containers: + - name: mongodb-arbiter + image: {{ include "mongodb.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.arbiter.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.arbiter.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.arbiter.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.arbiter.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: K8S_SERVICE_NAME + value: "{{ include "mongodb.fullname" . }}-arbiter-headless" + - name: MONGODB_REPLICA_SET_MODE + value: "arbiter" + - name: MONGODB_INITIAL_PRIMARY_HOST + value: "{{ include "mongodb.fullname" . }}-0.{{ include "mongodb.fullname" . }}-headless.$(MY_POD_NAMESPACE).svc.{{ .Values.clusterDomain }}" + - name: MONGODB_REPLICA_SET_NAME + value: {{ .Values.replicaSetName | quote }} + - name: MONGODB_ADVERTISED_HOSTNAME + value: "$(MY_POD_NAME).$(K8S_SERVICE_NAME).$(MY_POD_NAMESPACE).svc.{{ .Values.clusterDomain }}" + {{- if .Values.auth.enabled }} + - name: MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-root-password + - name: MONGODB_REPLICA_SET_KEY + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-replica-set-key + {{- end }} + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + {{- if and .Values.tls.enabled .Values.arbiter.enabled }} + - name: MONGODB_EXTRA_FLAGS + value: --tlsMode=requireTLS --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.arbiter.extraFlags }} + - name: MONGODB_EXTRA_FLAGS + value: {{ .Values.arbiter.extraFlags | join " " | quote }} + {{- end }} + {{- if and .Values.tls.enabled .Values.arbiter.enabled }} + - name: MONGODB_CLIENT_EXTRA_FLAGS + value: --tls --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.arbiter.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.arbiter.extraEnvVarsCM .Values.arbiter.extraEnvVarsSecret }} + envFrom: + {{- if .Values.arbiter.extraEnvVarsCM }} + - configMapRef: + name: {{ tpl .Values.arbiter.extraEnvVarsCM . | quote }} + {{- end }} + {{- if .Values.arbiter.extraEnvVarsSecret }} + - secretRef: + name: {{ tpl .Values.arbiter.extraEnvVarsSecret . | quote }} + {{- end }} + {{- end }} + ports: + - containerPort: 27017 + name: mongodb + {{- if .Values.arbiter.livenessProbe.enabled }} + livenessProbe: + tcpSocket: + port: mongodb + initialDelaySeconds: {{ .Values.arbiter.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.arbiter.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.arbiter.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.arbiter.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.arbiter.livenessProbe.failureThreshold }} + {{- else if .Values.arbiter.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.arbiter.readinessProbe.enabled }} + readinessProbe: + tcpSocket: + port: mongodb + initialDelaySeconds: {{ .Values.arbiter.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.arbiter.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.arbiter.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.arbiter.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.arbiter.readinessProbe.failureThreshold }} + {{- else if .Values.arbiter.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.arbiter.resources }} + resources: {{- toYaml .Values.arbiter.resources | nindent 12 }} + {{- end }} + {{- if or .Values.arbiter.configuration .Values.arbiter.existingConfigmap .Values.arbiter.extraVolumeMounts .Values.tls.enabled }} + volumeMounts: + {{- if or .Values.arbiter.configuration .Values.arbiter.existingConfigmap }} + - name: config + mountPath: /opt/bitnami/mongodb/conf/mongodb.conf + subPath: mongodb.conf + {{- end }} + {{- if and .Values.tls.enabled .Values.arbiter.enabled }} + - name: certs + mountPath: /certs + {{- end }} + {{- if .Values.arbiter.extraVolumeMounts }} + {{- toYaml .Values.arbiter.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.arbiter.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.arbiter.sidecars "context" $) | nindent 8 }} + {{- end }} + {{- if or .Values.arbiter.configuration .Values.arbiter.existingConfigmap .Values.arbiter.extraVolumes .Values.tls.enabled }} + volumes: + {{- if or .Values.arbiter.configuration .Values.arbiter.existingConfigmap }} + - name: config + configMap: + name: {{ include "mongodb.arbiter.configmapName" . }} + {{- end }} + {{- if and .Values.tls.enabled .Values.arbiter.enabled }} + - name: certs + emptyDir: {} + - name: certs-volume + secret: + secretName: {{ template "mongodb.caSecretName" . }} + items: + - key: mongodb-ca-cert + path: mongodb-ca-cert + mode: 511 + - key: mongodb-ca-key + path: mongodb-ca-key + mode: 511 + {{- end }} + {{- if .Values.arbiter.extraVolumes }} + {{- toYaml .Values.arbiter.extraVolumes | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/configmap.yaml b/dependency_charts/mongodb/templates/configmap.yaml new file mode 100644 index 0000000..158c101 --- /dev/null +++ b/dependency_charts/mongodb/templates/configmap.yaml @@ -0,0 +1,12 @@ +{{- if (include "mongodb.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +data: + mongodb.conf: |- + {{- include "common.tplvalues.render" (dict "value" .Values.configuration "context" $) | nindent 4 }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/initialization-configmap.yaml b/dependency_charts/mongodb/templates/initialization-configmap.yaml new file mode 100644 index 0000000..245aceb --- /dev/null +++ b/dependency_charts/mongodb/templates/initialization-configmap.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.initdbScripts (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mongodb.fullname" . }}-init-scripts + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +data: +{{- include "common.tplvalues.render" (dict "value" .Values.initdbScripts "context" .) | nindent 2 }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/metrics-svc.yaml b/dependency_charts/mongodb/templates/metrics-svc.yaml new file mode 100644 index 0000000..bbe7ea4 --- /dev/null +++ b/dependency_charts/mongodb/templates/metrics-svc.yaml @@ -0,0 +1,21 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mongodb.fullname" . }}-metrics + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.metrics.service.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + ports: + - port: {{ .Values.metrics.service.port }} + targetPort: metrics + protocol: TCP + name: http-metrics + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} + app.kubernetes.io/component: mongodb +{{- end }} diff --git a/dependency_charts/mongodb/templates/prometheusrule.yaml b/dependency_charts/mongodb/templates/prometheusrule.yaml new file mode 100644 index 0000000..eaf293c --- /dev/null +++ b/dependency_charts/mongodb/templates/prometheusrule.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.prometheusRule.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.metrics.prometheusRule.additionalLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.prometheusRule.additionalLabels "context" $) | nindent 4 }} + {{- end }} +spec: + groups: + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.prometheusRule.rules "context" $) | nindent 2 }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/replicaset/external-access-svc.yaml b/dependency_charts/mongodb/templates/replicaset/external-access-svc.yaml new file mode 100644 index 0000000..0e1fea1 --- /dev/null +++ b/dependency_charts/mongodb/templates/replicaset/external-access-svc.yaml @@ -0,0 +1,46 @@ +{{- if and (eq .Values.architecture "replicaset") .Values.externalAccess.enabled }} +{{- $fullName := include "mongodb.fullname" . }} +{{- $replicaCount := .Values.replicaCount | int }} +{{- $root := . }} + +{{- range $i, $e := until $replicaCount }} +{{- $targetPod := printf "%s-%d" (printf "%s" $fullName) $i }} +{{- $_ := set $ "targetPod" $targetPod }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }}-{{ $i }}-external + namespace: {{ include "mongodb.namespace" $ }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + pod: {{ $targetPod }} + {{- if $root.Values.externalAccess.service.annotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $root.Values.externalAccess.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ $root.Values.externalAccess.service.type }} + {{- if eq $root.Values.externalAccess.service.type "LoadBalancer" }} + {{- if not (empty $root.Values.externalAccess.service.loadBalancerIPs) }} + loadBalancerIP: {{ index $root.Values.externalAccess.service.loadBalancerIPs $i }} + {{- end }} + {{- if $root.Values.externalAccess.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml $root.Values.externalAccess.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + {{- end }} + publishNotReadyAddresses: true + ports: + - name: {{ $root.Values.service.portName }} + port: {{ $root.Values.externalAccess.service.port }} + {{- if not (empty $root.Values.externalAccess.service.nodePorts) }} + nodePort: {{ index $root.Values.externalAccess.service.nodePorts $i }} + {{- else }} + nodePort: null + {{- end }} + targetPort: mongodb + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} + app.kubernetes.io/component: mongodb + statefulset.kubernetes.io/pod-name: {{ $targetPod }} +--- +{{- end }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/replicaset/headless-svc.yaml b/dependency_charts/mongodb/templates/replicaset/headless-svc.yaml new file mode 100644 index 0000000..39c4874 --- /dev/null +++ b/dependency_charts/mongodb/templates/replicaset/headless-svc.yaml @@ -0,0 +1,22 @@ +{{- if eq .Values.architecture "replicaset" }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mongodb.fullname" . }}-headless + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb + {{- if .Values.service.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: {{ .Values.service.portName }} + port: {{ .Values.service.port }} + targetPort: mongodb + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: mongodb +{{- end }} diff --git a/dependency_charts/mongodb/templates/replicaset/pdb.yaml b/dependency_charts/mongodb/templates/replicaset/pdb.yaml new file mode 100644 index 0000000..cab61ba --- /dev/null +++ b/dependency_charts/mongodb/templates/replicaset/pdb.yaml @@ -0,0 +1,19 @@ +{{- if and (eq .Values.architecture "replicaset") .Values.pdb.create }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +spec: + {{- if .Values.pdb.minAvailable }} + minAvailable: {{ .Values.pdb.minAvailable }} + {{- end }} + {{- if .Values.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: {{ include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: mongodb +{{- end }} diff --git a/dependency_charts/mongodb/templates/replicaset/scripts-configmap.yaml b/dependency_charts/mongodb/templates/replicaset/scripts-configmap.yaml new file mode 100644 index 0000000..c16a3fe --- /dev/null +++ b/dependency_charts/mongodb/templates/replicaset/scripts-configmap.yaml @@ -0,0 +1,88 @@ +{{- if eq .Values.architecture "replicaset" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "mongodb.fullname" . }}-scripts + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +data: + {{- $fullname := include "mongodb.fullname" . }} + {{- $releaseNamespace := include "mongodb.namespace" . }} + {{- if and .Values.externalAccess.autoDiscovery.enabled (eq .Values.externalAccess.service.type "LoadBalancer") }} + auto-discovery.sh: |- + #!/bin/bash + + SVC_NAME="${MY_POD_NAME}-external" + + # Auxiliary functions + retry_while() { + local -r cmd="${1:?cmd is missing}" + local -r retries="${2:-12}" + local -r sleep_time="${3:-5}" + local return_value=1 + + read -r -a command <<< "$cmd" + for ((i = 1 ; i <= retries ; i+=1 )); do + "${command[@]}" && return_value=0 && break + sleep "$sleep_time" + done + return $return_value + } + k8s_svc_lb_ip() { + local namespace=${1:?namespace is missing} + local service=${2:?service is missing} + local service_ip=$(kubectl get svc "$service" -n "$namespace" -o jsonpath="{.status.loadBalancer.ingress[0].ip}") + local service_hostname=$(kubectl get svc "$service" -n "$namespace" -o jsonpath="{.status.loadBalancer.ingress[0].hostname}") + + if [[ -n ${service_ip} ]]; then + echo "${service_ip}" + else + echo "${service_hostname}" + fi + } + k8s_svc_lb_ip_ready() { + local namespace=${1:?namespace is missing} + local service=${2:?service is missing} + [[ -n "$(k8s_svc_lb_ip "$namespace" "$service")" ]] + } + # Wait until LoadBalancer IP is ready + retry_while "k8s_svc_lb_ip_ready {{ $releaseNamespace }} $SVC_NAME" || exit 1 + # Obtain LoadBalancer external IP + k8s_svc_lb_ip "{{ $releaseNamespace }}" "$SVC_NAME" | tee "$SHARED_FILE" + {{- end }} + setup.sh: |- + #!/bin/bash + + {{- if .Values.externalAccess.enabled }} + {{- if eq .Values.externalAccess.service.type "LoadBalancer" }} + {{- if .Values.externalAccess.autoDiscovery.enabled }} + export MONGODB_ADVERTISED_HOSTNAME="$(<${SHARED_FILE})" + {{- else }} + ID="${MY_POD_NAME#"{{ $fullname }}-"}" + export MONGODB_ADVERTISED_HOSTNAME=$(echo '{{ .Values.externalAccess.service.loadBalancerIPs }}' | tr -d '[]' | cut -d ' ' -f "$(($ID + 1))") + {{- end }} + {{- else if eq .Values.externalAccess.service.type "NodePort" }} + {{- if .Values.externalAccess.service.domain }} + export MONGODB_ADVERTISED_HOSTNAME={{ .Values.externalAccess.service.domain }} + {{- else }} + export MONGODB_ADVERTISED_HOSTNAME=$(curl -s https://ipinfo.io/ip) + {{- end }} + {{- end }} + {{- end }} + + echo "Advertised Hostname: $MONGODB_ADVERTISED_HOSTNAME" + + if [[ "$MY_POD_NAME" = "{{ $fullname }}-0" ]]; then + echo "Pod name matches initial primary pod name, configuring node as a primary" + export MONGODB_REPLICA_SET_MODE="primary" + else + echo "Pod name doesn't match initial primary pod name, configuring node as a secondary" + export MONGODB_REPLICA_SET_MODE="secondary" + export MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD="$MONGODB_ROOT_PASSWORD" + export MONGODB_INITIAL_PRIMARY_PORT_NUMBER="$MONGODB_PORT_NUMBER" + export MONGODB_ROOT_PASSWORD="" MONGODB_USERNAME="" MONGODB_DATABASE="" MONGODB_PASSWORD="" + fi + + exec /opt/bitnami/scripts/mongodb/entrypoint.sh /opt/bitnami/scripts/mongodb/run.sh +{{- end }} diff --git a/dependency_charts/mongodb/templates/replicaset/statefulset.yaml b/dependency_charts/mongodb/templates/replicaset/statefulset.yaml new file mode 100644 index 0000000..91cccb8 --- /dev/null +++ b/dependency_charts/mongodb/templates/replicaset/statefulset.yaml @@ -0,0 +1,478 @@ +{{- if eq .Values.architecture "replicaset" }} +{{- $replicaCount := int .Values.replicaCount }} +{{- $loadBalancerIPListLength := len .Values.externalAccess.service.loadBalancerIPs }} +{{- if not (and .Values.externalAccess.enabled (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $loadBalancerIPListLength )) (eq .Values.externalAccess.service.type "LoadBalancer")) }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb + {{- if .Values.labels }} + {{- include "common.tplvalues.render" (dict "value" .Values.labels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + serviceName: {{ include "mongodb.fullname" . }}-headless + podManagementPolicy: {{ .Values.podManagementPolicy }} + replicas: {{ .Values.replicaCount }} + updateStrategy: + type: {{ .Values.strategyType }} + {{- if (eq "OnDelete" .Values.strategyType) }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: mongodb + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: mongodb + {{- if .Values.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }} + {{- end }} + {{- if or (include "mongodb.createConfigmap" .) .Values.podAnnotations }} + annotations: + {{- if (include "mongodb.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- end }} + {{- end }} + spec: + {{- include "mongodb.imagePullSecrets" . | nindent 6 }} + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} + serviceAccountName: {{ template "mongodb.serviceAccountName" . }} + {{- if .Values.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "mongodb" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "mongodb" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if or .Values.initContainers (and .Values.volumePermissions.enabled .Values.persistence.enabled) (and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled) .Values.tls.enabled }} + initContainers: + {{- if .Values.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} + - name: volume-permissions + image: {{ include "mongodb.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + args: + - | + mkdir -p {{ .Values.persistence.mountPath }} + {{- if and .Values.podSecurityContext.enabled .Values.containerSecurityContext.enabled }} + chown -R "{{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }}" "{{ .Values.persistence.mountPath }}" + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: datadir + mountPath: {{ .Values.persistence.mountPath }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: generate-tls-certs + image: {{ include "mongodb.tls.image" . }} + imagePullPolicy: {{ .Values.tls.image.pullPolicy | quote }} + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: certs-volume + mountPath: /certs/CAs + - name: certs + mountPath: /certs + command: + - sh + - "-c" + - | + /bin/bash <<'EOF' + my_hostname=$(hostname) + svc=$(echo -n "$my_hostname" | sed s/-[0-9]*$//)-headless + cp /certs/CAs/* /certs/ + cat >/certs/openssl.cnf < /certs/mongodb.pem + cd /certs/ + shopt -s extglob + rm -rf !(mongodb-ca-cert|mongodb.pem|CAs|openssl.cnf) + EOF + {{- end }} + {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (eq .Values.externalAccess.service.type "LoadBalancer") }} + - name: auto-discovery + image: {{ include "mongodb.externalAccess.autoDiscovery.image" . }} + imagePullPolicy: {{ .Values.externalAccess.autoDiscovery.image.pullPolicy | quote }} + command: + - /scripts/auto-discovery.sh + env: + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SHARED_FILE + value: "/shared/info.txt" + {{- if .Values.externalAccess.autoDiscovery.resources }} + resources: {{- toYaml .Values.externalAccess.autoDiscovery.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: shared + mountPath: /shared + - name: scripts + mountPath: /scripts/auto-discovery.sh + subPath: auto-discovery.sh + {{- end }} + {{- end }} + containers: + - name: mongodb + image: {{ include "mongodb.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- else }} + command: + - /scripts/setup.sh + {{- end }} + {{- if .Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (eq .Values.externalAccess.service.type "LoadBalancer") }} + - name: SHARED_FILE + value: "/shared/info.txt" + {{- end }} + - name: MY_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: K8S_SERVICE_NAME + value: "{{ include "mongodb.fullname" . }}-headless" + - name: MONGODB_INITIAL_PRIMARY_HOST + value: "{{ include "mongodb.fullname" . }}-0.$(K8S_SERVICE_NAME).$(MY_POD_NAMESPACE).svc.{{ .Values.clusterDomain }}" + - name: MONGODB_REPLICA_SET_NAME + value: {{ .Values.replicaSetName | quote }} + {{- if and .Values.replicaSetHostnames (not .Values.externalAccess.enabled) }} + - name: MONGODB_ADVERTISED_HOSTNAME + value: "$(MY_POD_NAME).$(K8S_SERVICE_NAME).$(MY_POD_NAMESPACE).svc.{{ .Values.clusterDomain }}" + {{- end }} + {{- if .Values.auth.username }} + - name: MONGODB_USERNAME + value: {{ .Values.auth.username | quote }} + {{- end }} + {{- if .Values.auth.database }} + - name: MONGODB_DATABASE + value: {{ .Values.auth.database | quote }} + {{- end }} + {{- if .Values.auth.enabled }} + {{- if and .Values.auth.username .Values.auth.database }} + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-password + {{- end }} + - name: MONGODB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-root-password + - name: MONGODB_REPLICA_SET_KEY + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-replica-set-key + {{- end }} + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + - name: MONGODB_SYSTEM_LOG_VERBOSITY + value: {{ .Values.systemLogVerbosity | quote }} + - name: MONGODB_DISABLE_SYSTEM_LOG + value: {{ ternary "yes" "no" .Values.disableSystemLog | quote }} + - name: MONGODB_ENABLE_IPV6 + value: {{ ternary "yes" "no" .Values.enableIPv6 | quote }} + - name: MONGODB_ENABLE_DIRECTORY_PER_DB + value: {{ ternary "yes" "no" .Values.directoryPerDB | quote }} + {{- if .Values.tls.enabled }} + - name: MONGODB_EXTRA_FLAGS + value: --tlsMode=requireTLS --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.extraFlags }} + - name: MONGODB_EXTRA_FLAGS + value: {{ .Values.extraFlags | join " " | quote }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: MONGODB_CLIENT_EXTRA_FLAGS + value: --tls --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }} + envFrom: + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ tpl .Values.extraEnvVarsCM . | quote }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ tpl .Values.extraEnvVarsSecret . | quote }} + {{- end }} + {{- end }} + ports: + - containerPort: 27017 + name: mongodb + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - mongo + {{- if .Values.tls.enabled }} + - --tls + - --tlsCertificateKeyFile=/certs/mongodb.pem + - --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - mongo + {{- if .Values.tls.enabled }} + - --tls + - --tlsCertificateKeyFile=/certs/mongodb.pem + - --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: datadir + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d + {{- end }} + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: config + mountPath: /opt/bitnami/mongodb/conf/mongodb.conf + subPath: mongodb.conf + {{- end }} + - name: scripts + mountPath: /scripts/setup.sh + subPath: setup.sh + {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (eq .Values.externalAccess.service.type "LoadBalancer") }} + - name: shared + mountPath: /shared + {{- end }} + {{- if .Values.tls.enabled }} + - name: certs + mountPath: /certs + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "mongodb.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + command: + - /bin/bash + - -ec + args: + - | + /bin/mongodb_exporter --mongodb.uri "{{ include "mongodb.mongodb_exporter.uri" . }}" {{ .Values.metrics.extraFlags }} + env: + {{- if .Values.auth.enabled }} + - name: MONGODB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-root-password + {{- end }} + volumeMounts: + {{- if .Values.tls.enabled }} + - name: certs + mountPath: /certs + {{- end }} + ports: + - name: metrics + containerPort: 9216 + {{- if .Values.metrics.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: metrics + initialDelaySeconds: {{ .Values.metrics.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.metrics.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.metrics.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.metrics.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: metrics + initialDelaySeconds: {{ .Values.metrics.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.metrics.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.metrics.readinessProbe.successThreshold }} + {{- end }} + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ template "mongodb.initdbScriptsCM" . }} + {{- end }} + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: config + configMap: + name: {{ include "mongodb.configmapName" . }} + {{- end }} + {{- if and .Values.externalAccess.enabled .Values.externalAccess.autoDiscovery.enabled (eq .Values.externalAccess.service.type "LoadBalancer") }} + - name: shared + emptyDir: {} + {{- end }} + - name: scripts + configMap: + name: {{ include "mongodb.fullname" . }}-scripts + defaultMode: 0755 + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: certs + emptyDir: {} + - name: certs-volume + secret: + secretName: {{ template "mongodb.caSecretName" . }} + items: + - key: mongodb-ca-cert + path: mongodb-ca-cert + mode: 511 + - key: mongodb-ca-key + path: mongodb-ca-key + mode: 511 + {{- end }} + {{- if not .Values.persistence.enabled }} + - name: datadir + emptyDir: {} + {{- else }} + volumeClaimTemplates: + - metadata: + name: datadir + {{- if .Values.persistence.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.volumeClaimTemplates.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.volumeClaimTemplates.selector "context" $) | nindent 10 }} + {{- end }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + {{- end }} +{{- end }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/role.yaml b/dependency_charts/mongodb/templates/role.yaml new file mode 100644 index 0000000..f8eda3d --- /dev/null +++ b/dependency_charts/mongodb/templates/role.yaml @@ -0,0 +1,17 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch +{{- end }} diff --git a/dependency_charts/mongodb/templates/rolebinding.yaml b/dependency_charts/mongodb/templates/rolebinding.yaml new file mode 100644 index 0000000..4e24df5 --- /dev/null +++ b/dependency_charts/mongodb/templates/rolebinding.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +roleRef: + kind: Role + name: {{ include "mongodb.fullname" . }} + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ include "mongodb.serviceAccountName" . }} + namespace: {{ include "mongodb.namespace" . }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/secrets-ca.yaml b/dependency_charts/mongodb/templates/secrets-ca.yaml new file mode 100644 index 0000000..c4de7b2 --- /dev/null +++ b/dependency_charts/mongodb/templates/secrets-ca.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.tls.enabled (not .Values.tls.existingSecret) -}} +{{- $cn := printf "%s.%s.svc.cluster.local" ( include "mongodb.fullname" . ) .Release.Namespace }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "mongodb.caSecretName" . }} + namespace: {{ template "mongodb.namespace" . }} + annotations: + "helm.sh/hook": "pre-install" + labels: + {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +type: Opaque +data: + {{ if and .Values.tls.caCert .Values.tls.caKey }} + {{- $ca := buildCustomCert .Values.tls.caCert .Values.tls.caKey -}} + {{- $cert := genSignedCert $cn nil nil 3650 $ca -}} + {{- $pem := printf "%s%s" $cert.Cert $cert.Key -}} + mongodb-ca-cert: {{ b64enc $ca.Cert }} + mongodb-ca-key: {{ b64enc $ca.Key }} + client-pem: {{ b64enc $pem }} + {{- else -}} + {{- $ca:= genCA "myMongo-ca" 3650 -}} + {{- $cert := genSignedCert $cn nil nil 3650 $ca -}} + {{- $pem := printf "%s%s" $cert.Cert $cert.Key -}} + mongodb-ca-cert: {{ b64enc $ca.Cert }} + mongodb-ca-key: {{ b64enc $ca.Key }} + client-pem: {{ b64enc $pem }} + {{- end -}} +{{- end -}} diff --git a/dependency_charts/mongodb/templates/secrets.yaml b/dependency_charts/mongodb/templates/secrets.yaml new file mode 100644 index 0000000..d78e31d --- /dev/null +++ b/dependency_charts/mongodb/templates/secrets.yaml @@ -0,0 +1,30 @@ +{{- if (include "mongodb.createSecret" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ template "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +type: Opaque +data: + {{- if .Values.auth.rootPassword }} + mongodb-root-password: {{ .Values.auth.rootPassword | toString | b64enc | quote }} + {{- else }} + mongodb-root-password: {{ randAlphaNum 10 | b64enc | quote }} + {{- end }} + {{- if and .Values.auth.username .Values.auth.database }} + {{- if .Values.auth.password }} + mongodb-password: {{ .Values.auth.password | toString | b64enc | quote }} + {{- else }} + mongodb-password: {{ randAlphaNum 10 | b64enc | quote }} + {{- end }} + {{- end }} + {{- if eq .Values.architecture "replicaset" }} + {{- if .Values.auth.replicaSetKey }} + mongodb-replica-set-key: {{ .Values.auth.replicaSetKey | toString | b64enc | quote }} + {{- else }} + mongodb-replica-set-key: {{ randAlphaNum 10 | b64enc | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/serviceaccount.yaml b/dependency_charts/mongodb/templates/serviceaccount.yaml new file mode 100644 index 0000000..bac0a86 --- /dev/null +++ b/dependency_charts/mongodb/templates/serviceaccount.yaml @@ -0,0 +1,10 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "mongodb.serviceAccountName" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} +secrets: + - name: {{ template "mongodb.fullname" . }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/servicemonitor.yaml b/dependency_charts/mongodb/templates/servicemonitor.yaml new file mode 100644 index 0000000..5dae1cc --- /dev/null +++ b/dependency_charts/mongodb/templates/servicemonitor.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.serviceMonitor.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.metrics.serviceMonitor.additionalLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.additionalLabels "context" $) | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: http-metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - "{{ include "mongodb.namespace" . }}" + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: metrics +{{- end }} diff --git a/dependency_charts/mongodb/templates/standalone/dep-sts.yaml b/dependency_charts/mongodb/templates/standalone/dep-sts.yaml new file mode 100644 index 0000000..b483196 --- /dev/null +++ b/dependency_charts/mongodb/templates/standalone/dep-sts.yaml @@ -0,0 +1,411 @@ +{{- if not (eq .Values.architecture "replicaset") }} +apiVersion: {{ if .Values.useStatefulSet }}{{ include "common.capabilities.statefulset.apiVersion" . }}{{- else }}{{ include "common.capabilities.deployment.apiVersion" . }}{{- end }} +kind: {{ if .Values.useStatefulSet }}StatefulSet{{- else }}Deployment{{- end }} +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb + {{- if .Values.labels }} + {{- include "common.tplvalues.render" (dict "value" .Values.labels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.useStatefulSet }} + serviceName: {{ include "mongodb.fullname" . }} + updateStrategy: + {{- else }} + strategy: + {{- end }} + type: {{ .Values.strategyType }} + {{- if or (and (not .Values.useStatefulSet) (eq "Recreate" .Values.strategyType)) (and .Values.useStatefulSet (eq "OnDelete" .Values.strategyType)) }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: mongodb + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: mongodb + {{- if .Values.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.podLabels "context" $) | nindent 8 }} + {{- end }} + {{- if or (include "mongodb.createConfigmap" .) .Values.podAnnotations }} + annotations: + {{- if (include "mongodb.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- end }} + {{- end }} + spec: + {{- include "mongodb.imagePullSecrets" . | nindent 6 }} + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} + serviceAccountName: {{ template "mongodb.serviceAccountName" . }} + {{- if .Values.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "component" "mongodb" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "component" "mongodb" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + {{- if or .Values.initContainers (and .Values.volumePermissions.enabled .Values.persistence.enabled) .Values.tls.enabled }} + initContainers: + {{- if .Values.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if and .Values.volumePermissions.enabled .Values.persistence.enabled }} + - name: volume-permissions + image: {{ include "mongodb.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + args: + - | + mkdir -p {{ .Values.persistence.mountPath }} + {{- if and .Values.podSecurityContext.enabled .Values.containerSecurityContext.enabled }} + chown -R "{{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }}" "{{ .Values.persistence.mountPath }}" + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.securityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.securityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: datadir + mountPath: {{ .Values.persistence.mountPath }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: generate-tls-certs + image: {{ include "mongodb.tls.image" . }} + imagePullPolicy: {{ .Values.tls.image.pullPolicy | quote }} + env: + - name: MY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumeMounts: + - name: certs-volume + mountPath: /certs/CAs + - name: certs + mountPath: /certs + command: + - sh + - "-c" + - | + /bin/bash <<'EOF' + my_hostname=$(hostname) + svc=$(echo -n "$my_hostname" | sed s/-[0-9]*$//)-headless + cp /certs/CAs/* /certs/ + cat >/certs/openssl.cnf < /certs/mongodb.pem + cd /certs/ + shopt -s extglob + rm -rf !(mongodb-ca-cert|mongodb.pem|CAs|openssl.cnf) + EOF + {{- end }} + {{- end }} + containers: + - name: mongodb + image: {{ include "mongodb.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + {{- if .Values.auth.username }} + - name: MONGODB_USERNAME + value: {{ .Values.auth.username | quote }} + {{- end }} + {{- if .Values.auth.database }} + - name: MONGODB_DATABASE + value: {{ .Values.auth.database | quote }} + {{- end }} + {{- if .Values.auth.enabled }} + {{- if and .Values.auth.username .Values.auth.database }} + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-password + {{- end }} + - name: MONGODB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-root-password + {{- end }} + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + - name: MONGODB_SYSTEM_LOG_VERBOSITY + value: {{ .Values.systemLogVerbosity | quote }} + - name: MONGODB_DISABLE_SYSTEM_LOG + value: {{ ternary "yes" "no" .Values.disableSystemLog | quote }} + - name: MONGODB_ENABLE_IPV6 + value: {{ ternary "yes" "no" .Values.enableIPv6 | quote }} + - name: MONGODB_ENABLE_DIRECTORY_PER_DB + value: {{ ternary "yes" "no" .Values.directoryPerDB | quote }} + {{- if .Values.tls.enabled }} + - name: MONGODB_EXTRA_FLAGS + value: --tlsMode=requireTLS --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.extraFlags }} + - name: MONGODB_EXTRA_FLAGS + value: {{ .Values.extraFlags | join " " | quote }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: MONGODB_CLIENT_EXTRA_FLAGS + value: --tls --tlsCertificateKeyFile=/certs/mongodb.pem --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + {{- if .Values.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.extraEnvVarsCM .Values.extraEnvVarsSecret }} + envFrom: + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ tpl .Values.extraEnvVarsCM . | quote }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ tpl .Values.extraEnvVarsSecret . | quote }} + {{- end }} + {{- end }} + ports: + - name: mongodb + containerPort: 27017 + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - mongo + {{- if .Values.tls.enabled }} + - --tls + - --tlsCertificateKeyFile=/certs/mongodb.pem + - --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- else if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + exec: + command: + - mongo + {{- if .Values.tls.enabled }} + - --tls + - --tlsCertificateKeyFile=/certs/mongodb.pem + - --tlsCAFile=/certs/mongodb-ca-cert + {{- end }} + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- else if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: datadir + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d + {{- end }} + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: config + mountPath: /opt/bitnami/mongodb/conf/mongodb.conf + subPath: mongodb.conf + {{- end }} + {{- if .Values.tls.enabled }} + - name: certs + mountPath: /certs + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "mongodb.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + command: + - /bin/bash + - -ec + args: + - | + /bin/mongodb_exporter --mongodb.uri "{{ include "mongodb.mongodb_exporter.uri" . }}" {{ .Values.metrics.extraFlags }} + env: + {{- if .Values.auth.enabled }} + - name: MONGODB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "mongodb.secretName" . }} + key: mongodb-root-password + {{- end }} + volumeMounts: + {{- if .Values.tls.enabled }} + - name: certs + mountPath: /certs + {{- end }} + ports: + - name: metrics + containerPort: 9216 + {{- if .Values.metrics.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: metrics + initialDelaySeconds: {{ .Values.metrics.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.metrics.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.metrics.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.metrics.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: metrics + initialDelaySeconds: {{ .Values.metrics.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.metrics.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.metrics.readinessProbe.successThreshold }} + {{- end }} + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ template "mongodb.initdbScriptsCM" . }} + {{- end }} + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: config + configMap: + name: {{ include "mongodb.configmapName" . }} + {{- end }} + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: certs + emptyDir: {} + - name: certs-volume + secret: + secretName: {{ template "mongodb.caSecretName" . }} + items: + - key: mongodb-ca-cert + path: mongodb-ca-cert + mode: 511 + - key: mongodb-ca-key + path: mongodb-ca-key + mode: 511 + {{- end }} + {{- if not .Values.persistence.enabled }} + - name: datadir + emptyDir: {} + {{- else if .Values.persistence.existingClaim }} + - name: datadir + persistentVolumeClaim: + claimName: {{ printf "%s" (tpl .Values.persistence.existingClaim .) }} + {{- else if not .Values.useStatefulSet }} + - name: datadir + persistentVolumeClaim: + claimName: {{ template "mongodb.fullname" . }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: datadir + {{- if .Values.persistence.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.annotations "context" $) | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.volumeClaimTemplates.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.volumeClaimTemplates.selector "context" $) | nindent 10 }} + {{- end }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} + {{- end }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/standalone/pvc.yaml b/dependency_charts/mongodb/templates/standalone/pvc.yaml new file mode 100644 index 0000000..70e161c --- /dev/null +++ b/dependency_charts/mongodb/templates/standalone/pvc.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) (not (eq .Values.architecture "replicaset")) (not .Values.useStatefulSet) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb +spec: + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{ include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) }} +{{- end }} diff --git a/dependency_charts/mongodb/templates/standalone/svc.yaml b/dependency_charts/mongodb/templates/standalone/svc.yaml new file mode 100644 index 0000000..b421c3c --- /dev/null +++ b/dependency_charts/mongodb/templates/standalone/svc.yaml @@ -0,0 +1,37 @@ +{{- if not (eq .Values.architecture "replicaset") }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mongodb.fullname" . }} + namespace: {{ include "mongodb.namespace" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: mongodb + {{- if .Values.service.annotations }} + annotations: {{- include "common.tplvalues.render" (dict "value" .Values.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if and (eq .Values.service.type "ClusterIP") .Values.service.clusterIP }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.externalIPs }} + externalIPs: {{ toYaml .Values.service.externalIPs | nindent 4 }} + {{- end }} + {{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - name: {{ .Values.service.portName }} + port: {{ .Values.service.port }} + targetPort: mongodb + {{- if and (or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort")) .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: mongodb +{{- end }} diff --git a/dependency_charts/mongodb/values-production.yaml b/dependency_charts/mongodb/values-production.yaml new file mode 100644 index 0000000..a647f26 --- /dev/null +++ b/dependency_charts/mongodb/values-production.yaml @@ -0,0 +1,1014 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass +## Override the namespace for resource deployed by the chart, but can itself be overridden by the local namespaceOverride +# namespaceOverride: my-global-namespace + +image: + ## Bitnami MongoDB registry + ## + registry: docker.io + ## Bitnami MongoDB image name + ## + repository: bitnami/mongodb + ## Bitnami MongoDB image tag + ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/ + ## + tag: 4.4.3-debian-10-r0 + ## Specify a imagePullPolicy + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Set to true if you would like to see extra information on logs + ## It turns on Bitnami debugging in minideb-extras-base + ## ref: https://github.com/bitnami/minideb-extras-base + debug: false + +## String to partially override mongodb.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override mongodb.fullname template +## +# fullnameOverride: + +## Kubernetes Cluster Domain +## +clusterDomain: cluster.local + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## MongoDB architecture. Allowed values: standalone or replicaset +## +architecture: replicaset + +## Use StatefulSet instead of Deployment when deploying standalone +## +useStatefulSet: false + +## MongoDB Authentication parameters +## +auth: + ## Enable authentication + ## ref: https://docs.mongodb.com/manual/tutorial/enable-authentication/ + ## + enabled: true + ## MongoDB root password + ## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#setting-the-root-password-on-first-run + ## + rootPassword: "" + ## MongoDB custom user and database + ## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#creating-a-user-and-database-on-first-run + ## + # username: username + # password: password + # database: database + ## Key used for replica set authentication + ## Ignored when mongodb.architecture=standalone + ## + replicaSetKey: "" + + ## Existing secret with MongoDB credentials + ## NOTE: When it's set the previous parameters are ignored. + ## + # existingSecret: name-of-existing-secret + +tls: + ## Enable or disable MongoDB TLS Support + enabled: false + + ## Existing secret with MongoDB TLS certificates + ## NOTE: When it's set it will disable certificate creation + ## + # existingSecret: name-of-existing-secret + + ## Add Custom CA certificate + # caCert: base64 encoded ca certificate + # caKey: base64 encoded private key + ## + ## Bitnami Nginx image + ## + image: + registry: docker.io + repository: bitnami/nginx + tag: 1.19.6-debian-10-r6 + pullPolicy: IfNotPresent + +## Name of the replica set +## Ignored when mongodb.architecture=standalone +## +replicaSetName: rs0 + +## Enable DNS hostnames in the replica set config +## Ignored when mongodb.architecture=standalone +## Ignored when externalAccess.enabled=true +## +replicaSetHostnames: true + +## Whether enable/disable IPv6 on MongoDB +## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#enabling/disabling-ipv6 +## +enableIPv6: false + +## Whether enable/disable DirectoryPerDB on MongoDB +## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#enabling/disabling-directoryperdb +## +directoryPerDB: false + +## MongoDB System Log configuration +## ref: https://github.com/bitnami/bitnami-docker-mongodb#configuring-system-log-verbosity-level +## +systemLogVerbosity: 0 +disableSystemLog: false + +## MongoDB configuration file for Primary and Secondary nodes. For documentation of all options, see: +## http://docs.mongodb.org/manual/reference/configuration-options/ +## Example: +## configuration: |- +## # where and how to store data. +## storage: +## dbPath: /bitnami/mongodb/data/db +## journal: +## enabled: true +## directoryPerDB: false +## # where to write logging data +## systemLog: +## destination: file +## quiet: false +## logAppend: true +## logRotate: reopen +## path: /opt/bitnami/mongodb/logs/mongodb.log +## verbosity: 0 +## # network interfaces +## net: +## port: 27017 +## unixDomainSocket: +## enabled: true +## pathPrefix: /opt/bitnami/mongodb/tmp +## ipv6: false +## bindIpAll: true +## # replica set options +## #replication: +## #replSetName: replicaset +## #enableMajorityReadConcern: true +## # process management options +## processManagement: +## fork: false +## pidFilePath: /opt/bitnami/mongodb/tmp/mongodb.pid +## # set parameter options +## setParameter: +## enableLocalhostAuthBypass: true +## # security options +## security: +## authorization: disabled +## #keyFile: /opt/bitnami/mongodb/conf/keyfile +## +configuration: "" + +## ConfigMap with MongoDB configuration for Primary and Secondary nodes +## NOTE: When it's set the arbiter.configuration parameter is ignored +## +# existingConfigmap: + +## initdb scripts +## Specify dictionary of scripts to be run at first boot +## Example: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/bash +## echo "Do something." +initdbScripts: {} + +## Existing ConfigMap with custom init scripts +## +# initdbScriptsConfigMap: + +## Command and args for running the container (set to default if not set). Use array form +## +# command: +# args: + +## Additional command line flags +## Example: +## extraFlags: +## - "--wiredTigerCacheSizeGB=2" +## +extraFlags: [] + +## Additional environment variables to set +## E.g: +## extraEnvVars: +## - name: FOO +## value: BAR +## +extraEnvVars: [] + +## ConfigMap with extra environment variables +## +# extraEnvVarsCM: + +## Secret with extra environment variables +## +# extraEnvVarsSecret: + +## Annotations to be added to the MongoDB statefulset. Evaluated as a template. +## +annotations: {} + +## Additional labels to be added to the MongoDB statefulset. Evaluated as a template. +## +labels: {} + +## Number of MongoDB replicas to deploy. +## Ignored when mongodb.architecture=standalone +## +replicaCount: 4 + +## StrategyType for MongoDB statefulset +## It can be set to RollingUpdate or Recreate by default. +## +strategyType: RollingUpdate + +## MongoDB should be initialized one by one when building the replicaset for the first time. +## +podManagementPolicy: OrderedReady + +## Pod affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAffinityPreset: "" + +## Pod anti-affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAntiAffinityPreset: soft + +## Node affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## Allowed values: soft, hard +## +nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + +## Affinity for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set +## +affinity: {} + +## Node labels for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## Labels for MongoDB pods. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} + +## Annotations for MongoDB pods. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} + +## MongoDB pods' priority. +## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +# priorityClassName: "" + +## MongoDB pods' Security Context. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## +podSecurityContext: + enabled: true + fsGroup: 1001 + ## sysctl settings + ## Example: + ## sysctls: + ## - name: net.core.somaxconn + ## value: "10000" + ## + sysctls: [] + +## MongoDB containers' Security Context (main and metrics container). +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + +## MongoDB containers' resource requests and limits. +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + +## MongoDB pods' liveness and readiness probes. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes +## +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Custom Liveness probes for MongoDB pods +## +customLivenessProbe: {} + +## Custom Rediness probes MongoDB pods +## +customReadinessProbe: {} + +## Add init containers to the MongoDB pods. +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: {} + +## Add sidecars to the MongoDB pods. +## Example: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: {} + +## extraVolumes and extraVolumeMounts allows you to mount other volumes on MongoDB pods +## Examples: +## extraVolumeMounts: +## - name: extras +## mountPath: /usr/share/extras +## readOnly: true +## extraVolumes: +## - name: extras +## emptyDir: {} +extraVolumeMounts: [] +extraVolumes: [] + +## MongoDB Pod Disruption Budget configuration +## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +## +pdb: + create: true + ## Min number of pods that must still be available after the eviction + ## + minAvailable: 1 + ## Max number of pods that can be unavailable after the eviction + ## + # maxUnavailable: 1 + +## Enable persistence using Persistent Volume Claims +## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ +## +persistence: + enabled: true + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + ## Ignored when mongodb.architecture=replicaset + ## + # existingClaim: + ## PV Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + ## + # storageClass: "-" + ## PV Access Mode + ## + accessModes: + - ReadWriteOnce + ## PVC size + ## + size: 8Gi + ## PVC annotations + ## + annotations: {} + ## The path the volume will be mounted at, useful when using different + ## MongoDB images. + ## + mountPath: /bitnami/mongodb + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + ## + subPath: "" + ## Fine tuning for volumeClaimTemplates + volumeClaimTemplates: + ## A label query over volumes to consider for binding (e.g. when using local volumes) + ## See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#labelselector-v1-meta for more details + selector: + +## Service parameters +## +service: + ## Service type + ## + type: ClusterIP + ## MongoDB service port + ## + port: 27017 + ## MongoDB service port name + ## + portName: mongodb + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePort: "" + ## MongoDB service clusterIP IP + ## + # clusterIP: None + ## Specify the externalIP value ClusterIP service type. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips + ## + externalIPs: [] + ## Specify the loadBalancerIP value for LoadBalancer service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer + ## + # loadBalancerIP: + ## Specify the loadBalancerSourceRanges value for LoadBalancer service types. + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## + loadBalancerSourceRanges: [] + ## Provide any additional annotations which may be required. Evaluated as a template + ## + annotations: {} + +## External Access to MongoDB nodes configuration +## +externalAccess: + ## Enable Kubernetes external cluster access to MongoDB nodes + ## + enabled: false + ## External IPs auto-discovery configuration + ## An init container is used to auto-detect LB IPs or node ports by querying the K8s API + ## Note: RBAC might be required + ## + autoDiscovery: + ## Enable external IP/ports auto-discovery + ## + enabled: false + ## Bitnami Kubectl image + ## ref: https://hub.docker.com/r/bitnami/kubectl/tags/ + ## + image: + registry: docker.io + repository: bitnami/kubectl + tag: 1.18.13-debian-10-r12 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init Container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + ## Parameters to configure K8s service(s) used to externally access MongoDB + ## A new service per broker will be created + ## + service: + ## Service type. Allowed values: LoadBalancer or NodePort + ## + type: LoadBalancer + ## Port used when service type is LoadBalancer + ## + port: 27017 + ## Array of load balancer IPs for each MongoDB node. Length must be the same as replicaCount + ## Example: + ## loadBalancerIPs: + ## - X.X.X.X + ## - Y.Y.Y.Y + ## + loadBalancerIPs: [] + ## Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## Example: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## Array of node ports used for each MongoDB node. Length must be the same as replicaCount + ## Example: + ## nodePorts: + ## - 30001 + ## - 30002 + ## + nodePorts: [] + ## When service type is NodePort, you can specify the domain used for MongoDB advertised hostnames. + ## If not specified, the container will try to get the kubernetes node external IP + ## + # domain: mydomain.com + ## Provide any additional annotations which may be required. Evaluated as a template + ## + annotations: {} + +## +## MongoDB Arbiter parameters. +## +arbiter: + ## Enable deploying the MongoDB Arbiter + ## https://docs.mongodb.com/manual/tutorial/add-replica-set-arbiter/ + enabled: true + + ## MongoDB configuration file for the Arbiter. For documentation of all options, see: + ## http://docs.mongodb.org/manual/reference/configuration-options/ + ## + configuration: "" + + ## ConfigMap with MongoDB configuration for the Arbiter + ## NOTE: When it's set the arbiter.configuration parameter is ignored + ## + # existingConfigmap: + + ## Command and args for running the container (set to default if not set). Use array form + ## + # command: + # args: + + ## Additional command line flags + ## Example: + ## extraFlags: + ## - "--wiredTigerCacheSizeGB=2" + ## + extraFlags: [] + + ## Additional environment variables to set + ## E.g: + ## extraEnvVars: + ## - name: FOO + ## value: BAR + ## + extraEnvVars: [] + + ## ConfigMap with extra environment variables + ## + # extraEnvVarsCM: + + ## Secret with extra environment variables + ## + # extraEnvVarsSecret: + + ## Annotations to be added to the Arbiter statefulset. Evaluated as a template. + ## + annotations: {} + + ## Additional to be added to the Arbiter statefulset. Evaluated as a template. + ## + labels: {} + + ## Pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## Pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## Node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: arbiter.podAffinityPreset, arbiter.podAntiAffinityPreset, and arbiter.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + + ## Labels for MongoDB Arbiter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + + ## Annotations for MongoDB Arbiter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + + ## MongoDB Arbiter pods' priority. + ## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + ## + # priorityClassName: "" + + ## MongoDB Arbiter pods' Security Context. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## sysctl settings + ## Example: + ## sysctls: + ## - name: net.core.somaxconn + ## value: "10000" + ## + sysctls: [] + + ## MongoDB Arbiter containers' Security Context (only main container). + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + + ## MongoDB Arbiter containers' resource requests and limits. + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + + ## MongoDB Arbiter pods' liveness and readiness probes. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + ## Custom Liveness probes for MongoDB Arbiter pods + ## + customLivenessProbe: {} + + ## Custom Rediness probes MongoDB Arbiter pods + ## + customReadinessProbe: {} + + ## Add init containers to the MongoDB Arbiter pods. + ## Example: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + initContainers: {} + + ## Add sidecars to the MongoDB Arbiter pods. + ## Example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: {} + + ## extraVolumes and extraVolumeMounts allows you to mount other volumes on MongoDB Arbiter pods + ## Examples: + ## extraVolumeMounts: + ## - name: extras + ## mountPath: /usr/share/extras + ## readOnly: true + ## extraVolumes: + ## - name: extras + ## emptyDir: {} + extraVolumeMounts: [] + extraVolumes: [] + + ## MongoDB Arbiter Pod Disruption Budget configuration + ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + ## + pdb: + create: false + ## Min number of pods that must still be available after the eviction + ## + minAvailable: 1 + ## Max number of pods that can be unavailable after the eviction + ## + # maxUnavailable: 1 + +## ServiceAccount +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: true + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the rabbitmq.fullname template + ## + # name: + +## Role Based Access +## ref: https://kubernetes.io/docs/admin/authorization/rbac/ +## +rbac: + ## Specifies whether RBAC rules should be created + ## binding MongoDB ServiceAccount to a role + ## that allows MongoDB pods querying the K8s API + ## + create: false + +## Init Container parameters +## Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each component +## values from the securityContext section of the component +## +volumePermissions: + enabled: false + ## Bitnami Minideb image + ## ref: https://hub.docker.com/r/bitnami/minideb/tags/ + ## + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init Container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + ## Init container Security Context + ## Note: the chown of the data folder is done to containerSecurityContext.runAsUser + ## and not the below volumePermissions.securityContext.runAsUser + ## When runAsUser is set to special value "auto", init container will try to chwon the + ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). + ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with + ## podSecurityContext.enabled=false,containerSecurityContext.enabled=false and shmVolume.chmod.enabled=false + ## + securityContext: + runAsUser: 0 + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + ## Bitnami MongoDB Promtheus Exporter image + ## ref: https://hub.docker.com/r/bitnami/mongodb-exporter/tags/ + ## + image: + registry: docker.io + repository: bitnami/mongodb-exporter + tag: 0.20.1-debian-10-r18 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## String with extra flags to the metrics exporter + ## ref: https://github.com/percona/mongodb_exporter/blob/master/mongodb_exporter.go + ## + extraFlags: "" + + ## String with additional URI options to the metrics exporter + ## ref: https://docs.mongodb.com/manual/reference/connection-string + ## + extraUri: "" + + ## Metrics exporter container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + + ## Prometheus Exporter service configuration + ## + service: + ## Annotations for Prometheus Exporter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.metrics.service.port }}" + prometheus.io/path: "/metrics" + type: ClusterIP + port: 9216 + + ## Metrics exporter liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md + ## + serviceMonitor: + ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry + enabled: false + + ## Specify the namespace where Prometheus Operator is running + ## + # namespace: monitoring + + ## Specify the interval at which metrics should be scraped + ## + interval: 30s + ## Specify the timeout after which the scrape is ended + ## + # scrapeTimeout: 30s + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + ## + additionalLabels: {} + + ## Custom PrometheusRule to be defined + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + ## Specify the namespace where Prometheus Operator is running + ## + # namespace: monitoring + + ## Define individual alerting rules as required + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#rulegroup + ## https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + ## + ## This is an example of a rule, you should add the below code block under the "rules" param, removing the brackets + ## - name: example + ## rules: + ## - alert: HighRequestLatency + ## expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 + ## for: 10m + ## labels: + ## severity: page + ## annotations: + ## summary: High request latency + ## + rules: {} diff --git a/dependency_charts/mongodb/values.schema.json b/dependency_charts/mongodb/values.schema.json new file mode 100644 index 0000000..5e9d5da --- /dev/null +++ b/dependency_charts/mongodb/values.schema.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "architecture": { + "type": "string", + "title": "MongoDB architecture", + "form": true, + "description": "Allowed values: `standalone` or `replicaset`" + }, + "auth": { + "type": "object", + "title": "Authentication configuration", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable Authentication", + "form": true + }, + "rootPassword": { + "type": "string", + "title": "MongoDB admin password", + "form": true, + "description": "Defaults to a random 10-character alphanumeric string if not set", + "hidden": { + "value": false, + "path": "auth/enabled" + } + }, + "database": { + "type": "string", + "title": "MongoDB custom database", + "description": "Name of the custom database to be created during the 1st initialization of MongoDB", + "form": true + }, + "username": { + "type": "string", + "title": "MongoDB custom user", + "description": "Name of the custom user to be created during the 1st initialization of MongoDB. This user only has permissions on the MongoDB custom database", + "form": true + }, + "password": { + "type": "string", + "title": "Password for MongoDB custom user", + "form": true, + "description": "Defaults to a random 10-character alphanumeric string if not set", + "hidden": { + "value": false, + "path": "auth/enabled" + } + }, + "replicaSetKey": { + "type": "string", + "title": "Key used for replica set authentication", + "form": true, + "description": "Defaults to a random 10-character alphanumeric string if not set", + "hidden": { + "value": "standalone", + "path": "architecture" + } + } + } + }, + "replicaCount": { + "type": "integer", + "form": true, + "title": "Number of MongoDB replicas", + "hidden": { + "value": "standalone", + "path": "architecture" + } + }, + "configuration": { + "type": "string", + "title": "MongoDB Custom Configuration", + "form": true, + "render": "textArea" + }, + "arbiter": { + "type": "object", + "title": "Arbiter configuration", + "form": true, + "properties": { + "configuration": { + "type": "string", + "title": "Arbiter Custom Configuration", + "form": true, + "render": "textArea", + "hidden": { + "value": "standalone", + "path": "architecture" + } + } + } + }, + "persistence": { + "type": "object", + "title": "Persistence configuration", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable persistence", + "description": "Enable persistence using Persistent Volume Claims" + }, + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "persistence/enabled" + } + } + } + }, + "volumePermissions": { + "type": "object", + "hidden": { + "value": false, + "path": "persistence/enabled" + }, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable Init Containers", + "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination" + } + } + }, + "metrics": { + "type": "object", + "form": true, + "title": "Prometheus metrics details", + "properties": { + "enabled": { + "type": "boolean", + "title": "Create Prometheus metrics exporter", + "description": "Create a side-car container to expose Prometheus metrics", + "form": true + }, + "serviceMonitor": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Create Prometheus Operator ServiceMonitor", + "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", + "form": true, + "hidden": { + "value": false, + "path": "metrics/enabled" + } + } + } + } + } + } + } +} diff --git a/dependency_charts/mongodb/values.yaml b/dependency_charts/mongodb/values.yaml new file mode 100644 index 0000000..45e86bf --- /dev/null +++ b/dependency_charts/mongodb/values.yaml @@ -0,0 +1,1014 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +# global: +# imageRegistry: myRegistryName +# imagePullSecrets: +# - myRegistryKeySecretName +# storageClass: myStorageClass +## Override the namespace for resource deployed by the chart, but can itself be overridden by the local namespaceOverride +# namespaceOverride: my-global-namespace + +image: + ## Bitnami MongoDB registry + ## + registry: docker.io + ## Bitnami MongoDB image name + ## + repository: bitnami/mongodb + ## Bitnami MongoDB image tag + ## ref: https://hub.docker.com/r/bitnami/mongodb/tags/ + ## + tag: 4.4.3-debian-10-r0 + ## Specify a imagePullPolicy + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Set to true if you would like to see extra information on logs + ## It turns on Bitnami debugging in minideb-extras-base + ## ref: https://github.com/bitnami/minideb-extras-base + debug: false + +## String to partially override mongodb.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override mongodb.fullname template +## +# fullnameOverride: + +## Kubernetes Cluster Domain +## +clusterDomain: cluster.local + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## MongoDB architecture. Allowed values: standalone or replicaset +## +architecture: standalone + +## Use StatefulSet instead of Deployment when deploying standalone +## +useStatefulSet: false + +## MongoDB Authentication parameters +## +auth: + ## Enable authentication + ## ref: https://docs.mongodb.com/manual/tutorial/enable-authentication/ + ## + enabled: true + ## MongoDB root password + ## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#setting-the-root-password-on-first-run + ## + rootPassword: "" + ## MongoDB custom user and database + ## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#creating-a-user-and-database-on-first-run + ## + # username: username + # password: password + # database: database + ## Key used for replica set authentication + ## Ignored when mongodb.architecture=standalone + ## + replicaSetKey: "" + + ## Existing secret with MongoDB credentials + ## NOTE: When it's set the previous parameters are ignored. + ## + # existingSecret: name-of-existing-secret + +tls: + ## Enable or disable MongoDB TLS Support + enabled: false + + ## Existing secret with MongoDB TLS certificates + ## NOTE: When it's set it will disable certificate creation + ## + # existingSecret: name-of-existing-secret + + ## Add Custom CA certificate + # caCert: base64 encoded ca certificate + # caKey: base64 encoded private key + ## + ## Bitnami Nginx image + ## + image: + registry: docker.io + repository: bitnami/nginx + tag: 1.19.6-debian-10-r6 + pullPolicy: IfNotPresent + +## Name of the replica set +## Ignored when mongodb.architecture=standalone +## +replicaSetName: rs0 + +## Enable DNS hostnames in the replica set config +## Ignored when mongodb.architecture=standalone +## Ignored when externalAccess.enabled=true +## +replicaSetHostnames: true + +## Whether enable/disable IPv6 on MongoDB +## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#enabling/disabling-ipv6 +## +enableIPv6: false + +## Whether enable/disable DirectoryPerDB on MongoDB +## ref: https://github.com/bitnami/bitnami-docker-mongodb/blob/master/README.md#enabling/disabling-directoryperdb +## +directoryPerDB: false + +## MongoDB System Log configuration +## ref: https://github.com/bitnami/bitnami-docker-mongodb#configuring-system-log-verbosity-level +## +systemLogVerbosity: 0 +disableSystemLog: false + +## MongoDB configuration file for Primary and Secondary nodes. For documentation of all options, see: +## http://docs.mongodb.org/manual/reference/configuration-options/ +## Example: +## configuration: |- +## # where and how to store data. +## storage: +## dbPath: /bitnami/mongodb/data/db +## journal: +## enabled: true +## directoryPerDB: false +## # where to write logging data +## systemLog: +## destination: file +## quiet: false +## logAppend: true +## logRotate: reopen +## path: /opt/bitnami/mongodb/logs/mongodb.log +## verbosity: 0 +## # network interfaces +## net: +## port: 27017 +## unixDomainSocket: +## enabled: true +## pathPrefix: /opt/bitnami/mongodb/tmp +## ipv6: false +## bindIpAll: true +## # replica set options +## #replication: +## #replSetName: replicaset +## #enableMajorityReadConcern: true +## # process management options +## processManagement: +## fork: false +## pidFilePath: /opt/bitnami/mongodb/tmp/mongodb.pid +## # set parameter options +## setParameter: +## enableLocalhostAuthBypass: true +## # security options +## security: +## authorization: disabled +## #keyFile: /opt/bitnami/mongodb/conf/keyfile +## +configuration: "" + +## ConfigMap with MongoDB configuration for Primary and Secondary nodes +## NOTE: When it's set the arbiter.configuration parameter is ignored +## +# existingConfigmap: + +## initdb scripts +## Specify dictionary of scripts to be run at first boot +## Example: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/bash +## echo "Do something." +initdbScripts: {} + +## Existing ConfigMap with custom init scripts +## +# initdbScriptsConfigMap: + +## Command and args for running the container (set to default if not set). Use array form +## +# command: +# args: + +## Additional command line flags +## Example: +## extraFlags: +## - "--wiredTigerCacheSizeGB=2" +## +extraFlags: [] + +## Additional environment variables to set +## E.g: +## extraEnvVars: +## - name: FOO +## value: BAR +## +extraEnvVars: [] + +## ConfigMap with extra environment variables +## +# extraEnvVarsCM: + +## Secret with extra environment variables +## +# extraEnvVarsSecret: + +## Annotations to be added to the MongoDB statefulset. Evaluated as a template. +## +annotations: {} + +## Additional labels to be added to the MongoDB statefulset. Evaluated as a template. +## +labels: {} + +## Number of MongoDB replicas to deploy. +## Ignored when mongodb.architecture=standalone +## +replicaCount: 2 + +## StrategyType for MongoDB statefulset +## It can be set to RollingUpdate or Recreate by default. +## +strategyType: RollingUpdate + +## MongoDB should be initialized one by one when building the replicaset for the first time. +## +podManagementPolicy: OrderedReady + +## Pod affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAffinityPreset: "" + +## Pod anti-affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## Allowed values: soft, hard +## +podAntiAffinityPreset: soft + +## Node affinity preset +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## Allowed values: soft, hard +## +nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + +## Affinity for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## Note: podAffinityPreset, podAntiAffinityPreset, and nodeAffinityPreset will be ignored when it's set +## +affinity: {} + +## Node labels for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for pod assignment. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] + +## Labels for MongoDB pods. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} + +## Annotations for MongoDB pods. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} + +## MongoDB pods' priority. +## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +# priorityClassName: "" + +## MongoDB pods' Security Context. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## +podSecurityContext: + enabled: true + fsGroup: 1001 + ## sysctl settings + ## Example: + ## sysctls: + ## - name: net.core.somaxconn + ## value: "10000" + ## + sysctls: [] + +## MongoDB containers' Security Context (main and metrics container). +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true + +## MongoDB containers' resource requests and limits. +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + +## MongoDB pods' liveness and readiness probes. Evaluated as a template. +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes +## +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + +## Custom Liveness probes for MongoDB pods +## +customLivenessProbe: {} + +## Custom Rediness probes MongoDB pods +## +customReadinessProbe: {} + +## Add init containers to the MongoDB pods. +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: {} + +## Add sidecars to the MongoDB pods. +## Example: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: {} + +## extraVolumes and extraVolumeMounts allows you to mount other volumes on MongoDB pods +## Examples: +## extraVolumeMounts: +## - name: extras +## mountPath: /usr/share/extras +## readOnly: true +## extraVolumes: +## - name: extras +## emptyDir: {} +extraVolumeMounts: [] +extraVolumes: [] + +## MongoDB Pod Disruption Budget configuration +## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +## +pdb: + create: false + ## Min number of pods that must still be available after the eviction + ## + minAvailable: 1 + ## Max number of pods that can be unavailable after the eviction + ## + # maxUnavailable: 1 + +## Enable persistence using Persistent Volume Claims +## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ +## +persistence: + enabled: true + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + ## Ignored when mongodb.architecture=replicaset + ## + # existingClaim: + ## PV Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. + ## + # storageClass: "-" + ## PV Access Mode + ## + accessModes: + - ReadWriteOnce + ## PVC size + ## + size: 8Gi + ## PVC annotations + ## + annotations: {} + ## The path the volume will be mounted at, useful when using different + ## MongoDB images. + ## + mountPath: /bitnami/mongodb + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + ## + subPath: "" + ## Fine tuning for volumeClaimTemplates + volumeClaimTemplates: + ## A label query over volumes to consider for binding (e.g. when using local volumes) + ## See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#labelselector-v1-meta for more details + selector: + +## Service parameters +## +service: + ## Service type + ## + type: ClusterIP + ## MongoDB service port + ## + port: 27017 + ## MongoDB service port name + ## + portName: mongodb + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePort: "" + ## MongoDB service clusterIP IP + ## + # clusterIP: None + ## Specify the externalIP value ClusterIP service type. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips + ## + externalIPs: [] + ## Specify the loadBalancerIP value for LoadBalancer service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer + ## + # loadBalancerIP: + ## Specify the loadBalancerSourceRanges value for LoadBalancer service types. + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## + loadBalancerSourceRanges: [] + ## Provide any additional annotations which may be required. Evaluated as a template + ## + annotations: {} + +## External Access to MongoDB nodes configuration +## +externalAccess: + ## Enable Kubernetes external cluster access to MongoDB nodes + ## + enabled: false + ## External IPs auto-discovery configuration + ## An init container is used to auto-detect LB IPs or node ports by querying the K8s API + ## Note: RBAC might be required + ## + autoDiscovery: + ## Enable external IP/ports auto-discovery + ## + enabled: false + ## Bitnami Kubectl image + ## ref: https://hub.docker.com/r/bitnami/kubectl/tags/ + ## + image: + registry: docker.io + repository: bitnami/kubectl + tag: 1.18.13-debian-10-r12 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init Container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + ## Parameters to configure K8s service(s) used to externally access MongoDB + ## A new service per broker will be created + ## + service: + ## Service type. Allowed values: LoadBalancer or NodePort + ## + type: LoadBalancer + ## Port used when service type is LoadBalancer + ## + port: 27017 + ## Array of load balancer IPs for each MongoDB node. Length must be the same as replicaCount + ## Example: + ## loadBalancerIPs: + ## - X.X.X.X + ## - Y.Y.Y.Y + ## + loadBalancerIPs: [] + ## Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## Example: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## Array of node ports used for each MongoDB nodes. Length must be the same as replicaCount + ## Example: + ## nodePorts: + ## - 30001 + ## - 30002 + ## + nodePorts: [] + ## When service type is NodePort, you can specify the domain used for MongoDB advertised hostnames. + ## If not specified, the container will try to get the kubernetes node external IP + ## + # domain: mydomain.com + ## Provide any additional annotations which may be required. Evaluated as a template + ## + annotations: {} + +## +## MongoDB Arbiter parameters. +## +arbiter: + ## Enable deploying the MongoDB Arbiter + ## https://docs.mongodb.com/manual/tutorial/add-replica-set-arbiter/ + enabled: true + + ## MongoDB configuration file for the Arbiter. For documentation of all options, see: + ## http://docs.mongodb.org/manual/reference/configuration-options/ + ## + configuration: "" + + ## ConfigMap with MongoDB configuration for the Arbiter + ## NOTE: When it's set the arbiter.configuration parameter is ignored + ## + # existingConfigmap: + + ## Command and args for running the container (set to default if not set). Use array form + ## + # command: + # args: + + ## Additional command line flags + ## Example: + ## extraFlags: + ## - "--wiredTigerCacheSizeGB=2" + ## + extraFlags: [] + + ## Additional environment variables to set + ## E.g: + ## extraEnvVars: + ## - name: FOO + ## value: BAR + ## + extraEnvVars: [] + + ## ConfigMap with extra environment variables + ## + # extraEnvVarsCM: + + ## Secret with extra environment variables + ## + # extraEnvVarsSecret: + + ## Annotations to be added to the Arbiter statefulset. Evaluated as a template. + ## + annotations: {} + + ## Additional to be added to the Arbiter statefulset. Evaluated as a template. + ## + labels: {} + + ## Pod affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAffinityPreset: "" + + ## Pod anti-affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## Allowed values: soft, hard + ## + podAntiAffinityPreset: soft + + ## Node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## Allowed values: soft, hard + ## + nodeAffinityPreset: + ## Node affinity type + ## Allowed values: soft, hard + type: "" + ## Node label key to match + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## Node label values to match + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + + ## Affinity for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: arbiter.podAffinityPreset, arbiter.podAntiAffinityPreset, and arbiter.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + + ## Labels for MongoDB Arbiter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + + ## Annotations for MongoDB Arbiter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + + ## MongoDB Arbiter pods' priority. + ## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ + ## + # priorityClassName: "" + + ## MongoDB Arbiter pods' Security Context. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## sysctl settings + ## Example: + ## sysctls: + ## - name: net.core.somaxconn + ## value: "10000" + ## + sysctls: [] + + ## MongoDB Arbiter containers' Security Context (only main container). + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + + ## MongoDB Arbiter containers' resource requests and limits. + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + + ## MongoDB Arbiter pods' liveness and readiness probes. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + ## Custom Liveness probes for MongoDB Arbiter pods + ## + customLivenessProbe: {} + + ## Custom Rediness probes MongoDB Arbiter pods + ## + customReadinessProbe: {} + + ## Add init containers to the MongoDB Arbiter pods. + ## Example: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + initContainers: {} + + ## Add sidecars to the MongoDB Arbiter pods. + ## Example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: {} + + ## extraVolumes and extraVolumeMounts allows you to mount other volumes on MongoDB Arbiter pods + ## Examples: + ## extraVolumeMounts: + ## - name: extras + ## mountPath: /usr/share/extras + ## readOnly: true + ## extraVolumes: + ## - name: extras + ## emptyDir: {} + extraVolumeMounts: [] + extraVolumes: [] + + ## MongoDB Arbiter Pod Disruption Budget configuration + ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + ## + pdb: + create: false + ## Min number of pods that must still be available after the eviction + ## + minAvailable: 1 + ## Max number of pods that can be unavailable after the eviction + ## + # maxUnavailable: 1 + +## ServiceAccount +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: true + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the rabbitmq.fullname template + ## + # name: + +## Role Based Access +## ref: https://kubernetes.io/docs/admin/authorization/rbac/ +## +rbac: + ## Specifies whether RBAC rules should be created + ## binding MongoDB ServiceAccount to a role + ## that allows MongoDB pods querying the K8s API + ## + create: false + +## Init Container parameters +## Change the owner and group of the persistent volume(s) mountpoint(s) to 'runAsUser:fsGroup' on each component +## values from the securityContext section of the component +## +volumePermissions: + enabled: false + ## Bitnami Minideb image + ## ref: https://hub.docker.com/r/bitnami/minideb/tags/ + ## + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets (secrets must be manually created in the namespace) + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init Container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + ## Init container Security Context + ## Note: the chown of the data folder is done to containerSecurityContext.runAsUser + ## and not the below volumePermissions.securityContext.runAsUser + ## When runAsUser is set to special value "auto", init container will try to chwon the + ## data folder to autodetermined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic userids (and 0 is not allowed). + ## You may want to use this volumePermissions.securityContext.runAsUser="auto" in combination with + ## podSecurityContext.enabled=false,containerSecurityContext.enabled=false and shmVolume.chmod.enabled=false + ## + securityContext: + runAsUser: 0 + +## Prometheus Exporter / Metrics +## +metrics: + enabled: false + ## Bitnami MongoDB Promtheus Exporter image + ## ref: https://hub.docker.com/r/bitnami/mongodb-exporter/tags/ + ## + image: + registry: docker.io + repository: bitnami/mongodb-exporter + tag: 0.20.1-debian-10-r18 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## String with extra flags to the metrics exporter + ## ref: https://github.com/percona/mongodb_exporter/blob/master/mongodb_exporter.go + ## + extraFlags: "" + + ## String with additional URI options to the metrics exporter + ## ref: https://docs.mongodb.com/manual/reference/connection-string + ## + extraUri: "" + + ## Metrics exporter container resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: {} + # cpu: 100m + # memory: 128Mi + requests: {} + # cpu: 100m + # memory: 128Mi + + ## Prometheus Exporter service configuration + ## + service: + ## Annotations for Prometheus Exporter pods. Evaluated as a template. + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.metrics.service.port }}" + prometheus.io/path: "/metrics" + type: ClusterIP + port: 9216 + + ## Metrics exporter liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 + + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md + ## + serviceMonitor: + ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry + enabled: false + + ## Specify the namespace where Prometheus Operator is running + ## + # namespace: monitoring + + ## Specify the interval at which metrics should be scraped + ## + interval: 30s + ## Specify the timeout after which the scrape is ended + ## + # scrapeTimeout: 30s + ## Used to pass Labels that are used by the Prometheus installed in your cluster to select Service Monitors to work with + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec + ## + additionalLabels: {} + + ## Custom PrometheusRule to be defined + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + enabled: false + additionalLabels: {} + ## Specify the namespace where Prometheus Operator is running + ## + # namespace: monitoring + + ## Define individual alerting rules as required + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#rulegroup + ## https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ + ## + ## This is an example of a rule, you should add the below code block under the "rules" param, removing the brackets + ## - name: example + ## rules: + ## - alert: HighRequestLatency + ## expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 + ## for: 10m + ## labels: + ## severity: page + ## annotations: + ## summary: High request latency + ## + rules: {} diff --git a/dependency_charts/redis/.helmignore b/dependency_charts/redis/.helmignore new file mode 100755 index 0000000..f0c1319 --- /dev/null +++ b/dependency_charts/redis/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/dependency_charts/redis/Chart.yaml b/dependency_charts/redis/Chart.yaml new file mode 100755 index 0000000..b745708 --- /dev/null +++ b/dependency_charts/redis/Chart.yaml @@ -0,0 +1,24 @@ +annotations: + category: Database +apiVersion: v1 +appVersion: 6.0.8 +description: Open source, advanced key-value store. It is often referred to as a data + structure server since keys can contain strings, hashes, lists, sets and sorted + sets. +engine: gotpl +home: https://github.com/bitnami/charts/tree/master/bitnami/redis +icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png +keywords: +- redis +- keyvalue +- database +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: cedric@desaintmartin.fr + name: desaintmartin +name: redis +sources: +- https://github.com/bitnami/bitnami-docker-redis +- http://redis.io/ +version: 10.9.0 diff --git a/dependency_charts/redis/README.md b/dependency_charts/redis/README.md new file mode 100755 index 0000000..a218880 --- /dev/null +++ b/dependency_charts/redis/README.md @@ -0,0 +1,564 @@ +# Redis + +[Redis](http://redis.io/) is an advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs. + +## TL;DR + +```bash +# Testing configuration +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/redis +``` + +```bash +# Production configuration +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/redis --values values-production.yaml +``` + +## Introduction + +This chart bootstraps a [Redis](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.com/) for deployment and management of Helm Charts in clusters. This chart has been tested to work with NGINX Ingress, cert-manager, fluentd and Prometheus on top of the [BKPR](https://kubeprod.io/). + +### Choose between Redis Helm Chart and Redis Cluster Helm Chart + +You can choose any of the two Redis Helm charts for deploying a Redis cluster. +While [Redis Helm Chart](https://github.com/bitnami/charts/tree/master/bitnami/redis) will deploy a master-slave cluster using Redis Sentinel, the [Redis Cluster Helm Chart](https://github.com/bitnami/charts/tree/master/bitnami/redis-cluster) will deploy a Redis Cluster topology with sharding. +The main features of each chart are the following: + +| Redis | Redis Cluster | +|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| Supports multiple databases | Supports only one database. Better if you have a big dataset | +| Single write point (single master) | Multiple write points (multiple masters) | +| ![Redis Topology](img/redis-topology.png) | ![Redis Cluster Topology](img/redis-cluster-topology.png) | + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 2.12+ or Helm 3.0-beta3+ +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```bash +$ helm install my-release bitnami/redis +``` + +The command deploys Redis on the Kubernetes cluster in the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```bash +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +The following table lists the configurable parameters of the Redis chart and their default values. + +| Parameter | Description | Default | +|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------| +| `global.imageRegistry` | Global Docker image registry | `nil` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` (does not add image pull secrets to deployed pods) | +| `global.storageClass` | Global storage class for dynamic provisioning | `nil` | +| `global.redis.password` | Redis password (overrides `password`) | `nil` | +| `image.registry` | Redis Image registry | `docker.io` | +| `image.repository` | Redis Image name | `bitnami/redis` | +| `image.tag` | Redis Image tag | `{TAG_NAME}` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `nameOverride` | String to partially override redis.fullname template with a string (will prepend the release name) | `nil` | +| `fullnameOverride` | String to fully override redis.fullname template with a string | `nil` | +| `cluster.enabled` | Use master-slave topology | `true` | +| `cluster.slaveCount` | Number of slaves | `2` | +| `existingSecret` | Name of existing secret object (for password authentication) | `nil` | +| `existingSecretPasswordKey` | Name of key containing password to be retrieved from the existing secret | `nil` | +| `usePassword` | Use password | `true` | +| `usePasswordFile` | Mount passwords as files instead of environment variables | `false` | +| `password` | Redis password (ignored if existingSecret set) | Randomly generated | +| `configmap` | Additional common Redis node configuration (this value is evaluated as a template) | See values.yaml | +| `clusterDomain` | Kubernetes DNS Domain name to use | `cluster.local` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.ingressNSMatchLabels` | Allow connections from other namespaces | `{}` | +| `networkPolicy.ingressNSPodMatchLabels` | For other namespaces match by pod labels and namespace labels | `{}` | +| `securityContext.enabled` | Enable security context (both redis master and slave pods) | `true` | +| `securityContext.fsGroup` | Group ID for the container (both redis master and slave pods) | `1001` | +| `securityContext.runAsUser` | User ID for the container (both redis master and slave pods) | `1001` | +| `securityContext.sysctls` | Set namespaced sysctls for the container (both redis master and slave pods) | `nil` | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `false` | +| `serviceAccount.name` | The name of the ServiceAccount to create | Generated using the fullname template | +| `rbac.create` | Specifies whether RBAC resources should be created | `false` | +| `rbac.role.rules` | Rules to create | `[]` | +| `metrics.enabled` | Start a side-car prometheus exporter | `false` | +| `metrics.image.registry` | Redis exporter image registry | `docker.io` | +| `metrics.image.repository` | Redis exporter image name | `bitnami/redis-exporter` | +| `metrics.image.tag` | Redis exporter image tag | `{TAG_NAME}` | +| `metrics.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `metrics.extraArgs` | Extra arguments for the binary; possible values [here](https://github.com/oliver006/redis_exporter#flags) | {} | +| `metrics.podLabels` | Additional labels for Metrics exporter pod | {} | +| `metrics.podAnnotations` | Additional annotations for Metrics exporter pod | {} | +| `metrics.resources` | Exporter resource requests/limit | Memory: `256Mi`, CPU: `100m` | +| `metrics.serviceMonitor.enabled` | if `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) | `false` | +| `metrics.serviceMonitor.namespace` | Optional namespace which Prometheus is running in | `nil` | +| `metrics.serviceMonitor.interval` | How frequently to scrape metrics (use by default, falling back to Prometheus' default) | `nil` | +| `metrics.serviceMonitor.selector` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install | `{ prometheus: kube-prometheus }` | +| `metrics.service.type` | Kubernetes Service type (redis metrics) | `ClusterIP` | +| `metrics.service.annotations` | Annotations for the services to monitor (redis master and redis slave service) | {} | +| `metrics.service.labels` | Additional labels for the metrics service | {} | +| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `nil` | +| `metrics.priorityClassName` | Metrics exporter pod priorityClassName | {} | +| `metrics.prometheusRule.enabled` | Set this to true to create prometheusRules for Prometheus operator | `false` | +| `metrics.prometheusRule.additionalLabels` | Additional labels that can be used so prometheusRules will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.namespace` | namespace where prometheusRules resource should be created | Same namespace as redis | +| `metrics.prometheusRule.rules` | [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) to be created, check values for an example. | `[]` | +| `persistence.existingClaim` | Provide an existing PersistentVolumeClaim | `nil` | +| `master.persistence.enabled` | Use a PVC to persist data (master node) | `true` | +| `master.persistence.path` | Path to mount the volume at, to use other images | `/data` | +| `master.persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `master.persistence.storageClass` | Storage class of backing PVC | `generic` | +| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | +| `master.persistence.size` | Size of data volume | `8Gi` | +| `master.persistence.matchLabels` | matchLabels persistent volume selector | `{}` | +| `master.persistence.matchExpressions` | matchExpressions persistent volume selector | `{}` | +| `master.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | +| `master.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | +| `master.podLabels` | Additional labels for Redis master pod | {} | +| `master.podAnnotations` | Additional annotations for Redis master pod | {} | +| `master.extraEnvVars` | Additional Environement Variables passed to the pod of the master's stateful set set | `[]` +| `master.extraEnvVarCMs` | Additional Environement Variables ConfigMappassed to the pod of the master's stateful set set | `[]` +| `master.extraEnvVarsSecret` | Additional Environement Variables Secret passed to the master's stateful set | `[]` +| `podDisruptionBudget.enabled` | Pod Disruption Budget toggle | `false` | +| `podDisruptionBudget.minAvailable` | Minimum available pods | `1` | +| `podDisruptionBudget.maxUnavailable` | Maximum unavailable pods | `nil` | +| `redisPort` | Redis port (in both master and slaves) | `6379` | +| `tls.enabled` | Enable TLS support for replication traffic | `false` | +| `tls.authClients` | Require clients to authenticate or not | `true` | +| `tls.certificatesSecret` | Name of the secret that contains the certificates | `nil` | +| `tls.certFilename` | Certificate filename | `nil` | +| `tls.certKeyFilename` | Certificate key filename | `nil` | +| `tls.certCAFilename` | CA Certificate filename |`nil` | +| `tls.dhParamsFilename` | DH params (in order to support DH based ciphers) |`nil` | +| `master.command` | Redis master entrypoint string. The command `redis-server` is executed if this is not provided. | `/run.sh` | +| `master.configmap` | Additional Redis configuration for the master nodes (this value is evaluated as a template) | `nil` | +| `master.disableCommands` | Array of Redis commands to disable (master) | `["FLUSHDB", "FLUSHALL"]` | +| `master.extraFlags` | Redis master additional command line flags | [] | +| `master.nodeSelector` | Redis master Node labels for pod assignment | {"beta.kubernetes.io/arch": "amd64"} | +| `master.tolerations` | Toleration labels for Redis master pod assignment | [] | +| `master.affinity` | Affinity settings for Redis master pod assignment | {} | +| `master.schedulerName` | Name of an alternate scheduler | `nil` | +| `master.service.type` | Kubernetes Service type (redis master) | `ClusterIP` | +| `master.service.port` | Kubernetes Service port (redis master) | `6379` | +| `master.service.nodePort` | Kubernetes Service nodePort (redis master) | `nil` | +| `master.service.annotations` | annotations for redis master service | {} | +| `master.service.labels` | Additional labels for redis master service | {} | +| `master.service.loadBalancerIP` | loadBalancerIP if redis master service type is `LoadBalancer` | `nil` | +| `master.service.loadBalancerSourceRanges` | loadBalancerSourceRanges if redis master service type is `LoadBalancer` | `nil` | +| `master.resources` | Redis master CPU/Memory resource requests/limits | Memory: `256Mi`, CPU: `100m` | +| `master.livenessProbe.enabled` | Turn on and off liveness probe (redis master pod) | `true` | +| `master.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis master pod) | `5` | +| `master.livenessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `5` | +| `master.livenessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `5` | +| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | +| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `master.readinessProbe.enabled` | Turn on and off readiness probe (redis master pod) | `true` | +| `master.readinessProbe.initialDelaySeconds` | Delay before readiness probe is initiated (redis master pod) | `5` | +| `master.readinessProbe.periodSeconds` | How often to perform the probe (redis master pod) | `5` | +| `master.readinessProbe.timeoutSeconds` | When the probe times out (redis master pod) | `1` | +| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis master pod) | `1` | +| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `master.shareProcessNamespace` | Redis Master pod `shareProcessNamespace` option. Enables /pause reap zombie PIDs. | `false` | +| `master.priorityClassName` | Redis Master pod priorityClassName | {} | +| `volumePermissions.enabled` | Enable init container that changes volume permissions in the registry (for cases where the default k8s `runAsUser` and `fsUser` values do not work) | `false` | +| `volumePermissions.image.registry` | Init container volume-permissions image registry | `docker.io` | +| `volumePermissions.image.repository` | Init container volume-permissions image name | `bitnami/minideb` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `buster` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `Always` | +| `volumePermissions.resources ` | Init container volume-permissions CPU/Memory resource requests/limits | {} | +| `slave.service.type` | Kubernetes Service type (redis slave) | `ClusterIP` | +| `slave.service.nodePort` | Kubernetes Service nodePort (redis slave) | `nil` | +| `slave.service.annotations` | annotations for redis slave service | {} | +| `slave.service.labels` | Additional labels for redis slave service | {} | +| `slave.service.port` | Kubernetes Service port (redis slave) | `6379` | +| `slave.service.loadBalancerIP` | LoadBalancerIP if Redis slave service type is `LoadBalancer` | `nil` | +| `slave.service.loadBalancerSourceRanges` | loadBalancerSourceRanges if Redis slave service type is `LoadBalancer` | `nil` | +| `slave.command` | Redis slave entrypoint array. The docker image's ENTRYPOINT is used if this is not provided. | `/run.sh` | +| `slave.configmap` | Additional Redis configuration for the slave nodes (this value is evaluated as a template) | `nil` | +| `slave.disableCommands` | Array of Redis commands to disable (slave) | `[FLUSHDB, FLUSHALL]` | +| `slave.extraFlags` | Redis slave additional command line flags | `[]` | +| `slave.livenessProbe.enabled` | Turn on and off liveness probe (redis slave pod) | `true` | +| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis slave pod) | `5` | +| `slave.livenessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `5` | +| `slave.livenessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `5` | +| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | +| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `slave.readinessProbe.enabled` | Turn on and off slave.readiness probe (redis slave pod) | `true` | +| `slave.readinessProbe.initialDelaySeconds` | Delay before slave.readiness probe is initiated (redis slave pod) | `5` | +| `slave.readinessProbe.periodSeconds` | How often to perform the probe (redis slave pod) | `5` | +| `slave.readinessProbe.timeoutSeconds` | When the probe times out (redis slave pod) | `1` | +| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis slave pod) | `1` | +| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis slave pod) | `5` | +| `slave.shareProcessNamespace` | Redis slave pod `shareProcessNamespace` option. Enables /pause reap zombie PIDs. | `false` | +| `slave.persistence.enabled` | Use a PVC to persist data (slave node) | `true` | +| `slave.persistence.path` | Path to mount the volume at, to use other images | `/data` | +| `slave.persistence.subPath` | Subdirectory of the volume to mount at | `""` | +| `slave.persistence.storageClass` | Storage class of backing PVC | `generic` | +| `slave.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | +| `slave.persistence.size` | Size of data volume | `8Gi` | +| `slave.persistence.matchLabels` | matchLabels persistent volume selector | `{}` | +| `slave.persistence.matchExpressions` | matchExpressions persistent volume selector | `{}` | +| `slave.statefulset.updateStrategy` | Update strategy for StatefulSet | onDelete | +| `slave.statefulset.rollingUpdatePartition` | Partition update strategy | `nil` | +| `slave.extraEnvVars` | Additional Environement Variables passed to the pod of the slave's stateful set set | `[]` +| `slave.extraEnvVarCMs` | Additional Environement Variables ConfigMappassed to the pod of the slave's stateful set set | `[]` +| `masslaveter.extraEnvVarsSecret` | Additional Environement Variables Secret passed to the slave's stateful set | `[]` +| `slave.podLabels` | Additional labels for Redis slave pod | `master.podLabels` | +| `slave.podAnnotations` | Additional annotations for Redis slave pod | `master.podAnnotations` | +| `slave.schedulerName` | Name of an alternate scheduler | `nil` | +| `slave.resources` | Redis slave CPU/Memory resource requests/limits | `{}` | +| `slave.affinity` | Enable node/pod affinity for slaves | {} | +| `slave.tolerations` | Toleration labels for Redis slave pod assignment | [] | +| `slave.spreadConstraints` | [Topology Spread Constraints](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) for Redis slave pod | {} | +| `slave.priorityClassName` | Redis Slave pod priorityClassName | {} | +| `sentinel.enabled` | Enable sentinel containers | `false` | +| `sentinel.usePassword` | Use password for sentinel containers | `true` | +| `sentinel.masterSet` | Name of the sentinel master set | `mymaster` | +| `sentinel.initialCheckTimeout` | Timeout for querying the redis sentinel service for the active sentinel list | `5` | +| `sentinel.quorum` | Quorum for electing a new master | `2` | +| `sentinel.downAfterMilliseconds` | Timeout for detecting a Redis node is down | `60000` | +| `sentinel.failoverTimeout` | Timeout for performing a election failover | `18000` | +| `sentinel.parallelSyncs` | Number of parallel syncs in the cluster | `1` | +| `sentinel.port` | Redis Sentinel port | `26379` | +| `sentinel.configmap` | Additional Redis configuration for the sentinel nodes (this value is evaluated as a template) | `nil` | +| `sentinel.staticID` | Enable static IDs for sentinel replicas (If disabled IDs will be randomly generated on startup) | `false` | +| `sentinel.service.type` | Kubernetes Service type (redis sentinel) | `ClusterIP` | +| `sentinel.service.nodePort` | Kubernetes Service nodePort (redis sentinel) | `nil` | +| `sentinel.service.annotations` | annotations for redis sentinel service | {} | +| `sentinel.service.labels` | Additional labels for redis sentinel service | {} | +| `sentinel.service.redisPort` | Kubernetes Service port for Redis read only operations | `6379` | +| `sentinel.service.sentinelPort` | Kubernetes Service port for Redis sentinel | `26379` | +| `sentinel.service.redisNodePort` | Kubernetes Service node port for Redis read only operations | `` | +| `sentinel.service.sentinelNodePort` | Kubernetes Service node port for Redis sentinel | `` | +| `sentinel.service.loadBalancerIP` | LoadBalancerIP if Redis sentinel service type is `LoadBalancer` | `nil` | +| `sentinel.livenessProbe.enabled` | Turn on and off liveness probe (redis sentinel pod) | `true` | +| `sentinel.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (redis sentinel pod) | `5` | +| `sentinel.livenessProbe.periodSeconds` | How often to perform the probe (redis sentinel container) | `5` | +| `sentinel.livenessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `5` | +| `sentinel.livenessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | +| `sentinel.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. | `5` | +| `sentinel.readinessProbe.enabled` | Turn on and off sentinel.readiness probe (redis sentinel pod) | `true` | +| `sentinel.readinessProbe.initialDelaySeconds` | Delay before sentinel.readiness probe is initiated (redis sentinel pod) | `5` | +| `sentinel.readinessProbe.periodSeconds` | How often to perform the probe (redis sentinel pod) | `5` | +| `sentinel.readinessProbe.timeoutSeconds` | When the probe times out (redis sentinel container) | `1` | +| `sentinel.readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed (redis sentinel container) | `1` | +| `sentinel.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe to be considered failed after having succeeded. (redis sentinel container) | `5` | +| `sentinel.resources` | Redis sentinel CPU/Memory resource requests/limits | `{}` | +| `sentinel.image.registry` | Redis Sentinel Image registry | `docker.io` | +| `sentinel.image.repository` | Redis Sentinel Image name | `bitnami/redis-sentinel` | +| `sentinel.image.tag` | Redis Sentinel Image tag | `{TAG_NAME}` | +| `sentinel.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `sentinel.image.pullSecrets` | Specify docker-registry secret names as an array | `nil` | +| `sysctlImage.enabled` | Enable an init container to modify Kernel settings | `false` | +| `sysctlImage.command` | sysctlImage command to execute | [] | +| `sysctlImage.registry` | sysctlImage Init container registry | `docker.io` | +| `sysctlImage.repository` | sysctlImage Init container name | `bitnami/minideb` | +| `sysctlImage.tag` | sysctlImage Init container tag | `buster` | +| `sysctlImage.pullPolicy` | sysctlImage Init container pull policy | `Always` | +| `sysctlImage.mountHostSys` | Mount the host `/sys` folder to `/host-sys` | `false` | +| `sysctlImage.resources` | sysctlImage Init container CPU/Memory resource requests/limits | {} | +| `podSecurityPolicy.create` | Specifies whether a PodSecurityPolicy should be created | `false` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install my-release \ + --set password=secretpassword \ + bitnami/redis +``` + +The above command sets the Redis server password to `secretpassword`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install my-release -f values.yaml bitnami/redis +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +> **Note for minikube users**: Current versions of minikube (v0.24.1 at the time of writing) provision `hostPath` persistent volumes that are only writable by root. Using chart defaults cause pod failure for the Redis pod as it attempts to write to the `/bitnami` directory. Consider installing Redis with `--set persistence.enabled=false`. See minikube issue [1990](https://github.com/kubernetes/minikube/issues/1990) for more information. + +## Configuration and installation details + +### [Rolling VS Immutable tags](https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Production configuration + +This chart includes a `values-production.yaml` file where you can find some parameters oriented to production configuration in comparison to the regular `values.yaml`. You can use this file instead of the default one. + +- Number of slaves: +```diff +- cluster.slaveCount: 2 ++ cluster.slaveCount: 3 +``` + +- Enable NetworkPolicy: +```diff +- networkPolicy.enabled: false ++ networkPolicy.enabled: true +``` + +- Start a side-car prometheus exporter: +```diff +- metrics.enabled: false ++ metrics.enabled: true +``` + +### Change Redis version + +To modify the Redis version used in this chart you can specify a [valid image tag](https://hub.docker.com/r/bitnami/redis/tags/) using the `image.tag` parameter. For example, `image.tag=X.Y.Z`. This approach is also applicable to other images like exporters. + +### Cluster topologies + +#### Default: Master-Slave + +When installing the chart with `cluster.enabled=true`, it will deploy a Redis master StatefulSet (only one master node allowed) and a Redis slave StatefulSet. The slaves will be read-replicas of the master. Two services will be exposed: + + - Redis Master service: Points to the master, where read-write operations can be performed + - Redis Slave service: Points to the slaves, where only read operations are allowed. + +In case the master crashes, the slaves will wait until the master node is respawned again by the Kubernetes Controller Manager. + +#### Master-Slave with Sentinel + +When installing the chart with `cluster.enabled=true` and `sentinel.enabled=true`, it will deploy a Redis master StatefulSet (only one master allowed) and a Redis slave StatefulSet. In this case, the pods will contain an extra container with Redis Sentinel. This container will form a cluster of Redis Sentinel nodes, which will promote a new master in case the actual one fails. In addition to this, only one service is exposed: + + - Redis service: Exposes port 6379 for Redis read-only operations and port 26379 for accesing Redis Sentinel. + +For read-only operations, access the service using port 6379. For write operations, it's necessary to access the Redis Sentinel cluster and query the current master using the command below (using redis-cli or similar: + +``` +SENTINEL get-master-addr-by-name +``` +This command will return the address of the current master, which can be accessed from inside the cluster. + +In case the current master crashes, the Sentinel containers will elect a new master node. + +### Using password file +To use a password file for Redis you need to create a secret containing the password. + +> *NOTE*: It is important that the file with the password must be called `redis-password` + +And then deploy the Helm Chart using the secret name as parameter: + +```console +usePassword=true +usePasswordFile=true +existingSecret=redis-password-file +sentinels.enabled=true +metrics.enabled=true +``` + +### Securing traffic using TLS + +TLS support can be enabled in the chart by specifying the `tls.` parameters while creating a release. The following parameters should be configured to properly enable the TLS support in the chart: + +- `tls.enabled`: Enable TLS support. Defaults to `false` +- `tls.certificatesSecret`: Name of the secret that contains the certificates. No defaults. +- `tls.certFilename`: Certificate filename. No defaults. +- `tls.certKeyFilename`: Certificate key filename. No defaults. +- `tls.certCAFilename`: CA Certificate filename. No defaults. + +For example: + +First, create the secret with the cetificates files: + +```console +kubectl create secret generic certificates-tls-secret --from-file=./cert.pem --from-file=./cert.key --from-file=./ca.pem +``` + +Then, use the following parameters: + +```console +tls.enabled="true" +tls.certificatesSecret="certificates-tls-secret" +tls.certFilename="cert.pem" +tls.certKeyFilename="cert.key" +tls.certCAFilename="ca.pem" +``` + +> **Note TLS and Prometheus Metrics**: Current version of Redis Metrics Exporter (v1.6.1 at the time of writing) does not fully support the use of TLS. By enabling both features, the metric reporting pod is likely to not work as expected. See Redis Metrics Exporter issue [387](https://github.com/oliver006/redis_exporter/issues/387) for more information. + +### Metrics + +The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9121) is exposed in the service. Metrics can be scraped from within the cluster using something similar as the described in the [example Prometheus scrape configuration](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus-kubernetes.yml). If metrics are to be scraped from outside the cluster, the Kubernetes API proxy can be utilized to access the endpoint. + +### Host Kernel Settings +Redis may require some changes in the kernel of the host machine to work as expected, in particular increasing the `somaxconn` value and disabling transparent huge pages. +To do so, you can set up a privileged initContainer with the `sysctlImage` config values, for example: +``` +sysctlImage: + enabled: true + mountHostSys: true + command: + - /bin/sh + - -c + - |- + install_packages procps + sysctl -w net.core.somaxconn=10000 + echo never > /host-sys/kernel/mm/transparent_hugepage/enabled +``` + +Alternatively, for Kubernetes 1.12+ you can set `securityContext.sysctls` which will configure sysctls for master and slave pods. Example: + +```yaml +securityContext: + sysctls: + - name: net.core.somaxconn + value: "10000" +``` + +Note that this will not disable transparent huge tables. + +## Persistence + +By default, the chart mounts a [Persistent Volume](http://kubernetes.io/docs/user-guide/persistent-volumes/) at the `/data` path. The volume is created using dynamic volume provisioning. If a Persistent Volume Claim already exists, specify it during installation. + +### Existing PersistentVolumeClaim + +1. Create the PersistentVolume +2. Create the PersistentVolumeClaim +3. Install the chart + +```bash +$ helm install my-release --set persistence.existingClaim=PVC_NAME bitnami/redis +``` + +## NetworkPolicy + +To enable network policy for Redis, install +[a networking plugin that implements the Kubernetes NetworkPolicy spec](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy#before-you-begin), +and set `networkPolicy.enabled` to `true`. + +For Kubernetes v1.5 & v1.6, you must also turn on NetworkPolicy by setting +the DefaultDeny namespace annotation. Note: this will enforce policy for _all_ pods in the namespace: + + kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" + +With NetworkPolicy enabled, only pods with the generated client label will be +able to connect to Redis. This label will be displayed in the output +after a successful install. + +With `networkPolicy.ingressNSMatchLabels` pods from other namespaces can connect to redis. Set `networkPolicy.ingressNSPodMatchLabels` to match pod labels in matched namespace. For example, for a namespace labeled `redis=external` and pods in that namespace labeled `redis-client=true` the fields should be set: + +``` +networkPolicy: + enabled: true + ingressNSMatchLabels: + redis: external + ingressNSPodMatchLabels: + redis-client: true +``` + +## Upgrading an existing Release to a new major version + +A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an +incompatible breaking change needing manual actions. + +### To 10.0.0 + +For releases with `usePassword: true`, the value `sentinel.usePassword` controls whether the password authentication also applies to the sentinel port. This defaults to `true` for a secure configuration, however it is possible to disable to account for the following cases: +* Using a version of redis-sentinel prior to `5.0.1` where the authentication feature was introduced. +* Where redis clients need to be updated to support sentinel authentication. + +If using a master/slave topology, or with `usePassword: false`, no action is required. + +### To 8.0.18 + +For releases with `metrics.enabled: true` the default tag for the exporter image is now `v1.x.x`. This introduces many changes including metrics names. You'll want to use [this dashboard](https://github.com/oliver006/redis_exporter/blob/master/contrib/grafana_prometheus_redis_dashboard.json) now. Please see the [redis_exporter github page](https://github.com/oliver006/redis_exporter#upgrading-from-0x-to-1x) for more details. + +### To 7.0.0 + +This version causes a change in the Redis Master StatefulSet definition, so the command helm upgrade would not work out of the box. As an alternative, one of the following could be done: + + - Recommended: Create a clone of the Redis Master PVC (for example, using projects like [this one](https://github.com/edseymour/pvc-transfer)). Then launch a fresh release reusing this cloned PVC. + + ``` + helm install my-release bitnami/redis --set persistence.existingClaim= + ``` + + - Alternative (not recommended, do at your own risk): `helm delete --purge` does not remove the PVC assigned to the Redis Master StatefulSet. As a consequence, the following commands can be done to upgrade the release + + ``` + helm delete --purge + helm install bitnami/redis + ``` + +Previous versions of the chart were not using persistence in the slaves, so this upgrade would add it to them. Another important change is that no values are inherited from master to slaves. For example, in 6.0.0 `slaves.readinessProbe.periodSeconds`, if empty, would be set to `master.readinessProbe.periodSeconds`. This approach lacked transparency and was difficult to maintain. From now on, all the slave parameters must be configured just as it is done with the masters. + +Some values have changed as well: + + - `master.port` and `slave.port` have been changed to `redisPort` (same value for both master and slaves) + - `master.securityContext` and `slave.securityContext` have been changed to `securityContext`(same values for both master and slaves) + +By default, the upgrade will not change the cluster topology. In case you want to use Redis Sentinel, you must explicitly set `sentinel.enabled` to `true`. + +### To 6.0.0 + +Previous versions of the chart were using an init-container to change the permissions of the volumes. This was done in case the `securityContext` directive in the template was not enough for that (for example, with cephFS). In this new version of the chart, this container is disabled by default (which should not affect most of the deployments). If your installation still requires that init container, execute `helm upgrade` with the `--set volumePermissions.enabled=true`. + +### To 5.0.0 + +The default image in this release may be switched out for any image containing the `redis-server` +and `redis-cli` binaries. If `redis-server` is not the default image ENTRYPOINT, `master.command` +must be specified. + +#### Breaking changes +- `master.args` and `slave.args` are removed. Use `master.command` or `slave.command` instead in order to override the image entrypoint, or `master.extraFlags` to pass additional flags to `redis-server`. +- `disableCommands` is now interpreted as an array of strings instead of a string of comma separated values. +- `master.persistence.path` now defaults to `/data`. + +### 4.0.0 + +This version removes the `chart` label from the `spec.selector.matchLabels` +which is immutable since `StatefulSet apps/v1beta2`. It has been inadvertently +added, causing any subsequent upgrade to fail. See https://github.com/helm/charts/issues/7726. + +It also fixes https://github.com/helm/charts/issues/7726 where a deployment `extensions/v1beta1` can not be upgraded if `spec.selector` is not explicitly set. + +Finally, it fixes https://github.com/helm/charts/issues/7803 by removing mutable labels in `spec.VolumeClaimTemplate.metadata.labels` so that it is upgradable. + +In order to upgrade, delete the Redis StatefulSet before upgrading: +```bash +$ kubectl delete statefulsets.apps --cascade=false my-release-redis-master +``` +And edit the Redis slave (and metrics if enabled) deployment: +```bash +kubectl patch deployments my-release-redis-slave --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' +kubectl patch deployments my-release-redis-metrics --type=json -p='[{"op": "remove", "path": "/spec/selector/matchLabels/chart"}]' +``` + +## Notable changes + +### 9.0.0 +The metrics exporter has been changed from a separate deployment to a sidecar container, due to the latest changes in the Redis exporter code. Check the [official page](https://github.com/oliver006/redis_exporter/) for more information. The metrics container image was changed from oliver006/redis_exporter to bitnami/redis-exporter (Bitnami's maintained package of oliver006/redis_exporter). + +### 7.0.0 +In order to improve the performance in case of slave failure, we added persistence to the read-only slaves. That means that we moved from Deployment to StatefulSets. This should not affect upgrades from previous versions of the chart, as the deployments did not contain any persistence at all. + +This version also allows enabling Redis Sentinel containers inside of the Redis Pods (feature disabled by default). In case the master crashes, a new Redis node will be elected as master. In order to query the current master (no redis master service is exposed), you need to query first the Sentinel cluster. Find more information [in this section](#master-slave-with-sentinel). diff --git a/dependency_charts/redis/ci/extra-flags-values.yaml b/dependency_charts/redis/ci/extra-flags-values.yaml new file mode 100755 index 0000000..71132f7 --- /dev/null +++ b/dependency_charts/redis/ci/extra-flags-values.yaml @@ -0,0 +1,11 @@ +master: + extraFlags: + - --maxmemory-policy allkeys-lru + persistence: + enabled: false +slave: + extraFlags: + - --maxmemory-policy allkeys-lru + persistence: + enabled: false +usePassword: false diff --git a/dependency_charts/redis/ci/production-sentinel-values.yaml b/dependency_charts/redis/ci/production-sentinel-values.yaml new file mode 100755 index 0000000..afa5c48 --- /dev/null +++ b/dependency_charts/redis/ci/production-sentinel-values.yaml @@ -0,0 +1,682 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +global: + # imageRegistry: myRegistryName + # imagePullSecrets: + # - myRegistryKeySecretName + # storageClass: myStorageClass + redis: {} + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.9-debian-10-r0 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## String to partially override redis.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override redis.fullname template +## +# fullnameOverride: + +## Cluster settings +cluster: + enabled: true + slaveCount: 3 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: true + ## Require password authentication on the sentinel itself + ## ref: https://redis.io/topics/sentinel + usePassword: true + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 5.0.9-debian-10-r0 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Additional Redis configuration for the sentinel nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Enable or disable static sentinel IDs for each replicas + ## If disabled each sentinel will generate a random id at startup + ## If enabled, each replicas will have a constant ID on each start-up + ## + staticID: false + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + customLivenessProbe: {} + customReadinessProbe: {} + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + +## Specifies the Kubernetes Cluster's Domain Name. +## +clusterDomain: cluster.local + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: true + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + + ## Allow connections from other namespacess. Just set label for namespace and set label for pods (optional). + ## + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## sysctl settings for master and slave pods + ## + ## Uncomment the setting below to increase the net.core.somaxconn value + ## + # sysctls: + # - name: net.core.somaxconn + # value: "10000" + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume (Redis Master) +persistence: + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: + +# Redis port +redisPort: 6379 + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the master nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + ## + priorityClassName: {} + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Redis slave port + port: 6379 + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the slave nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.5.3-debian-10-r14 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + prometheusRule: + enabled: false + additionalLabels: {} + namespace: "" + ## Redis prometheus rules + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current postgresql service. + # rules: + # - alert: RedisDown + # expr: redis_up{service="{{ template "redis.fullname" . }}-metrics"} == 0 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} down + # description: Redis instance {{ "{{ $labels.instance }}" }} is down + # - alert: RedisMemoryHigh + # expr: > + # redis_memory_used_bytes{service="{{ template "redis.fullname" . }}-metrics"} * 100 + # / + # redis_memory_max_bytes{service="{{ template "redis.fullname" . }}-metrics"} + # > 90 =< 100 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} is using too much memory + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + # - alert: RedisKeyEviction + # expr: | + # increase(redis_evicted_keys_total{service="{{ template "redis.fullname" . }}-metrics"}[5m]) > 0 + # for: 1s + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} has evicted keys + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. + rules: [] + + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + labels: {} + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## PodSecurityPolicy configuration +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +podSecurityPolicy: + ## Specifies whether a PodSecurityPolicy should be created + ## + create: false diff --git a/dependency_charts/redis/img/redis-cluster-topology.png b/dependency_charts/redis/img/redis-cluster-topology.png new file mode 100755 index 0000000000000000000000000000000000000000..f0a02a9f8835381302731c9cb000b2835a45e7c9 GIT binary patch literal 11448 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.4-0, <1.7-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiGroup for PodSecurityPolicy. +*/}} +{{- define "podSecurityPolicy.apiGroup" -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "policy" -}} +{{- else -}} +{{- print "extensions" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for PodSecurityPolicy. +*/}} +{{- define "podSecurityPolicy.apiVersion" -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "extensions/v1beta1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Redis image name +*/}} +{{- define "redis.image" -}} +{{- $registryName := .Values.image.registry -}} +{{- $repositoryName := .Values.image.repository -}} +{{- $tag := .Values.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Redis Sentinel image name +*/}} +{{- define "sentinel.image" -}} +{{- $registryName := .Values.sentinel.image.registry -}} +{{- $repositoryName := .Values.sentinel.image.repository -}} +{{- $tag := .Values.sentinel.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper image name (for the metrics image) +*/}} +{{- define "redis.metrics.image" -}} +{{- $registryName := .Values.metrics.image.registry -}} +{{- $repositoryName := .Values.metrics.image.repository -}} +{{- $tag := .Values.metrics.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper image name (for the init container volume-permissions image) +*/}} +{{- define "redis.volumePermissions.image" -}} +{{- $registryName := .Values.volumePermissions.image.registry -}} +{{- $repositoryName := .Values.volumePermissions.image.repository -}} +{{- $tag := .Values.volumePermissions.image.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "redis.tlsCert" -}} +{{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "redis.tlsCertKey" -}} +{{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "redis.tlsCACert" -}} +{{- required "Certificate CA filename is required when TLS in enabled" .Values.tls.certCAFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} + +{{/* +Return the path to the DH params file. +*/}} +{{- define "redis.tlsDHParams" -}} +{{- if .Values.tls.dhParamsFilename -}} +{{- printf "/opt/bitnami/redis/certs/%s" .Values.tls.dhParamsFilename -}} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "redis.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "redis.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "redis.secretName" -}} +{{- if .Values.existingSecret -}} +{{- printf "%s" .Values.existingSecret -}} +{{- else -}} +{{- printf "%s" (include "redis.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password key to be retrieved from Redis secret. +*/}} +{{- define "redis.secretPasswordKey" -}} +{{- if and .Values.existingSecret .Values.existingSecretPasswordKey -}} +{{- printf "%s" .Values.existingSecretPasswordKey -}} +{{- else -}} +{{- printf "redis-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Return Redis password +*/}} +{{- define "redis.password" -}} +{{- if not (empty .Values.global.redis.password) }} + {{- .Values.global.redis.password -}} +{{- else if not (empty .Values.password) -}} + {{- .Values.password -}} +{{- else -}} + {{- randAlphaNum 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Return sysctl image +*/}} +{{- define "redis.sysctl.image" -}} +{{- $registryName := default "docker.io" .Values.sysctlImage.registry -}} +{{- $repositoryName := .Values.sysctlImage.repository -}} +{{- $tag := default "buster" .Values.sysctlImage.tag | toString -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 doesn't support it, so we need to implement this if-else logic. +Also, we can't use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} + {{- if .Values.global.imageRegistry }} + {{- printf "%s/%s:%s" .Values.global.imageRegistry $repositoryName $tag -}} + {{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} + {{- end -}} +{{- else -}} + {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "redis.imagePullSecrets" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +Also, we can not use a single if because lazy evaluation is not an option +*/}} +{{- if .Values.global }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- range .Values.global.imagePullSecrets }} + - name: {{ . }} +{{- end }} +{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.metrics.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.sysctlImage.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- else if or .Values.image.pullSecrets .Values.metrics.image.pullSecrets .Values.sysctlImage.pullSecrets .Values.volumePermissions.image.pullSecrets }} +imagePullSecrets: +{{- range .Values.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.metrics.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.sysctlImage.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- range .Values.volumePermissions.image.pullSecrets }} + - name: {{ . }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* Check if there are rolling tags in the images */}} +{{- define "redis.checkRollingTags" -}} +{{- if and (contains "bitnami/" .Values.image.repository) (not (.Values.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .Values.image.repository }}:{{ .Values.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} +{{- if and (contains "bitnami/" .Values.sentinel.image.repository) (not (.Values.sentinel.image.tag | toString | regexFind "-r\\d+$|sha256:")) }} +WARNING: Rolling tag detected ({{ .Values.sentinel.image.repository }}:{{ .Values.sentinel.image.tag }}), please note that it is strongly recommended to avoid using rolling tags in a production environment. ++info https://docs.bitnami.com/containers/how-to/understand-rolling-tags-containers/ +{{- end }} +{{- end -}} + +{{/* +Return the proper Storage Class for master +*/}} +{{- define "redis.master.storageClass" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +*/}} +{{- if .Values.global -}} + {{- if .Values.global.storageClass -}} + {{- if (eq "-" .Values.global.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.global.storageClass -}} + {{- end -}} + {{- else -}} + {{- if .Values.master.persistence.storageClass -}} + {{- if (eq "-" .Values.master.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.master.persistence.storageClass -}} + {{- if (eq "-" .Values.master.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.master.persistence.storageClass -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Storage Class for slave +*/}} +{{- define "redis.slave.storageClass" -}} +{{/* +Helm 2.11 supports the assignment of a value to a variable defined in a different scope, +but Helm 2.9 and 2.10 does not support it, so we need to implement this if-else logic. +*/}} +{{- if .Values.global -}} + {{- if .Values.global.storageClass -}} + {{- if (eq "-" .Values.global.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.global.storageClass -}} + {{- end -}} + {{- else -}} + {{- if .Values.slave.persistence.storageClass -}} + {{- if (eq "-" .Values.slave.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- if .Values.slave.persistence.storageClass -}} + {{- if (eq "-" .Values.slave.persistence.storageClass) -}} + {{- printf "storageClassName: \"\"" -}} + {{- else }} + {{- printf "storageClassName: %s" .Values.slave.persistence.storageClass -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "redis.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "redis.validateValues.spreadConstraints" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis - spreadConstrainsts K8s version */}} +{{- define "redis.validateValues.spreadConstraints" -}} +{{- if and (semverCompare "<1.16-0" .Capabilities.KubeVersion.GitVersion) .Values.slave.spreadConstraints -}} +redis: spreadConstraints + Pod Topology Spread Constraints are only available on K8s >= 1.16 + Find more information at https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ +{{- end -}} +{{- end -}} + +{{/* +Renders a value that contains template. +Usage: +{{ include "redis.tplValue" (dict "value" .Values.path.to.the.Value "context" $) }} +*/}} +{{- define "redis.tplValue" -}} + {{- if typeIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end -}} diff --git a/dependency_charts/redis/templates/configmap.yaml b/dependency_charts/redis/templates/configmap.yaml new file mode 100755 index 0000000..092fb94 --- /dev/null +++ b/dependency_charts/redis/templates/configmap.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + redis.conf: |- +{{- if .Values.configmap }} + # User-supplied configuration: +{{- tpl .Values.configmap . | nindent 4 }} +{{- end }} + master.conf: |- + dir {{ .Values.master.persistence.path }} +{{- if .Values.master.configmap }} + # User-supplied master configuration: +{{- tpl .Values.master.configmap . | nindent 4 }} +{{- end }} +{{- if .Values.master.disableCommands }} +{{- range .Values.master.disableCommands }} + rename-command {{ . }} "" +{{- end }} +{{- end }} + replica.conf: |- + dir {{ .Values.slave.persistence.path }} + slave-read-only yes +{{- if .Values.slave.configmap }} + # User-supplied slave configuration: +{{- tpl .Values.slave.configmap . | nindent 4 }} +{{- end }} +{{- if .Values.slave.disableCommands }} +{{- range .Values.slave.disableCommands }} + rename-command {{ . }} "" +{{- end }} +{{- end }} +{{- if .Values.sentinel.enabled }} + sentinel.conf: |- + dir "/tmp" + bind 0.0.0.0 + port {{ .Values.sentinel.port }} + sentinel monitor {{ .Values.sentinel.masterSet }} {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{ .Values.redisPort }} {{ .Values.sentinel.quorum }} + sentinel down-after-milliseconds {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.downAfterMilliseconds }} + sentinel failover-timeout {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.failoverTimeout }} + sentinel parallel-syncs {{ .Values.sentinel.masterSet }} {{ .Values.sentinel.parallelSyncs }} +{{- if .Values.sentinel.configmap }} + # User-supplied sentinel configuration: +{{- tpl .Values.sentinel.configmap . | nindent 4 }} +{{- end }} +{{- end }} diff --git a/dependency_charts/redis/templates/headless-svc.yaml b/dependency_charts/redis/templates/headless-svc.yaml new file mode 100755 index 0000000..549a05d --- /dev/null +++ b/dependency_charts/redis/templates/headless-svc.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: redis + port: {{ .Values.redisPort }} + targetPort: redis + {{- if .Values.sentinel.enabled }} + - name: redis-sentinel + port: {{ .Values.sentinel.port }} + targetPort: redis-sentinel + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} diff --git a/dependency_charts/redis/templates/health-configmap.yaml b/dependency_charts/redis/templates/health-configmap.yaml new file mode 100755 index 0000000..35819e0 --- /dev/null +++ b/dependency_charts/redis/templates/health-configmap.yaml @@ -0,0 +1,201 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "redis.fullname" . }}-health + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +data: + ping_readiness_local.sh: |- + #!/bin/bash +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} +{{- if .Values.usePassword }} + no_auth_warning=$([[ "$(redis-cli --version)" =~ (redis-cli 5.*) ]] && echo --no-auth-warning) +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD $no_auth_warning \ +{{- end }} + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_TLS_PORT \ + --tls \ + --cacert {{ template "redis.tlsCACert" . }} \ + {{- if .Values.tls.authClients }} + --cert {{ template "redis.tlsCert" . }} \ + --key {{ template "redis.tlsCertKey" . }} \ + {{- end }} +{{- else }} + -p $REDIS_PORT \ +{{- end }} + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_local.sh: |- + #!/bin/bash +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} +{{- if .Values.usePassword }} + no_auth_warning=$([[ "$(redis-cli --version)" =~ (redis-cli 5.*) ]] && echo --no-auth-warning) +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD $no_auth_warning \ +{{- end }} + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_TLS_PORT \ + --tls \ + --cacert {{ template "redis.tlsCACert" . }} \ + {{- if .Values.tls.authClients }} + --cert {{ template "redis.tlsCert" . }} \ + --key {{ template "redis.tlsCertKey" . }} \ + {{- end }} +{{- else }} + -p $REDIS_PORT \ +{{- end }} + ping + ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi +{{- if .Values.sentinel.enabled }} + ping_sentinel.sh: |- + #!/bin/bash +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux +{{- end }} +{{- if .Values.usePassword }} + no_auth_warning=$([[ "$(redis-cli --version)" =~ (redis-cli 5.*) ]] && echo --no-auth-warning) +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_PASSWORD $no_auth_warning \ +{{- end }} + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_SENTINEL_TLS_PORT_NUMBER \ + --tls \ + --cacert {{ template "redis.tlsCACert" . }} \ + {{- if .Values.tls.authClients }} + --cert {{ template "redis.tlsCert" . }} \ + --key {{ template "redis.tlsCertKey" . }} \ + {{- end }} +{{- else }} + -p $REDIS_SENTINEL_PORT \ +{{- end }} + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + parse_sentinels.awk: |- + /ip/ {FOUND_IP=1} + /port/ {FOUND_PORT=1} + /runid/ {FOUND_RUNID=1} + !/ip|port|runid/ { + if (FOUND_IP==1) { + IP=$1; FOUND_IP=0; + } + else if (FOUND_PORT==1) { + PORT=$1; + FOUND_PORT=0; + } else if (FOUND_RUNID==1) { + printf "\nsentinel known-sentinel {{ .Values.sentinel.masterSet }} %s %s %s", IP, PORT, $0; FOUND_RUNID=0; + } + } +{{- end }} + ping_readiness_master.sh: |- + #!/bin/bash +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux +{{- end }} +{{- if .Values.usePassword }} + no_auth_warning=$([[ "$(redis-cli --version)" =~ (redis-cli 5.*) ]] && echo --no-auth-warning) +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_MASTER_PASSWORD $no_auth_warning \ +{{- end }} + -h $REDIS_MASTER_HOST \ + -p $REDIS_MASTER_PORT_NUMBER \ +{{- if .Values.tls.enabled }} + --tls \ + --cacert {{ template "redis.tlsCACert" . }} \ + {{- if .Values.tls.authClients }} + --cert {{ template "redis.tlsCert" . }} \ + --key {{ template "redis.tlsCertKey" . }} \ + {{- end }} +{{- end }} + ping + ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_master.sh: |- + #!/bin/bash +{{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux +{{- end }} +{{- if .Values.usePassword }} + no_auth_warning=$([[ "$(redis-cli --version)" =~ (redis-cli 5.*) ]] && echo --no-auth-warning) +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ +{{- if .Values.usePassword }} + -a $REDIS_MASTER_PASSWORD $no_auth_warning \ +{{- end }} + -h $REDIS_MASTER_HOST \ + -p $REDIS_MASTER_PORT_NUMBER \ +{{- if .Values.tls.enabled }} + --tls \ + --cacert {{ template "redis.tlsCACert" . }} \ + {{- if .Values.tls.authClients }} + --cert {{ template "redis.tlsCert" . }} \ + --key {{ template "redis.tlsCertKey" . }} \ + {{- end }} +{{- end }} + ping + ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + ping_readiness_local_and_master.sh: |- + script_dir="$(dirname "$0")" + exit_status=0 + "$script_dir/ping_readiness_local.sh" $1 || exit_status=$? + "$script_dir/ping_readiness_master.sh" $1 || exit_status=$? + exit $exit_status + ping_liveness_local_and_master.sh: |- + script_dir="$(dirname "$0")" + exit_status=0 + "$script_dir/ping_liveness_local.sh" $1 || exit_status=$? + "$script_dir/ping_liveness_master.sh" $1 || exit_status=$? + exit $exit_status diff --git a/dependency_charts/redis/templates/metrics-prometheus.yaml b/dependency_charts/redis/templates/metrics-prometheus.yaml new file mode 100755 index 0000000..551059a --- /dev/null +++ b/dependency_charts/redis/templates/metrics-prometheus.yaml @@ -0,0 +1,33 @@ +{{- if and (.Values.metrics.enabled) (.Values.metrics.serviceMonitor.enabled) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "redis.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- range $key, $value := .Values.metrics.serviceMonitor.selector }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + endpoints: + - port: metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + app.kubernetes.io/component: "metrics" + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end -}} diff --git a/dependency_charts/redis/templates/metrics-svc.yaml b/dependency_charts/redis/templates/metrics-svc.yaml new file mode 100755 index 0000000..f30e1fd --- /dev/null +++ b/dependency_charts/redis/templates/metrics-svc.yaml @@ -0,0 +1,31 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + app.kubernetes.io/component: "metrics" + {{- if .Values.metrics.service.labels -}} + {{- toYaml .Values.metrics.service.labels | nindent 4 }} + {{- end -}} + {{- if .Values.metrics.service.annotations }} + annotations: {{- toYaml .Values.metrics.service.annotations | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + {{ if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{- end }} + ports: + - name: metrics + port: 9121 + targetPort: metrics + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/dependency_charts/redis/templates/networkpolicy.yaml b/dependency_charts/redis/templates/networkpolicy.yaml new file mode 100755 index 0000000..c56a9d3 --- /dev/null +++ b/dependency_charts/redis/templates/networkpolicy.yaml @@ -0,0 +1,74 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "networkPolicy.apiVersion" . }} +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + {{- if .Values.cluster.enabled }} + policyTypes: + - Ingress + - Egress + egress: + # Allow dns resolution + - ports: + - port: 53 + protocol: UDP + # Allow outbound connections to other cluster pods + - ports: + - port: {{ .Values.redisPort }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.port }} + {{- end }} + to: + - podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + {{- end }} + ingress: + # Allow inbound connections + - ports: + - port: {{ .Values.redisPort }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.port }} + {{- end }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "redis.fullname" . }}-client: "true" + - podSelector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + {{- if .Values.networkPolicy.ingressNSMatchLabels }} + - namespaceSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- if .Values.networkPolicy.ingressNSPodMatchLabels }} + podSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSPodMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes for metrics + - ports: + - port: 9121 + {{- end }} +{{- end }} diff --git a/dependency_charts/redis/templates/pdb.yaml b/dependency_charts/redis/templates/pdb.yaml new file mode 100755 index 0000000..8021430 --- /dev/null +++ b/dependency_charts/redis/templates/pdb.yaml @@ -0,0 +1,21 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "redis.fullname" . }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} +spec: + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} + selector: + matchLabels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/dependency_charts/redis/templates/prometheusrule.yaml b/dependency_charts/redis/templates/prometheusrule.yaml new file mode 100755 index 0000000..9076a97 --- /dev/null +++ b/dependency_charts/redis/templates/prometheusrule.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "redis.fullname" . }} + {{- if .Values.metrics.prometheusRule.namespace }} + namespace: {{ .Values.metrics.prometheusRule.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +{{- with .Values.metrics.prometheusRule.additionalLabels }} +{{- toYaml . | nindent 4 }} +{{- end }} +spec: +{{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "redis.name" $ }} + rules: {{- tpl (toYaml .) $ | nindent 8 }} +{{- end }} +{{- end }} diff --git a/dependency_charts/redis/templates/psp.yaml b/dependency_charts/redis/templates/psp.yaml new file mode 100755 index 0000000..08e0840 --- /dev/null +++ b/dependency_charts/redis/templates/psp.yaml @@ -0,0 +1,43 @@ +{{- if .Values.podSecurityPolicy.create }} +apiVersion: {{ template "podSecurityPolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + allowPrivilegeEscalation: false + fsGroup: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.securityContext.fsGroup }} + max: {{ .Values.securityContext.fsGroup }} + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: false + requiredDropCapabilities: + - ALL + runAsUser: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.securityContext.runAsUser }} + max: {{ .Values.securityContext.runAsUser }} + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.securityContext.runAsUser }} + max: {{ .Values.securityContext.runAsUser }} + volumes: + - 'configMap' + - 'secret' + - 'emptyDir' + - 'persistentVolumeClaim' +{{- end }} diff --git a/dependency_charts/redis/templates/redis-master-statefulset.yaml b/dependency_charts/redis/templates/redis-master-statefulset.yaml new file mode 100755 index 0000000..f0c2eb2 --- /dev/null +++ b/dependency_charts/redis/templates/redis-master-statefulset.yaml @@ -0,0 +1,524 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "redis.fullname" . }}-master + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: master + serviceName: {{ template "redis.fullname" . }}-headless + template: + metadata: + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + role: master + {{- if .Values.master.podLabels }} + {{- toYaml .Values.master.podLabels | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- toYaml .Values.metrics.podLabels | nindent 8 }} + {{- end }} + annotations: + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.master.podAnnotations }} + {{- toYaml .Values.master.podAnnotations | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- toYaml .Values.metrics.podAnnotations | nindent 8 }} + {{- end }} + spec: + {{- include "redis.imagePullSecrets" . | nindent 6 }} + {{- if .Values.securityContext.enabled }} + securityContext: + fsGroup: {{ .Values.securityContext.fsGroup }} + {{- if .Values.securityContext.sysctls }} + sysctls: {{- toYaml .Values.securityContext.sysctls | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ template "redis.serviceAccountName" . }} + {{- if .Values.master.priorityClassName }} + priorityClassName: "{{ .Values.master.priorityClassName }}" + {{- end }} + {{- with .Values.master.affinity }} + affinity: {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- if .Values.master.nodeSelector }} + nodeSelector: {{- toYaml .Values.master.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.master.tolerations }} + tolerations: {{- toYaml .Values.master.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.master.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.master.shareProcessNamespace }} + {{- end }} + {{- if .Values.master.schedulerName }} + schedulerName: {{ .Values.master.schedulerName }} + {{- end }} + containers: + - name: {{ template "redis.name" . }} + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + {{- if (eq (.Values.securityContext.runAsUser | int) 0) }} + useradd redis + chown -R redis {{ .Values.master.persistence.path }} + {{- end }} + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis/etc/master.conf ]];then + cp /opt/bitnami/redis/mounted-etc/master.conf /opt/bitnami/redis/etc/master.conf + fi + if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then + cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf + fi + {{- if .Values.tls.enabled }} + ARGS=("--port" "0") + ARGS+=("--tls-port" "${REDIS_TLS_PORT}") + ARGS+=("--tls-cert-file" "${REDIS_TLS_CERT_FILE}") + ARGS+=("--tls-key-file" "${REDIS_TLS_KEY_FILE}") + ARGS+=("--tls-ca-cert-file" "${REDIS_TLS_CA_FILE}") + ARGS+=("--tls-auth-clients" "${REDIS_TLS_AUTH_CLIENTS}") + {{- if .Values.tls.dhParamsFilename }} + ARGS+=("--tls-dh-params-file" "${REDIS_TLS_DH_PARAMS_FILE}") + {{- end }} + {{- else }} + ARGS=("--port" "${REDIS_PORT}") + {{- end }} + {{- if .Values.usePassword }} + ARGS+=("--requirepass" "${REDIS_PASSWORD}") + ARGS+=("--masterauth" "${REDIS_PASSWORD}") + {{- else }} + ARGS+=("--protected-mode" "no") + {{- end }} + ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") + ARGS+=("--include" "/opt/bitnami/redis/etc/master.conf") + {{- if .Values.master.extraFlags }} + {{- range .Values.master.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + {{- if .Values.master.command }} + {{ .Values.master.command }} ${ARGS[@]} + {{- else }} + redis-server "${ARGS[@]}" + {{- end }} + env: + - name: REDIS_REPLICATION_MODE + value: master + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.redisPort | quote }} + - name: REDIS_TLS_AUTH_CLIENTS + value: {{ ternary "yes" "no" .Values.tls.authClients | quote }} + - name: REDIS_TLS_CERT_FILE + value: {{ template "redis.tlsCert" . }} + - name: REDIS_TLS_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_TLS_CA_FILE + value: {{ template "redis.tlsCACert" . }} + {{- if .Values.tls.dhParamsFilename }} + - name: REDIS_TLS_DH_PARAMS_FILE + value: {{ template "redis.tlsDHParams" . }} + {{- end }} + {{- else }} + - name: REDIS_PORT + value: {{ .Values.redisPort | quote }} + {{- end }} + {{- if .Values.master.extraEnvVars }} + {{- include "redis.tplValue" (dict "value" .Values.master.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.master.extraEnvVarsCM .Values.master.extraEnvVarsSecret }} + envFrom: + {{- if .Values.master.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.master.extraEnvVarsCM }} + {{- end }} + {{- if .Values.master.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.master.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: redis + containerPort: {{ .Values.redisPort }} + {{- if .Values.master.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} + # One second longer than command timeout should prevent generation of zombie processes. + timeoutSeconds: {{ add1 .Values.master.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.master.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.master.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_liveness_local.sh {{ .Values.master.livenessProbe.timeoutSeconds }} + {{- else if .Values.master.customLivenessProbe }} + livenessProbe: {{- toYaml .Values.master.customLivenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.master.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.master.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.master.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.master.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_readiness_local.sh {{ .Values.master.readinessProbe.timeoutSeconds }} + {{- else if .Values.master.customReadinessProbe }} + readinessProbe: {{- toYaml .Values.master.customReadinessProbe | nindent 12 }} + {{- end }} + resources: {{- toYaml .Values.master.resources | nindent 12 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis/mounted-etc + - name: redis-tmp-conf + mountPath: /opt/bitnami/redis/etc/ + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel + image: "{{ template "sentinel.image" . }}" + imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then + cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.usePassword }} + printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.sentinel.usePassword }} + printf "\nrequirepass $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + {{- end }} + {{- if .Values.sentinel.staticID }} + printf "\nsentinel myid $(echo $HOSTNAME | openssl sha1 | awk '{ print $2 }')" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + fi + echo "Getting information about current running sentinels" + # Get information from existing sentinels + existing_sentinels=$(timeout -s 3 {{ .Values.sentinel.initialCheckTimeout }} redis-cli --raw -h {{ template "redis.fullname" . }} -a "$REDIS_PASSWORD" -p {{ .Values.sentinel.service.sentinelPort }} SENTINEL sentinels {{ .Values.sentinel.masterSet }}) + echo "$existing_sentinels" | awk -f /health/parse_sentinels.awk | tee -a /opt/bitnami/redis-sentinel/etc/sentinel.conf + + {{- if .Values.tls.enabled }} + ARGS=("--port" "0") + ARGS+=("--tls-port" "${REDIS_SENTINEL_TLS_PORT_NUMBER}") + ARGS+=("--tls-cert-file" "${REDIS_SENTINEL_TLS_CERT_FILE}") + ARGS+=("--tls-key-file" "${REDIS_SENTINEL_TLS_KEY_FILE}") + ARGS+=("--tls-ca-cert-file" "${REDIS_SENTINEL_TLS_CA_FILE}") + ARGS+=("--tls-auth-clients" "${REDIS_SENTINEL_TLS_AUTH_CLIENTS}") + ARGS+=("--tls-replication" "yes") + {{- if .Values.tls.dhParamsFilename }} + ARGS+=("--tls-dh-params-file" "${REDIS_SENTINEL_TLS_DH_PARAMS_FILE}") + {{- end }} + {{- end }} + + redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel {{- if .Values.tls.enabled }} "${ARGS[@]}" {{- end }} + env: + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_SENTINEL_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_SENTINEL_TLS_PORT_NUMBER + value: {{ .Values.sentinel.port | quote }} + - name: REDIS_SENTINEL_TLS_AUTH_CLIENTS + value: {{ ternary "yes" "no" .Values.tls.authClients | quote }} + - name: REDIS_SENTINEL_TLS_CERT_FILE + value: {{ template "redis.tlsCert" . }} + - name: REDIS_SENTINEL_TLS_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_SENTINEL_TLS_CA_FILE + value: {{ template "redis.tlsCACert" . }} + {{- if .Values.tls.dhParamsFilename }} + - name: REDIS_SENTINEL_TLS_DH_PARAMS_FILE + value: {{ template "redis.dhParams" . }} + {{- end }} + {{- else }} + - name: REDIS_SENTINEL_PORT + value: {{ .Values.sentinel.port | quote }} + {{- end }} + ports: + - name: redis-sentinel + containerPort: {{ .Values.sentinel.port }} + {{- if .Values.sentinel.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.sentinel.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- else if .Values.sentinel.customLivenessProbe }} + livenessProbe: {{- toYaml .Values.sentinel.customLivenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.sentinel.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.sentinel.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.readinessProbe.timeoutSeconds }} + {{- else if .Values.sentinel.customReadinessProbe }} + readinessProbe: {{- toYaml .Values.sentinel.customReadinessProbe | nindent 12 }} + {{- end }} + resources: {{- toYaml .Values.sentinel.resources | nindent 12 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis-sentinel/mounted-etc + - name: sentinel-tmp-conf + mountPath: /opt/bitnami/redis-sentinel/etc/ + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + command: + - /bin/bash + - -c + - | + if [[ -f '/secrets/redis-password' ]]; then + export REDIS_PASSWORD=$(cat /secrets/redis-password) + fi + redis_exporter{{- range $key, $value := .Values.metrics.extraArgs }} --{{ $key }}={{ $value }}{{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "redis.fullname" . }} + {{- if and .Values.usePassword (not .Values.usePasswordFile) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis.tlsCert" . }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + ports: + - name: metrics + containerPort: 9121 + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and ( and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) ) .Values.securityContext.enabled) }} + {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} + initContainers: + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: "{{ template "redis.volumePermissions.image" . }}" + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.master.persistence.path }}"] + securityContext: + runAsUser: 0 + resources: {{- toYaml .Values.volumePermissions.resources | nindent 10 }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + {{- end }} + {{- if .Values.sysctlImage.enabled }} + - name: init-sysctl + image: {{ template "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} + resources: {{- toYaml .Values.sysctlImage.resources | nindent 10 }} + {{- if .Values.sysctlImage.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + command: {{- toYaml .Values.sysctlImage.command | nindent 10 }} + securityContext: + privileged: true + runAsUser: 0 + {{- end }} + {{- end }} + volumes: + - name: health + configMap: + name: {{ template "redis.fullname" . }}-health + defaultMode: 0755 + {{- if .Values.usePasswordFile }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ template "redis.fullname" . }} + {{- if not .Values.master.persistence.enabled }} + - name: "redis-data" + emptyDir: {} + {{- else }} + {{- if .Values.persistence.existingClaim }} + - name: "redis-data" + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim }} + {{- end }} + {{- end }} + {{- if .Values.sysctlImage.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: redis-tmp-conf + emptyDir: {} + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel-tmp-conf + emptyDir: {} + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ required "A secret containing the certificates for the TLS traffic is required when TLS in enabled" .Values.tls.certificatesSecret }} + defaultMode: 256 + {{- end }} + {{- if and .Values.master.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: master + spec: + accessModes: + {{- range .Values.master.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.master.persistence.size | quote }} + {{ include "redis.master.storageClass" . }} + selector: + {{- if .Values.master.persistence.matchLabels }} + matchLabels: {{- toYaml .Values.master.persistence.matchLabels | nindent 12 }} + {{- end -}} + {{- if .Values.master.persistence.matchExpressions }} + matchExpressions: {{- toYaml .Values.master.persistence.matchExpressions | nindent 12 }} + {{- end -}} + {{- end }} + updateStrategy: + type: {{ .Values.master.statefulset.updateStrategy }} + {{- if .Values.master.statefulset.rollingUpdatePartition }} + {{- if (eq "Recreate" .Values.master.statefulset.updateStrategy) }} + rollingUpdate: null + {{- else }} + rollingUpdate: + partition: {{ .Values.master.statefulset.rollingUpdatePartition }} + {{- end }} + {{- end }} diff --git a/dependency_charts/redis/templates/redis-master-svc.yaml b/dependency_charts/redis/templates/redis-master-svc.yaml new file mode 100755 index 0000000..09eab2a --- /dev/null +++ b/dependency_charts/redis/templates/redis-master-svc.yaml @@ -0,0 +1,40 @@ +{{- if not .Values.sentinel.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-master + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.master.service.labels -}} + {{- toYaml .Values.master.service.labels | nindent 4 }} + {{- end -}} +{{- if .Values.master.service.annotations }} + annotations: {{- toYaml .Values.master.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.master.service.type }} + {{- if and (eq .Values.master.service.type "LoadBalancer") .Values.master.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.master.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.master.service.type "LoadBalancer") .Values.master.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- with .Values.master.service.loadBalancerSourceRanges }} +{{- toYaml . | nindent 4 }} +{{- end }} + {{- end }} + ports: + - name: redis + port: {{ .Values.master.service.port }} + targetPort: redis + {{- if .Values.master.service.nodePort }} + nodePort: {{ .Values.master.service.nodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: master +{{- end }} diff --git a/dependency_charts/redis/templates/redis-role.yaml b/dependency_charts/redis/templates/redis-role.yaml new file mode 100755 index 0000000..c741268 --- /dev/null +++ b/dependency_charts/redis/templates/redis-role.yaml @@ -0,0 +1,22 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +rules: +{{- if .Values.podSecurityPolicy.create }} + - apiGroups: ['{{ template "podSecurityPolicy.apiGroup" . }}'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: [{{ template "redis.fullname" . }}] +{{- end -}} +{{- if .Values.rbac.role.rules }} +{{- toYaml .Values.rbac.role.rules | nindent 2 }} +{{- end -}} +{{- end -}} diff --git a/dependency_charts/redis/templates/redis-rolebinding.yaml b/dependency_charts/redis/templates/redis-rolebinding.yaml new file mode 100755 index 0000000..3657f14 --- /dev/null +++ b/dependency_charts/redis/templates/redis-rolebinding.yaml @@ -0,0 +1,19 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "redis.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ template "redis.serviceAccountName" . }} +{{- end -}} diff --git a/dependency_charts/redis/templates/redis-serviceaccount.yaml b/dependency_charts/redis/templates/redis-serviceaccount.yaml new file mode 100755 index 0000000..5c9707f --- /dev/null +++ b/dependency_charts/redis/templates/redis-serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "redis.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- end -}} diff --git a/dependency_charts/redis/templates/redis-slave-statefulset.yaml b/dependency_charts/redis/templates/redis-slave-statefulset.yaml new file mode 100755 index 0000000..b84d3c6 --- /dev/null +++ b/dependency_charts/redis/templates/redis-slave-statefulset.yaml @@ -0,0 +1,543 @@ +{{- if .Values.cluster.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "redis.fullname" . }}-slave + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: +{{- if .Values.slave.updateStrategy }} + strategy: {{- toYaml .Values.slave.updateStrategy | nindent 4 }} +{{- end }} + replicas: {{ .Values.cluster.slaveCount }} + serviceName: {{ template "redis.fullname" . }}-headless + selector: + matchLabels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: slave + template: + metadata: + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + chart: {{ template "redis.chart" . }} + role: slave + {{- if .Values.slave.podLabels }} + {{- toYaml .Values.slave.podLabels | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- toYaml .Values.metrics.podLabels | nindent 8 }} + {{- end }} + annotations: + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.slave.podAnnotations }} + {{- toYaml .Values.slave.podAnnotations | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- toYaml .Values.metrics.podAnnotations | nindent 8 }} + {{- end }} + spec: + {{- include "redis.imagePullSecrets" . | nindent 6 }} + {{- if .Values.securityContext.enabled }} + securityContext: + fsGroup: {{ .Values.securityContext.fsGroup }} + {{- if .Values.securityContext.sysctls }} + sysctls: {{- toYaml .Values.securityContext.sysctls | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ template "redis.serviceAccountName" . }} + {{- if .Values.slave.priorityClassName }} + priorityClassName: "{{ .Values.slave.priorityClassName }}" + {{- end }} + {{- if .Values.slave.nodeSelector }} + nodeSelector: {{- toYaml .Values.slave.nodeSelector | nindent 8 }} + {{- end }} + {{- if .Values.slave.tolerations }} + tolerations: {{- toYaml .Values.slave.tolerations | nindent 8 }} + {{- end }} + {{- if .Values.slave.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.slave.shareProcessNamespace }} + {{- end }} + {{- if .Values.slave.schedulerName }} + schedulerName: {{ .Values.slave.schedulerName }} + {{- end }} + {{- if .Values.master.spreadConstraints }} + topologySpreadConstraints: {{- toYaml .Values.master.spreadConstraints | nindent 8 }} + {{- end }} + {{- with .Values.slave.affinity }} + affinity: {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} + containers: + - name: {{ template "redis.name" . }} + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + {{- if (eq (.Values.securityContext.runAsUser | int) 0) }} + useradd redis + chown -R redis {{ .Values.slave.persistence.path }} + {{- end }} + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ -n $REDIS_MASTER_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_MASTER_PASSWORD_FILE}` + export REDIS_MASTER_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis/etc/replica.conf ]];then + cp /opt/bitnami/redis/mounted-etc/replica.conf /opt/bitnami/redis/etc/replica.conf + fi + if [[ ! -f /opt/bitnami/redis/etc/redis.conf ]];then + cp /opt/bitnami/redis/mounted-etc/redis.conf /opt/bitnami/redis/etc/redis.conf + fi + {{- if .Values.tls.enabled }} + ARGS=("--port" "0") + ARGS+=("--tls-port" "${REDIS_TLS_PORT}") + ARGS+=("--tls-cert-file" "${REDIS_TLS_CERT_FILE}") + ARGS+=("--tls-key-file" "${REDIS_TLS_KEY_FILE}") + ARGS+=("--tls-ca-cert-file" "${REDIS_TLS_CA_FILE}") + ARGS+=("--tls-auth-clients" "${REDIS_TLS_AUTH_CLIENTS}") + ARGS+=("--tls-replication" "yes") + {{- if .Values.tls.dhParamsFilename }} + ARGS+=("--tls-dh-params-file" "${REDIS_TLS_DH_PARAMS_FILE}") + {{- end }} + {{- else }} + ARGS=("--port" "${REDIS_PORT}") + {{- end }} + ARGS+=("--slaveof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}") + {{- if .Values.usePassword }} + ARGS+=("--requirepass" "${REDIS_PASSWORD}") + ARGS+=("--masterauth" "${REDIS_MASTER_PASSWORD}") + {{- else }} + ARGS+=("--protected-mode" "no") + {{- end }} + ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") + ARGS+=("--include" "/opt/bitnami/redis/etc/replica.conf") + {{- if .Values.slave.extraFlags }} + {{- range .Values.slave.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + {{- if .Values.slave.command }} + {{ .Values.slave.command }} "${ARGS[@]}" + {{- else }} + redis-server "${ARGS[@]}" + {{- end }} + env: + - name: REDIS_REPLICATION_MODE + value: slave + - name: REDIS_MASTER_HOST + value: {{ template "redis.fullname" . }}-master-0.{{ template "redis.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + - name: REDIS_MASTER_PORT_NUMBER + value: {{ .Values.redisPort | quote }} + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + - name: REDIS_MASTER_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + - name: REDIS_MASTER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.redisPort | quote }} + - name: REDIS_TLS_AUTH_CLIENTS + value: {{ ternary "yes" "no" .Values.tls.authClients | quote }} + - name: REDIS_TLS_CERT_FILE + value: {{ template "redis.tlsCert" . }} + - name: REDIS_TLS_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_TLS_CA_FILE + value: {{ template "redis.tlsCACert" . }} + {{- if .Values.tls.dhParamsFilename }} + - name: REDIS_TLS_DH_PARAMS_FILE + value: {{ template "redis.tlsDHParams" . }} + {{- end }} + {{- else }} + - name: REDIS_PORT + value: {{ .Values.redisPort | quote }} + {{- end }} + {{- if .Values.slave.extraEnvVars }} + {{- include "redis.tplValue" (dict "value" .Values.slave.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.slave.extraEnvVarsCM .Values.slave.extraEnvVarsSecret }} + envFrom: + {{- if .Values.slave.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.slave.extraEnvVarsCM }} + {{- end }} + {{- if .Values.slave.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.slave.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: redis + containerPort: {{ .Values.redisPort }} + {{- if .Values.slave.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.slave.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.slave.livenessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.slave.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.slave.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.slave.livenessProbe.failureThreshold}} + exec: + command: + - sh + - -c + {{- if .Values.sentinel.enabled }} + - /health/ping_liveness_local.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- else }} + - /health/ping_liveness_local_and_master.sh {{ .Values.slave.livenessProbe.timeoutSeconds }} + {{- end }} + {{- else if .Values.slave.customLivenessProbe }} + livenessProbe: {{- toYaml .Values.slave.customLivenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.slave.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.slave.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.slave.readinessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.slave.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.slave.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.slave.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + {{- if .Values.sentinel.enabled }} + - /health/ping_readiness_local.sh {{ .Values.slave.readinessProbe.timeoutSeconds }} + {{- else }} + - /health/ping_readiness_local_and_master.sh {{ .Values.slave.readinessProbe.timeoutSeconds }} + {{- end }} + {{- else if .Values.slave.customReadinessProbe }} + readinessProbe: {{- toYaml .Values.slave.customReadinessProbe | nindent 12 }} + {{- end }} + resources: {{- toYaml .Values.slave.resources | nindent 12 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: /data + - name: config + mountPath: /opt/bitnami/redis/mounted-etc + - name: redis-tmp-conf + mountPath: /opt/bitnami/redis/etc + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if and .Values.cluster.enabled .Values.sentinel.enabled }} + - name: sentinel + image: {{ template "sentinel.image" . }} + imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} + {{- if .Values.securityContext.enabled }} + securityContext: + runAsUser: {{ .Values.securityContext.runAsUser }} + {{- end }} + command: + - /bin/bash + - -c + - | + if [[ -n $REDIS_PASSWORD_FILE ]]; then + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDIS_PASSWORD=$password_aux + fi + if [[ ! -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]];then + cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.usePassword }} + printf "\nsentinel auth-pass {{ .Values.sentinel.masterSet }} $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.sentinel.usePassword }} + printf "\nrequirepass $REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + {{- end }} + {{- if .Values.sentinel.staticID }} + printf "\nsentinel myid $(echo $HOSTNAME | openssl sha1 | awk '{ print $2 }')" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + fi + + {{- if .Values.tls.enabled }} + ARGS=("--port" "0") + ARGS+=("--tls-port" "${REDIS_SENTINEL_TLS_PORT_NUMBER}") + ARGS+=("--tls-cert-file" "${REDIS_SENTINEL_TLS_CERT_FILE}") + ARGS+=("--tls-key-file" "${REDIS_SENTINEL_TLS_KEY_FILE}") + ARGS+=("--tls-ca-cert-file" "${REDIS_SENTINEL_TLS_CA_FILE}") + ARGS+=("--tls-replication" "yes") + ARGS+=("--tls-auth-clients" "${REDIS_SENTINEL_TLS_AUTH_CLIENTS}") + {{- if .Values.tls.dhParamsFilename }} + ARGS+=("--tls-dh-params-file" "${REDIS_SENTINEL_TLS_DH_PARAMS_FILE}") + {{- end }} + {{- end }} + + redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel {{- if .Values.tls.enabled }} "${ARGS[@]}" {{- end }} + env: + {{- if .Values.usePassword }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_SENTINEL_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_SENTINEL_TLS_PORT_NUMBER + value: {{ .Values.sentinel.port | quote }} + - name: REDIS_SENTINEL_TLS_AUTH_CLIENTS + value: {{ ternary "yes" "no" .Values.tls.authClients | quote }} + - name: REDIS_SENTINEL_TLS_CERT_FILE + value: {{ template "redis.tlsCert" . }} + - name: REDIS_SENTINEL_TLS_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_SENTINEL_TLS_CA_FILE + value: {{ template "redis.tlsCACert" . }} + {{- if .Values.tls.dhParamsFilename }} + - name: REDIS_SENTINEL_TLS_DH_PARAMS_FILE + value: {{ template "redis.dhParams" . }} + {{- end }} + {{- else }} + - name: REDIS_SENTINEL_PORT + value: {{ .Values.sentinel.port | quote }} + {{- end }} + ports: + - name: redis-sentinel + containerPort: {{ .Values.sentinel.port }} + {{- if .Values.sentinel.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.sentinel.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- else if .Values.sentinel.customLivenessProbe }} + livenessProbe: {{- toYaml .Values.sentinel.customLivenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.sentinel.readinessProbe.enabled}} + readinessProbe: + initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.sentinel.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.readinessProbe.timeoutSeconds }} + {{- else if .Values.sentinel.customReadinessProbe }} + readinessProbe: {{- toYaml .Values.sentinel.customReadinessProbe | nindent 12 }} + {{- end }} + resources: {{- toYaml .Values.sentinel.resources | nindent 12 }} + volumeMounts: + - name: health + mountPath: /health + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis-sentinel/mounted-etc + - name: sentinel-tmp-conf + mountPath: /opt/bitnami/redis-sentinel/etc + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + command: + - /bin/bash + - -c + - | + if [[ -f '/secrets/redis-password' ]]; then + export REDIS_PASSWORD=$(cat /secrets/redis-password) + fi + redis_exporter{{- range $key, $value := .Values.metrics.extraArgs }} --{{ $key }}={{ $value }}{{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "redis.fullname" . }} + {{- if and .Values.usePassword (not .Values.usePasswordFile) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis.tlsCert" . }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + ports: + - name: metrics + containerPort: 9121 + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled (and .Values.slave.persistence.enabled .Values.securityContext.enabled) }} + {{- if or $needsVolumePermissions .Values.sysctlImage.enabled }} + initContainers: + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: {{ template "redis.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: ["/bin/chown", "-R", "{{ .Values.securityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }}", "{{ .Values.slave.persistence.path }}"] + securityContext: + runAsUser: 0 + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.slave.persistence.path }} + subPath: {{ .Values.slave.persistence.subPath }} + {{- end }} + {{- if .Values.sysctlImage.enabled }} + - name: init-sysctl + image: {{ template "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctlImage.pullPolicy | quote }} + resources: {{- toYaml .Values.sysctlImage.resources | nindent 12 }} + {{- if .Values.sysctlImage.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + command: {{- toYaml .Values.sysctlImage.command | nindent 12 }} + securityContext: + privileged: true + runAsUser: 0 + {{- end }} + {{- end }} + volumes: + - name: health + configMap: + name: {{ template "redis.fullname" . }}-health + defaultMode: 0755 + {{- if .Values.usePasswordFile }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ template "redis.fullname" . }} + {{- if .Values.sysctlImage.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: sentinel-tmp-conf + emptyDir: {} + - name: redis-tmp-conf + emptyDir: {} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ required "A secret containing the certificates for the TLS traffic is required when TLS in enabled" .Values.tls.certificatesSecret }} + defaultMode: 256 + {{- end }} + {{- if not .Values.slave.persistence.enabled }} + - name: redis-data + emptyDir: {} + {{- else }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: slave + spec: + accessModes: + {{- range .Values.slave.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.slave.persistence.size | quote }} + {{ include "redis.slave.storageClass" . }} + selector: + {{- if .Values.slave.persistence.matchLabels }} + matchLabels: {{- toYaml .Values.slave.persistence.matchLabels | nindent 12 }} + {{- end -}} + {{- if .Values.slave.persistence.matchExpressions }} + matchExpressions: {{- toYaml .Values.slave.persistence.matchExpressions | nindent 12 }} + {{- end -}} + {{- end }} + updateStrategy: + type: {{ .Values.slave.statefulset.updateStrategy }} + {{- if .Values.slave.statefulset.rollingUpdatePartition }} + {{- if (eq "Recreate" .Values.slave.statefulset.updateStrategy) }} + rollingUpdate: null + {{- else }} + rollingUpdate: + partition: {{ .Values.slave.statefulset.rollingUpdatePartition }} + {{- end }} + {{- end }} +{{- end }} diff --git a/dependency_charts/redis/templates/redis-slave-svc.yaml b/dependency_charts/redis/templates/redis-slave-svc.yaml new file mode 100755 index 0000000..dab36c3 --- /dev/null +++ b/dependency_charts/redis/templates/redis-slave-svc.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.cluster.enabled (not .Values.sentinel.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }}-slave + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.slave.service.labels -}} + {{- toYaml .Values.slave.service.labels | nindent 4 }} + {{- end -}} +{{- if .Values.slave.service.annotations }} + annotations: {{- toYaml .Values.slave.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.slave.service.type }} + {{- if and (eq .Values.slave.service.type "LoadBalancer") .Values.slave.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.slave.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.slave.service.type "LoadBalancer") .Values.slave.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- with .Values.slave.service.loadBalancerSourceRanges }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + ports: + - name: redis + port: {{ .Values.slave.service.port }} + targetPort: redis + {{- if .Values.slave.service.nodePort }} + nodePort: {{ .Values.slave.service.nodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} + role: slave +{{- end }} diff --git a/dependency_charts/redis/templates/redis-with-sentinel-svc.yaml b/dependency_charts/redis/templates/redis-with-sentinel-svc.yaml new file mode 100755 index 0000000..f587373 --- /dev/null +++ b/dependency_charts/redis/templates/redis-with-sentinel-svc.yaml @@ -0,0 +1,40 @@ +{{- if .Values.sentinel.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.sentinel.service.labels }} + {{- toYaml .Values.sentinel.service.labels | nindent 4 }} + {{- end }} +{{- if .Values.sentinel.service.annotations }} + annotations: {{- toYaml .Values.sentinel.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.sentinel.service.type }} + {{ if eq .Values.sentinel.service.type "LoadBalancer" -}} {{ if .Values.sentinel.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.sentinel.service.loadBalancerIP }} + {{ end -}} + {{- end -}} + ports: + - name: redis + port: {{ .Values.sentinel.service.redisPort }} + targetPort: redis + {{- if .Values.sentinel.service.redisNodePort }} + nodePort: {{ .Values.sentinel.service.redisNodePort }} + {{- end }} + - name: redis-sentinel + port: {{ .Values.sentinel.service.sentinelPort }} + targetPort: redis-sentinel + {{- if .Values.sentinel.service.sentinelNodePort }} + nodePort: {{ .Values.sentinel.service.sentinelNodePort }} + {{- end }} + selector: + app: {{ template "redis.name" . }} + release: {{ .Release.Name }} +{{- end }} diff --git a/dependency_charts/redis/templates/secret.yaml b/dependency_charts/redis/templates/secret.yaml new file mode 100755 index 0000000..4c39ffd --- /dev/null +++ b/dependency_charts/redis/templates/secret.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.usePassword (not .Values.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "redis.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "redis.name" . }} + chart: {{ template "redis.chart" . }} + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +type: Opaque +data: + redis-password: {{ include "redis.password" . | b64enc | quote }} +{{- end -}} diff --git a/dependency_charts/redis/values-production.yaml b/dependency_charts/redis/values-production.yaml new file mode 100755 index 0000000..df8bb2d --- /dev/null +++ b/dependency_charts/redis/values-production.yaml @@ -0,0 +1,776 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +global: + # imageRegistry: myRegistryName + # imagePullSecrets: + # - myRegistryKeySecretName + # storageClass: myStorageClass + redis: {} + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 6.0.8-debian-10-r0 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## String to partially override redis.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override redis.fullname template +## +# fullnameOverride: + +## Cluster settings +cluster: + enabled: true + slaveCount: 3 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: false + ## Require password authentication on the sentinel itself + ## ref: https://redis.io/topics/sentinel + usePassword: true + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 6.0.8-debian-10-r1 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Additional Redis configuration for the sentinel nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Enable or disable static sentinel IDs for each replicas + ## If disabled each sentinel will generate a random id at startup + ## If enabled, each replicas will have a constant ID on each start-up + ## + staticID: false + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + customLivenessProbe: {} + customReadinessProbe: {} + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + +## Specifies the Kubernetes Cluster's Domain Name. +## +clusterDomain: cluster.local + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: true + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + + ## Allow connections from other namespacess. Just set label for namespace and set label for pods (optional). + ## + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## sysctl settings for master and slave pods + ## + ## Uncomment the setting below to increase the net.core.somaxconn value + ## + # sysctls: + # - name: net.core.somaxconn + # value: "10000" + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume (Redis Master) +persistence: + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: + +# Redis port +redisPort: 6379 + +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to require clients to authenticate or not. + authClients: true + # + # Name of the Secret that contains the certificates + certificatesSecret: + # + # Certificate filename + certFilename: + # + # Certificate Key filename + certKeyFilename: + # + # CA Certificate filename + certCAFilename: + # + # File containing DH params (in order to support DH based ciphers) + # dhParamsFilename: + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the master nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + # Enable shared process namespace in a pod. + # If set to false (default), each container will run in separate namespace, redis will have PID=1. + # If set to true, the /pause will run as init process and will reap any zombie PIDs, + # for example, generated by a custom exec probe running longer than a probe timeoutSeconds. + # Enable this only if customLivenessProbe or customReadinessProbe is used and zombie PIDs are accumulating. + # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + shareProcessNamespace: false + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + ## + priorityClassName: {} + + ## An array to add extra env vars + ## For example: + ## extraEnvVars: + ## - name: name + ## value: value + ## - name: other_name + ## valueFrom: + ## fieldRef: + ## fieldPath: fieldPath + ## + extraEnvVars: [] + + ## ConfigMap with extra env vars: + ## + extraEnvVarsCM: [] + + ## Secret with extra env vars: + ## + extraEnvVarsSecret: [] + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Redis slave port + port: 6379 + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the slave nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Kubernetes Spread Constraints for pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + # - maxSkew: 1 + # topologyKey: node + # whenUnsatisfiable: DoNotSchedule + spreadConstraints: {} + + # Enable shared process namespace in a pod. + # If set to false (default), each container will run in separate namespace, redis will have PID=1. + # If set to true, the /pause will run as init process and will reap any zombie PIDs, + # for example, generated by a custom exec probe running longer than a probe timeoutSeconds. + # Enable this only if customLivenessProbe or customReadinessProbe is used and zombie PIDs are accumulating. + # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + shareProcessNamespace: false + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## An array to add extra env vars + ## For example: + ## extraEnvVars: + ## - name: name + ## value: value + ## - name: other_name + ## valueFrom: + ## fieldRef: + ## fieldPath: fieldPath + ## + extraEnvVars: [] + + ## ConfigMap with extra env vars: + ## + extraEnvVarsCM: [] + + ## Secret with extra env vars: + ## + extraEnvVarsSecret: [] + +## Prometheus Exporter / Metrics +## +metrics: + enabled: true + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.11.1-debian-10-r12 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + prometheusRule: + enabled: false + additionalLabels: {} + namespace: "" + ## Redis prometheus rules + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current redis service. + # rules: + # - alert: RedisDown + # expr: redis_up{service="{{ template "redis.fullname" . }}-metrics"} == 0 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} down + # description: Redis instance {{ "{{ $labels.instance }}" }} is down + # - alert: RedisMemoryHigh + # expr: > + # redis_memory_used_bytes{service="{{ template "redis.fullname" . }}-metrics"} * 100 + # / + # redis_memory_max_bytes{service="{{ template "redis.fullname" . }}-metrics"} + # > 90 =< 100 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} is using too much memory + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + # - alert: RedisKeyEviction + # expr: | + # increase(redis_evicted_keys_total{service="{{ template "redis.fullname" . }}-metrics"}[5m]) > 0 + # for: 1s + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} has evicted keys + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. + rules: [] + + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + labels: {} + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## PodSecurityPolicy configuration +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +podSecurityPolicy: + ## Specifies whether a PodSecurityPolicy should be created + ## + create: false + +## Define a disruption budget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +## +podDisruptionBudget: + enabled: false + minAvailable: 1 + # maxUnavailable: 1 diff --git a/dependency_charts/redis/values.schema.json b/dependency_charts/redis/values.schema.json new file mode 100755 index 0000000..3188d0c --- /dev/null +++ b/dependency_charts/redis/values.schema.json @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "usePassword": { + "type": "boolean", + "title": "Use password authentication", + "form": true + }, + "password": { + "type": "string", + "title": "Password", + "form": true, + "description": "Defaults to a random 10-character alphanumeric string if not set", + "hidden": { + "value": false, + "path": "usePassword" + } + }, + "cluster": { + "type": "object", + "title": "Cluster Settings", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable master-slave", + "description": "Enable master-slave architecture" + }, + "slaveCount": { + "type": "integer", + "title": "Slave Replicas", + "form": true, + "hidden": { + "value": false, + "path": "cluster/enabled" + } + } + } + }, + "master": { + "type": "object", + "title": "Master replicas settings", + "form": true, + "properties": { + "persistence": { + "type": "object", + "title": "Persistence for master replicas", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable persistence", + "description": "Enable persistence using Persistent Volume Claims" + }, + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "master/persistence/enabled" + } + }, + "matchLabels": { + "type": "object", + "title": "Persistent Match Labels Selector" + }, + "matchExpressions": { + "type": "object", + "title": "Persistent Match Expressions Selector" + } + } + } + } + }, + "slave": { + "type": "object", + "title": "Slave replicas settings", + "form": true, + "hidden": { + "value": false, + "path": "cluster/enabled" + }, + "properties": { + "persistence": { + "type": "object", + "title": "Persistence for slave replicas", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable persistence", + "description": "Enable persistence using Persistent Volume Claims" + }, + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "slave/persistence/enabled" + } + }, + "matchLabels": { + "type": "object", + "title": "Persistent Match Labels Selector" + }, + "matchExpressions": { + "type": "object", + "title": "Persistent Match Expressions Selector" + } + } + } + } + }, + "volumePermissions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable Init Containers", + "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination" + } + } + }, + "metrics": { + "type": "object", + "form": true, + "title": "Prometheus metrics details", + "properties": { + "enabled": { + "type": "boolean", + "title": "Create Prometheus metrics exporter", + "description": "Create a side-car container to expose Prometheus metrics", + "form": true + }, + "serviceMonitor": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Create Prometheus Operator ServiceMonitor", + "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", + "form": true, + "hidden": { + "value": false, + "path": "metrics/enabled" + } + } + } + } + } + } + } +} diff --git a/dependency_charts/redis/values.yaml b/dependency_charts/redis/values.yaml new file mode 100755 index 0000000..af16102 --- /dev/null +++ b/dependency_charts/redis/values.yaml @@ -0,0 +1,776 @@ +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry and imagePullSecrets +## +global: + # imageRegistry: myRegistryName + # imagePullSecrets: + # - myRegistryKeySecretName + # storageClass: myStorageClass + redis: {} + +## Bitnami Redis image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## +image: + registry: docker.io + repository: bitnami/redis + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 6.0.8-debian-10-r0 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +## String to partially override redis.fullname template (will maintain the release name) +## +# nameOverride: + +## String to fully override redis.fullname template +## +# fullnameOverride: + +## Cluster settings +cluster: + enabled: true + slaveCount: 2 + +## Use redis sentinel in the redis pod. This will disable the master and slave services and +## create one redis service with ports to the sentinel and the redis instances +sentinel: + enabled: false + ## Require password authentication on the sentinel itself + ## ref: https://redis.io/topics/sentinel + usePassword: true + ## Bitnami Redis Sentintel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + ## Bitnami Redis image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis-sentinel#supported-tags-and-respective-dockerfile-links + ## + tag: 6.0.8-debian-10-r1 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + masterSet: mymaster + initialCheckTimeout: 5 + quorum: 2 + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + parallelSyncs: 1 + port: 26379 + ## Additional Redis configuration for the sentinel nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Enable or disable static sentinel IDs for each replicas + ## If disabled each sentinel will generate a random id at startup + ## If enabled, each replicas will have a constant ID on each start-up + ## + staticID: false + ## Configure extra options for Redis Sentinel liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + customLivenessProbe: {} + customReadinessProbe: {} + ## Redis Sentinel resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Redis Sentinel Service properties + service: + ## Redis Sentinel Service type + type: ClusterIP + sentinelPort: 26379 + redisPort: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # sentinelNodePort: + # redisNodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + +## Specifies the Kubernetes Cluster's Domain Name. +## +clusterDomain: cluster.local + +networkPolicy: + ## Specifies whether a NetworkPolicy should be created + ## + enabled: false + + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port Redis is listening + ## on. When true, Redis will accept connections from any source + ## (with the correct destination port). + ## + # allowExternal: true + + ## Allow connections from other namespacess. Just set label for namespace and set label for pods (optional). + ## + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} + +serviceAccount: + ## Specifies whether a ServiceAccount should be created + ## + create: false + ## The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the fullname template + name: + +rbac: + ## Specifies whether RBAC resources should be created + ## + create: false + + role: + ## Rules to create. It follows the role specification + # rules: + # - apiGroups: + # - extensions + # resources: + # - podsecuritypolicies + # verbs: + # - use + # resourceNames: + # - gce.unprivileged + rules: [] + +## Redis pod Security Context +securityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## sysctl settings for master and slave pods + ## + ## Uncomment the setting below to increase the net.core.somaxconn value + ## + # sysctls: + # - name: net.core.somaxconn + # value: "10000" + +## Use password authentication +usePassword: true +## Redis password (both master and slave) +## Defaults to a random 10-character alphanumeric string if not set and usePassword is true +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +password: "" +## Use existing secret (ignores previous password) +# existingSecret: +## Password key to be retrieved from Redis secret +## +# existingSecretPasswordKey: + +## Mount secrets as files instead of environment variables +usePasswordFile: false + +## Persist data to a persistent volume (Redis Master) +persistence: + ## A manually managed Persistent Volume and Claim + ## Requires persistence.enabled: true + ## If defined, PVC must be created manually before volume will be bound + existingClaim: + +# Redis port +redisPort: 6379 + +## +## TLS configuration +## +tls: + # Enable TLS traffic + enabled: false + # + # Whether to require clients to authenticate or not. + authClients: true + # + # Name of the Secret that contains the certificates + certificatesSecret: + # + # Certificate filename + certFilename: + # + # Certificate Key filename + certKeyFilename: + # + # CA Certificate filename + certCAFilename: + # + # File containing DH params (in order to support DH based ciphers) + # dhParamsFilename: + +## +## Redis Master parameters +## +master: + ## Redis command arguments + ## + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the master nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis additional command line flags + ## + ## Can be used to specify command line flags, for example: + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + extraFlags: [] + ## Comma-separated list of Redis commands to disable + ## + ## Can be used to disable Redis commands for security reasons. + ## Commands will be completely disabled by renaming each to an empty string. + ## ref: https://redis.io/topics/security#disabling-of-specific-commands + ## + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Master additional pod labels and annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + podAnnotations: {} + + ## Redis Master resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + # Enable shared process namespace in a pod. + # If set to false (default), each container will run in separate namespace, redis will have PID=1. + # If set to true, the /pause will run as init process and will reap any zombie PIDs, + # for example, generated by a custom exec probe running longer than a probe timeoutSeconds. + # Enable this only if customLivenessProbe or customReadinessProbe is used and zombie PIDs are accumulating. + # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + shareProcessNamespace: false + ## Configure extra options for Redis Master liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis Master Node selectors and tolerations for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#taints-and-tolerations-beta-feature + ## + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + ## Redis Master pod/node affinity/anti-affinity + ## + affinity: {} + + ## Redis Master Service properties + service: + ## Redis Master Service type + type: ClusterIP + port: 6379 + + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## Redis Master pod priorityClassName + ## + priorityClassName: {} + + ## An array to add extra env vars + ## For example: + ## extraEnvVars: + ## - name: name + ## value: value + ## - name: other_name + ## valueFrom: + ## fieldRef: + ## fieldPath: fieldPath + ## + extraEnvVars: [] + + ## ConfigMap with extra env vars: + ## + extraEnvVarsCM: [] + + ## Secret with extra env vars: + ## + extraEnvVarsSecret: [] + +## +## Redis Slave properties +## Note: service.type is a mandatory parameter +## The rest of the parameters are either optional or, if undefined, will inherit those declared in Redis Master +## +slave: + ## Slave Service properties + service: + ## Redis Slave Service type + type: ClusterIP + ## Redis port + port: 6379 + ## Specify the nodePort value for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + # nodePort: + + ## Provide any additional annotations which may be required. This can be used to + ## set the LoadBalancer service type to internal only. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + annotations: {} + labels: {} + loadBalancerIP: + # loadBalancerSourceRanges: ["10.0.0.0/8"] + + ## Redis slave port + port: 6379 + ## Can be used to specify command line arguments, for example: + ## + command: "/run.sh" + ## Additional Redis configuration for the slave nodes + ## ref: https://redis.io/topics/config + ## + configmap: + ## Redis extra flags + extraFlags: [] + ## List of Redis commands to disable + disableCommands: + - FLUSHDB + - FLUSHALL + + ## Redis Slave pod/node affinity/anti-affinity + ## + affinity: {} + + ## Kubernetes Spread Constraints for pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## + # - maxSkew: 1 + # topologyKey: node + # whenUnsatisfiable: DoNotSchedule + spreadConstraints: {} + + # Enable shared process namespace in a pod. + # If set to false (default), each container will run in separate namespace, redis will have PID=1. + # If set to true, the /pause will run as init process and will reap any zombie PIDs, + # for example, generated by a custom exec probe running longer than a probe timeoutSeconds. + # Enable this only if customLivenessProbe or customReadinessProbe is used and zombie PIDs are accumulating. + # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + shareProcessNamespace: false + ## Configure extra options for Redis Slave liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 10 + successThreshold: 1 + failureThreshold: 5 + + ## Configure custom probes for images other images like + ## rhscl/redis-32-rhel7 rhscl/redis-5-rhel7 + ## Only used if readinessProbe.enabled: false / livenessProbe.enabled: false + ## + # customLivenessProbe: + # tcpSocket: + # port: 6379 + # initialDelaySeconds: 10 + # periodSeconds: 5 + # customReadinessProbe: + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # exec: + # command: + # - "container-entrypoint" + # - "bash" + # - "-c" + # - "redis-cli set liveness-probe \"`date`\" | grep OK" + customLivenessProbe: {} + customReadinessProbe: {} + + ## Redis slave Resource + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + + ## Redis slave selectors and tolerations for pod assignment + # nodeSelector: {"beta.kubernetes.io/arch": "amd64"} + # tolerations: [] + + ## Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + # schedulerName: + + ## Redis slave pod Annotation and Labels + podLabels: {} + podAnnotations: {} + + ## Redis slave pod priorityClassName + # priorityClassName: {} + + ## Enable persistence using Persistent Volume Claims + ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + enabled: true + ## The path the volume will be mounted at, useful when using different + ## Redis images. + path: /data + ## The subdirectory of the volume to mount to, useful in dev environments + ## and one PV for multiple services. + subPath: "" + ## redis data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessModes: + - ReadWriteOnce + size: 8Gi + ## Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + matchLabels: {} + matchExpressions: {} + + ## Update strategy, can be set to RollingUpdate or onDelete by default. + ## https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#updating-statefulsets + statefulset: + updateStrategy: RollingUpdate + ## Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + # rollingUpdatePartition: + + ## An array to add extra env vars + ## For example: + ## extraEnvVars: + ## - name: name + ## value: value + ## - name: other_name + ## valueFrom: + ## fieldRef: + ## fieldPath: fieldPath + ## + extraEnvVars: [] + + ## ConfigMap with extra env vars: + ## + extraEnvVarsCM: [] + + ## Secret with extra env vars: + ## + extraEnvVarsSecret: [] + +## Prometheus Exporter / Metrics +## +metrics: + enabled: false + + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.11.1-debian-10-r12 + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + + ## Metrics exporter resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + ## + # resources: {} + + ## Extra arguments for Metrics exporter, for example: + ## extraArgs: + ## check-keys: myKey,myOtherKey + # extraArgs: {} + + ## Metrics exporter pod Annotation and Labels + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + # podLabels: {} + + # Enable this if you're using https://github.com/coreos/prometheus-operator + serviceMonitor: + enabled: false + ## Specify a namespace if needed + # namespace: monitoring + # fallback to the prometheus default unless specified + # interval: 10s + ## Defaults to what's used if you follow CoreOS [Prometheus Install Instructions](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#tldr) + ## [Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#exporters) + selector: + prometheus: kube-prometheus + + ## Custom PrometheusRule to be defined + ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + prometheusRule: + enabled: false + additionalLabels: {} + namespace: "" + ## Redis prometheus rules + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current redis service. + # rules: + # - alert: RedisDown + # expr: redis_up{service="{{ template "redis.fullname" . }}-metrics"} == 0 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} down + # description: Redis instance {{ "{{ $labels.instance }}" }} is down + # - alert: RedisMemoryHigh + # expr: > + # redis_memory_used_bytes{service="{{ template "redis.fullname" . }}-metrics"} * 100 + # / + # redis_memory_max_bytes{service="{{ template "redis.fullname" . }}-metrics"} + # > 90 =< 100 + # for: 2m + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} is using too much memory + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + # - alert: RedisKeyEviction + # expr: | + # increase(redis_evicted_keys_total{service="{{ template "redis.fullname" . }}-metrics"}[5m]) > 0 + # for: 1s + # labels: + # severity: error + # annotations: + # summary: Redis instance {{ "{{ $labels.instance }}" }} has evicted keys + # description: | + # Redis instance {{ "{{ $labels.instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. + rules: [] + + ## Metrics exporter pod priorityClassName + # priorityClassName: {} + service: + type: ClusterIP + ## Use serviceLoadBalancerIP to request a specific static IP, + ## otherwise leave blank + # loadBalancerIP: + annotations: {} + labels: {} + +## +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + enabled: false + image: + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## Redis config file +## ref: https://redis.io/topics/config +## +configmap: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" + +## Sysctl InitContainer +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +sysctlImage: + enabled: false + command: [] + registry: docker.io + repository: bitnami/minideb + tag: buster + pullPolicy: Always + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + mountHostSys: false + resources: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + +## PodSecurityPolicy configuration +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +podSecurityPolicy: + ## Specifies whether a PodSecurityPolicy should be created + ## + create: false + +## Define a disruption budget +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ +## +podDisruptionBudget: + enabled: false + minAvailable: 1 + # maxUnavailable: 1