From 9fa2c2faab4a914d0b3b758f3c628f180132c8c1 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Fri, 2 Feb 2024 10:46:18 +0100 Subject: [PATCH] Update layer1-port-openscienceframework and same for zenodo --- rds/base/charts/all/Chart.lock | 2 +- rds/base/charts/all/charts/jaeger-0.34.0.tgz | Bin 153149 -> 85676 bytes .../all/charts/layer0-describo-0.2.9.tgz | Bin 60461 -> 6651 bytes ...r0-helper-describo-token-updater-0.2.1.tgz | Bin 3062 -> 2601 bytes .../charts/all/charts/layer0-web-0.3.3.tgz | Bin 5337 -> 4885 bytes ...layer1-port-openscienceframework-0.2.3.tgz | Bin 3704 -> 3085 bytes .../all/charts/layer1-port-owncloud-0.3.3.tgz | Bin 3766 -> 3313 bytes .../all/charts/layer1-port-reva-0.2.0.tgz | Bin 3430 -> 2955 bytes .../all/charts/layer1-port-zenodo-0.2.2.tgz | Bin 3521 -> 3038 bytes .../charts/layer2-exporter-service-0.2.3.tgz | Bin 3202 -> 2732 bytes .../charts/layer2-metadata-service-0.2.3.tgz | Bin 3173 -> 2503 bytes .../all/charts/layer2-port-service-0.2.5.tgz | Bin 3303 -> 2834 bytes .../charts/layer3-research-manager-0.3.4.tgz | Bin 3374 -> 2689 bytes .../all/charts/layer3-token-storage-0.3.0.tgz | Bin 3582 -> 3105 bytes rds/base/charts/all/charts/redis-16.13.2.tgz | Bin 102823 -> 88794 bytes .../charts/all/charts/redis-cluster-7.6.4.tgz | Bin 114056 -> 100145 bytes rds/base/charts/common/.helmignore | 23 + rds/base/charts/common/Chart.yaml | 8 + .../charts/common/templates/_deployment.tpl | 65 + rds/base/charts/jaeger/.helmignore | 21 + rds/base/charts/jaeger/Chart.yaml | 23 + rds/base/charts/jaeger/OWNERS | 10 + rds/base/charts/jaeger/README.md | 380 ++++ .../charts/jaeger/charts/cassandra-0.15.2.tgz | Bin 0 -> 11697 bytes .../jaeger/charts/cassandra/.helmignore | 17 + .../charts/jaeger/charts/cassandra/Chart.yaml | 19 + .../charts/jaeger/charts/cassandra/README.md | 218 ++ .../cassandra/sample/create-storage-gce.yaml | 7 + .../charts/cassandra/templates/NOTES.txt | 35 + .../charts/cassandra/templates/_helpers.tpl | 43 + .../cassandra/templates/backup/cronjob.yaml | 90 + .../cassandra/templates/backup/rbac.yaml | 50 + .../charts/cassandra/templates/configmap.yaml | 14 + .../charts/cassandra/templates/pdb.yaml | 18 + .../charts/cassandra/templates/service.yaml | 46 + .../cassandra/templates/servicemonitor.yaml | 25 + .../cassandra/templates/statefulset.yaml | 230 +++ .../jaeger/charts/cassandra/values.yaml | 254 +++ .../jaeger/charts/elasticsearch-7.8.1.tgz | Bin 0 -> 25705 bytes .../jaeger/charts/elasticsearch/.helmignore | 2 + .../jaeger/charts/elasticsearch/Chart.yaml | 12 + .../jaeger/charts/elasticsearch/Makefile | 1 + .../jaeger/charts/elasticsearch/README.md | 445 ++++ .../elasticsearch/examples/config/Makefile | 19 + .../elasticsearch/examples/config/README.md | 27 + .../examples/config/test/goss.yaml | 26 + .../elasticsearch/examples/config/values.yaml | 31 + .../examples/config/watcher_encryption_key | 1 + .../elasticsearch/examples/default/Makefile | 16 + .../elasticsearch/examples/default/README.md | 25 + .../examples/default/rolling_upgrade.sh | 19 + .../examples/default/test/goss.yaml | 39 + .../examples/docker-for-mac/Makefile | 12 + .../examples/docker-for-mac/README.md | 23 + .../examples/docker-for-mac/values.yaml | 23 + .../examples/kubernetes-kind/Makefile | 16 + .../examples/kubernetes-kind/README.md | 36 + .../kubernetes-kind/values-local-path.yaml | 23 + .../examples/kubernetes-kind/values.yaml | 23 + .../elasticsearch/examples/microk8s/Makefile | 12 + .../elasticsearch/examples/microk8s/README.md | 32 + .../examples/microk8s/values.yaml | 32 + .../elasticsearch/examples/migration/Makefile | 10 + .../examples/migration/README.md | 167 ++ .../examples/migration/client.yml | 23 + .../elasticsearch/examples/migration/data.yml | 17 + .../examples/migration/master.yml | 26 + .../elasticsearch/examples/minikube/Makefile | 12 + .../elasticsearch/examples/minikube/README.md | 38 + .../examples/minikube/values.yaml | 23 + .../elasticsearch/examples/multi/Makefile | 16 + .../elasticsearch/examples/multi/README.md | 27 + .../elasticsearch/examples/multi/data.yml | 9 + .../elasticsearch/examples/multi/master.yml | 9 + .../examples/multi/test/goss.yaml | 9 + .../elasticsearch/examples/openshift/Makefile | 15 + .../examples/openshift/README.md | 24 + .../examples/openshift/test/goss.yaml | 17 + .../examples/openshift/values.yaml | 11 + .../elasticsearch/examples/oss/Makefile | 12 + .../elasticsearch/examples/oss/README.md | 23 + .../elasticsearch/examples/oss/test/goss.yaml | 17 + .../elasticsearch/examples/oss/values.yaml | 4 + .../elasticsearch/examples/security/Makefile | 37 + .../elasticsearch/examples/security/README.md | 29 + .../examples/security/security.yml | 38 + .../examples/security/test/goss.yaml | 45 + .../elasticsearch/examples/upgrade/Makefile | 16 + .../elasticsearch/examples/upgrade/README.md | 27 + .../examples/upgrade/scripts/upgrade.sh | 76 + .../examples/upgrade/test/goss.yaml | 17 + .../charts/elasticsearch/templates/NOTES.txt | 4 + .../elasticsearch/templates/_helpers.tpl | 87 + .../elasticsearch/templates/configmap.yaml | 17 + .../elasticsearch/templates/ingress.yaml | 39 + .../templates/poddisruptionbudget.yaml | 13 + .../templates/podsecuritypolicy.yaml | 15 + .../charts/elasticsearch/templates/role.yaml | 26 + .../elasticsearch/templates/rolebinding.yaml | 25 + .../elasticsearch/templates/service.yaml | 73 + .../templates/serviceaccount.yaml | 21 + .../elasticsearch/templates/statefulset.yaml | 430 ++++ .../test/test-elasticsearch-health.yaml | 25 + .../jaeger/charts/elasticsearch/values.yaml | 277 +++ .../charts/jaeger/charts/kafka-0.20.6.tgz | Bin 0 -> 32345 bytes .../charts/jaeger/charts/kafka/.helmignore | 21 + .../charts/jaeger/charts/kafka/Chart.yaml | 24 + rds/base/charts/jaeger/charts/kafka/OWNERS | 4 + rds/base/charts/jaeger/charts/kafka/README.md | 434 ++++ .../charts/kafka/charts/zookeeper/.helmignore | 21 + .../charts/kafka/charts/zookeeper/Chart.yaml | 17 + .../charts/kafka/charts/zookeeper/OWNERS | 6 + .../charts/kafka/charts/zookeeper/README.md | 145 ++ .../charts/zookeeper/templates/NOTES.txt | 7 + .../charts/zookeeper/templates/_helpers.tpl | 46 + .../templates/config-jmx-exporter.yaml | 20 + .../zookeeper/templates/config-script.yaml | 113 + .../zookeeper/templates/job-chroots.yaml | 66 + .../templates/poddisruptionbudget.yaml | 18 + .../zookeeper/templates/service-headless.yaml | 26 + .../charts/zookeeper/templates/service.yaml | 42 + .../zookeeper/templates/servicemonitors.yaml | 60 + .../zookeeper/templates/statefulset.yaml | 227 ++ .../charts/kafka/charts/zookeeper/values.yaml | 295 +++ .../jaeger/charts/kafka/requirements.lock | 6 + .../jaeger/charts/kafka/requirements.yaml | 6 + .../jaeger/charts/kafka/templates/NOTES.txt | 76 + .../charts/kafka/templates/_helpers.tpl | 128 ++ .../kafka/templates/configmap-config.yaml | 80 + .../charts/kafka/templates/configmap-jmx.yaml | 65 + .../templates/deployment-kafka-exporter.yaml | 46 + .../charts/kafka/templates/job-config.yaml | 30 + .../kafka/templates/podisruptionbudget.yaml | 15 + .../kafka/templates/prometheusrules.yaml | 16 + .../templates/service-brokers-external.yaml | 78 + .../kafka/templates/service-brokers.yaml | 37 + .../kafka/templates/service-headless.yaml | 22 + .../kafka/templates/servicemonitors.yaml | 47 + .../charts/kafka/templates/statefulset.yaml | 273 +++ .../test_topic_create_consume_produce.yaml | 25 + .../charts/jaeger/charts/kafka/values.yaml | 511 +++++ rds/base/charts/jaeger/requirements.lock | 12 + rds/base/charts/jaeger/requirements.yaml | 14 + rds/base/charts/jaeger/templates/NOTES.txt | 27 + rds/base/charts/jaeger/templates/_helpers.tpl | 370 ++++ .../charts/jaeger/templates/agent-ds.yaml | 142 ++ .../charts/jaeger/templates/agent-sa.yaml | 10 + .../templates/agent-servicemonitor.yaml | 38 + .../charts/jaeger/templates/agent-svc.yaml | 41 + .../templates/cassandra-schema-job.yaml | 98 + .../jaeger/templates/cassandra-schema-sa.yaml | 10 + .../jaeger/templates/cassandra-secret.yaml | 12 + .../jaeger/templates/collector-configmap.yaml | 14 + .../jaeger/templates/collector-deploy.yaml | 181 ++ .../jaeger/templates/collector-hpa.yaml | 28 + .../charts/jaeger/templates/collector-sa.yaml | 10 + .../templates/collector-servicemonitor.yaml | 38 + .../jaeger/templates/collector-svc.yaml | 47 + .../templates/elasticsearch-secret.yaml | 12 + .../templates/es-index-cleaner-cronjob.yaml | 84 + .../jaeger/templates/es-index-cleaner-sa.yaml | 10 + .../jaeger/templates/hotrod-deploy.yaml | 66 + .../charts/jaeger/templates/hotrod-ing.yaml | 33 + .../charts/jaeger/templates/hotrod-sa.yaml | 10 + .../charts/jaeger/templates/hotrod-svc.yaml | 25 + .../jaeger/templates/ingester-deploy.yaml | 131 ++ .../charts/jaeger/templates/ingester-hpa.yaml | 28 + .../charts/jaeger/templates/ingester-sa.yaml | 10 + .../templates/ingester-servicemonitor.yaml | 38 + .../charts/jaeger/templates/ingester-svc.yaml | 24 + .../jaeger/templates/query-configmap.yaml | 13 + .../charts/jaeger/templates/query-deploy.yaml | 212 ++ .../charts/jaeger/templates/query-ing.yaml | 31 + .../charts/jaeger/templates/query-sa.yaml | 10 + .../templates/query-servicemonitor.yaml | 37 + .../charts/jaeger/templates/query-svc.yaml | 32 + .../jaeger/templates/spark-cronjob.yaml | 98 + .../charts/jaeger/templates/spark-sa.yaml | 10 + rds/base/charts/jaeger/values.yaml | 538 +++++ rds/base/charts/layer0_describo/.helmignore | 22 + rds/base/charts/layer0_describo/Chart.lock | 9 + rds/base/charts/layer0_describo/Chart.yaml | 29 + .../layer0_describo/charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../charts/postgresql-10.14.3.tgz | Bin 0 -> 53725 bytes .../layer0_describo/defaults/nginx.conf | 82 + .../defaults/type-definitions-lookup.json | 16 + .../defaults/type-definitions.json | 200 ++ .../layer0_describo/templates/NOTES.txt | 21 + .../layer0_describo/templates/_helpers.tpl | 91 + .../layer0_describo/templates/configmap.yaml | 59 + .../layer0_describo/templates/deployment.yaml | 121 ++ .../layer0_describo/templates/ingress.yaml | 28 + .../layer0_describo/templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + rds/base/charts/layer0_describo/values.yaml | 87 + .../.helmignore | 22 + .../Chart.lock | 6 + .../Chart.yaml | 27 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 70 + .../templates/configmap.yaml | 13 + .../templates/deployment.yaml | 48 + .../values.yaml | 44 + rds/base/charts/layer0_web/.helmignore | 22 + rds/base/charts/layer0_web/Chart.lock | 6 + rds/base/charts/layer0_web/Chart.yaml | 23 + .../charts/layer0_web/charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../charts/layer0_web/templates/NOTES.txt | 21 + .../charts/layer0_web/templates/_helpers.tpl | 62 + .../layer0_web/templates/configmap.yaml | 49 + .../layer0_web/templates/deployment.yaml | 97 + .../charts/layer0_web/templates/ingress.yaml | 28 + .../charts/layer0_web/templates/service.yaml | 25 + .../templates/tests/test-connection.yaml | 15 + rds/base/charts/layer0_web/values.yaml | 92 + .../.helmignore | 22 + .../Chart.lock | 6 + .../Chart.yaml | 25 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 69 + .../templates/configmap.yaml | 15 + .../templates/deployment.yaml | 73 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../values.yaml | 60 + .../charts/layer1_port_owncloud/.helmignore | 22 + .../charts/layer1_port_owncloud/Chart.lock | 6 + .../charts/layer1_port_owncloud/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 71 + .../templates/configmap.yaml | 35 + .../templates/deployment.yaml | 88 + .../templates/service.yaml | 36 + .../templates/tests/test-connection.yaml | 30 + .../charts/layer1_port_owncloud/values.yaml | 60 + rds/base/charts/layer1_port_reva/.helmignore | 22 + rds/base/charts/layer1_port_reva/Chart.lock | 6 + rds/base/charts/layer1_port_reva/Chart.yaml | 24 + .../layer1_port_reva/charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../layer1_port_reva/templates/_helpers.tpl | 70 + .../layer1_port_reva/templates/configmap.yaml | 9 + .../templates/deployment.yaml | 66 + .../layer1_port_reva/templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + rds/base/charts/layer1_port_reva/values.yaml | 52 + .../charts/layer1_port_zenodo/.helmignore | 22 + rds/base/charts/layer1_port_zenodo/Chart.lock | 6 + rds/base/charts/layer1_port_zenodo/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../layer1_port_zenodo/templates/_helpers.tpl | 70 + .../templates/configmap.yaml | 13 + .../templates/deployment.yaml | 73 + .../layer1_port_zenodo/templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../charts/layer1_port_zenodo/values.yaml | 60 + .../layer2_exporter_service/.helmignore | 22 + .../charts/layer2_exporter_service/Chart.lock | 6 + .../charts/layer2_exporter_service/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 69 + .../templates/configmap.yaml | 7 + .../templates/deployment.yaml | 62 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../layer2_exporter_service/values.yaml | 43 + .../charts/layer2_metadata_service/Chart.lock | 6 + .../charts/layer2_metadata_service/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 70 + .../templates/configmap.yaml | 6 + .../templates/deployment.yaml | 62 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../layer2_metadata_service/values.yaml | 43 + .../charts/layer2_port_service/.helmignore | 22 + .../charts/layer2_port_service/Chart.lock | 6 + .../charts/layer2_port_service/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 71 + .../templates/configmap.yaml | 7 + .../templates/deployment.yaml | 67 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../charts/layer2_port_service/values.yaml | 47 + .../charts/layer3_research_manager/Chart.lock | 6 + .../charts/layer3_research_manager/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 69 + .../templates/configmap.yaml | 8 + .../templates/deployment.yaml | 65 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../layer3_research_manager/values.yaml | 52 + .../charts/layer3_token_storage/.helmignore | 22 + .../charts/layer3_token_storage/Chart.lock | 6 + .../charts/layer3_token_storage/Chart.yaml | 24 + .../charts/common-0.1.2.tgz | Bin 0 -> 995 bytes .../templates/_helpers.tpl | 69 + .../templates/configmap.yaml | 14 + .../templates/deployment.yaml | 65 + .../templates/service.yaml | 21 + .../templates/tests/test-connection.yaml | 15 + .../charts/layer3_token_storage/values.yaml | 53 + rds/base/charts/postgresql/.helmignore | 21 + rds/base/charts/postgresql/Chart.lock | 6 + rds/base/charts/postgresql/Chart.yaml | 30 + rds/base/charts/postgresql/README.md | 816 ++++++++ .../postgresql/charts/common-1.10.3.tgz | Bin 0 -> 13331 bytes .../postgresql/charts/common/.helmignore | 22 + .../postgresql/charts/common/Chart.yaml | 23 + .../charts/postgresql/charts/common/README.md | 328 +++ .../charts/common/templates/_affinities.tpl | 102 + .../charts/common/templates/_capabilities.tpl | 128 ++ .../charts/common/templates/_errors.tpl | 23 + .../charts/common/templates/_images.tpl | 75 + .../charts/common/templates/_ingress.tpl | 55 + .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 52 + .../charts/common/templates/_secrets.tpl | 129 ++ .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 62 + .../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 | 129 ++ .../common/templates/validations/_redis.tpl | 76 + .../templates/validations/_validations.tpl | 46 + .../postgresql/charts/common/values.yaml | 5 + .../postgresql/ci/commonAnnotations.yaml | 3 + .../charts/postgresql/ci/default-values.yaml | 1 + .../ci/shmvolume-disabled-values.yaml | 2 + rds/base/charts/postgresql/files/README.md | 1 + .../charts/postgresql/files/conf.d/README.md | 4 + .../docker-entrypoint-initdb.d/README.md | 3 + .../charts/postgresql/templates/NOTES.txt | 89 + .../charts/postgresql/templates/_helpers.tpl | 361 ++++ .../postgresql/templates/configmap.yaml | 34 + .../templates/extended-config-configmap.yaml | 29 + .../postgresql/templates/extra-list.yaml | 4 + .../templates/initialization-configmap.yaml | 26 + .../templates/metrics-configmap.yaml | 17 + .../postgresql/templates/metrics-svc.yaml | 29 + .../postgresql/templates/networkpolicy.yaml | 42 + .../templates/podsecuritypolicy.yaml | 42 + .../postgresql/templates/prometheusrule.yaml | 28 + .../charts/postgresql/templates/role.yaml | 24 + .../postgresql/templates/rolebinding.yaml | 23 + .../charts/postgresql/templates/secrets.yaml | 27 + .../postgresql/templates/serviceaccount.yaml | 15 + .../postgresql/templates/servicemonitor.yaml | 44 + .../templates/statefulset-readreplicas.yaml | 430 ++++ .../postgresql/templates/statefulset.yaml | 636 ++++++ .../postgresql/templates/svc-headless.yaml | 31 + .../postgresql/templates/svc-read-set.yaml | 43 + .../charts/postgresql/templates/svc-read.yaml | 47 + rds/base/charts/postgresql/templates/svc.yaml | 45 + .../postgresql/templates/tls-secrets.yaml | 26 + rds/base/charts/postgresql/values.schema.json | 103 + rds/base/charts/postgresql/values.yaml | 996 +++++++++ rds/base/charts/redis-cluster/.helmignore | 21 + rds/base/charts/redis-cluster/Chart.lock | 6 + rds/base/charts/redis-cluster/Chart.yaml | 28 + rds/base/charts/redis-cluster/README.md | 682 ++++++ .../redis-cluster/charts/common-1.16.0.tgz | Bin 0 -> 14693 bytes .../redis-cluster/charts/common/.helmignore | 22 + .../redis-cluster/charts/common/Chart.yaml | 23 + .../redis-cluster/charts/common/README.md | 350 ++++ .../charts/common/templates/_affinities.tpl | 102 + .../charts/common/templates/_capabilities.tpl | 154 ++ .../charts/common/templates/_errors.tpl | 23 + .../charts/common/templates/_images.tpl | 75 + .../charts/common/templates/_ingress.tpl | 68 + .../charts/common/templates/_labels.tpl | 18 + .../charts/common/templates/_names.tpl | 70 + .../charts/common/templates/_secrets.tpl | 140 ++ .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../charts/common/templates/_utils.tpl | 62 + .../charts/common/templates/_warnings.tpl | 14 + .../templates/validations/_cassandra.tpl | 72 + .../common/templates/validations/_mariadb.tpl | 103 + .../common/templates/validations/_mongodb.tpl | 108 + .../common/templates/validations/_mysql.tpl | 103 + .../templates/validations/_postgresql.tpl | 129 ++ .../common/templates/validations/_redis.tpl | 76 + .../templates/validations/_validations.tpl | 46 + .../redis-cluster/charts/common/values.yaml | 5 + .../img/redis-cluster-topology.png | Bin 0 -> 11448 bytes .../redis-cluster/img/redis-topology.png | Bin 0 -> 9709 bytes .../charts/redis-cluster/templates/NOTES.txt | 117 ++ .../redis-cluster/templates/_helpers.tpl | 254 +++ .../redis-cluster/templates/configmap.yaml | 1829 +++++++++++++++++ .../redis-cluster/templates/extra-list.yaml | 4 + .../redis-cluster/templates/headless-svc.yaml | 24 + .../templates/metrics-prometheus.yaml | 54 + .../redis-cluster/templates/metrics-svc.yaml | 35 + .../templates/networkpolicy.yaml | 66 + .../templates/poddisruptionbudget.yaml | 20 + .../templates/prometheusrule.yaml | 27 + .../charts/redis-cluster/templates/psp.yaml | 46 + .../redis-cluster/templates/redis-role.yaml | 25 + .../templates/redis-rolebinding.yaml | 21 + .../templates/redis-serviceaccount.yaml | 21 + .../templates/redis-statefulset.yaml | 449 ++++ .../redis-cluster/templates/redis-svc.yaml | 53 + .../templates/scripts-configmap.yaml | 111 + .../redis-cluster/templates/secret.yaml | 17 + .../svc-cluster-external-access.yaml | 45 + .../redis-cluster/templates/tls-secret.yaml | 27 + .../templates/update-cluster.yaml | 266 +++ rds/base/charts/redis-cluster/values.yaml | 980 +++++++++ rds/base/charts/redis/.helmignore | 21 + rds/base/charts/redis/Chart.lock | 6 + rds/base/charts/redis/Chart.yaml | 29 + rds/base/charts/redis/README.md | 898 ++++++++ .../charts/redis/charts/common-1.16.0.tgz | Bin 0 -> 14693 bytes .../charts/redis/charts/common/.helmignore | 22 + .../charts/redis/charts/common/Chart.yaml | 23 + rds/base/charts/redis/charts/common/README.md | 350 ++++ .../charts/common/templates/_affinities.tpl | 102 + .../charts/common/templates/_capabilities.tpl | 154 ++ .../redis/charts/common/templates/_errors.tpl | 23 + .../redis/charts/common/templates/_images.tpl | 75 + .../charts/common/templates/_ingress.tpl | 68 + .../redis/charts/common/templates/_labels.tpl | 18 + .../redis/charts/common/templates/_names.tpl | 70 + .../charts/common/templates/_secrets.tpl | 140 ++ .../charts/common/templates/_storage.tpl | 23 + .../charts/common/templates/_tplvalues.tpl | 13 + .../redis/charts/common/templates/_utils.tpl | 62 + .../charts/common/templates/_warnings.tpl | 14 + .../templates/validations/_cassandra.tpl | 72 + .../common/templates/validations/_mariadb.tpl | 103 + .../common/templates/validations/_mongodb.tpl | 108 + .../common/templates/validations/_mysql.tpl | 103 + .../templates/validations/_postgresql.tpl | 129 ++ .../common/templates/validations/_redis.tpl | 76 + .../templates/validations/_validations.tpl | 46 + .../charts/redis/charts/common/values.yaml | 5 + .../redis/img/redis-cluster-topology.png | Bin 0 -> 11448 bytes rds/base/charts/redis/img/redis-topology.png | Bin 0 -> 9709 bytes rds/base/charts/redis/templates/NOTES.txt | 191 ++ rds/base/charts/redis/templates/_helpers.tpl | 291 +++ .../charts/redis/templates/configmap.yaml | 59 + .../charts/redis/templates/extra-list.yaml | 4 + .../charts/redis/templates/headless-svc.yaml | 30 + .../redis/templates/health-configmap.yaml | 192 ++ .../redis/templates/master/application.yaml | 473 +++++ .../charts/redis/templates/master/psp.yaml | 46 + .../charts/redis/templates/master/pvc.yaml | 27 + .../redis/templates/master/service.yaml | 58 + .../charts/redis/templates/metrics-svc.yaml | 41 + .../charts/redis/templates/networkpolicy.yaml | 78 + rds/base/charts/redis/templates/pdb.yaml | 23 + .../redis/templates/prometheusrule.yaml | 23 + .../charts/redis/templates/replicas/hpa.yaml | 47 + .../redis/templates/replicas/service.yaml | 58 + .../redis/templates/replicas/statefulset.yaml | 471 +++++ rds/base/charts/redis/templates/role.yaml | 28 + .../charts/redis/templates/rolebinding.yaml | 21 + .../redis/templates/scripts-configmap.yaml | 627 ++++++ rds/base/charts/redis/templates/secret.yaml | 23 + .../charts/redis/templates/sentinel/hpa.yaml | 47 + .../templates/sentinel/node-services.yaml | 70 + .../templates/sentinel/ports-configmap.yaml | 100 + .../redis/templates/sentinel/service.yaml | 103 + .../redis/templates/sentinel/statefulset.yaml | 688 +++++++ .../redis/templates/serviceaccount.yaml | 21 + .../redis/templates/servicemonitor.yaml | 41 + .../charts/redis/templates/tls-secret.yaml | 29 + rds/base/charts/redis/values.schema.json | 156 ++ rds/base/charts/redis/values.yaml | 1621 +++++++++++++++ 474 files changed, 35169 insertions(+), 1 deletion(-) create mode 100644 rds/base/charts/common/.helmignore create mode 100644 rds/base/charts/common/Chart.yaml create mode 100644 rds/base/charts/common/templates/_deployment.tpl create mode 100644 rds/base/charts/jaeger/.helmignore create mode 100644 rds/base/charts/jaeger/Chart.yaml create mode 100644 rds/base/charts/jaeger/OWNERS create mode 100644 rds/base/charts/jaeger/README.md create mode 100644 rds/base/charts/jaeger/charts/cassandra-0.15.2.tgz create mode 100644 rds/base/charts/jaeger/charts/cassandra/.helmignore create mode 100644 rds/base/charts/jaeger/charts/cassandra/Chart.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/README.md create mode 100644 rds/base/charts/jaeger/charts/cassandra/sample/create-storage-gce.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/NOTES.txt create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/_helpers.tpl create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/backup/cronjob.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/backup/rbac.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/configmap.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/pdb.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/service.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/servicemonitor.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/templates/statefulset.yaml create mode 100644 rds/base/charts/jaeger/charts/cassandra/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch-7.8.1.tgz create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/.helmignore create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/Chart.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/config/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/config/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/config/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/config/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/config/watcher_encryption_key create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/default/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/default/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/default/rolling_upgrade.sh create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/default/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/migration/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/migration/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/migration/client.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/migration/data.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/migration/master.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/multi/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/multi/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/multi/data.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/multi/master.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/multi/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/oss/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/oss/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/oss/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/oss/values.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/security/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/security/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/security/security.yml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/security/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/Makefile create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/README.md create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/scripts/upgrade.sh create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/test/goss.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/NOTES.txt create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/_helpers.tpl create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/configmap.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/ingress.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/poddisruptionbudget.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/podsecuritypolicy.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/role.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/rolebinding.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/service.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/serviceaccount.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/statefulset.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/templates/test/test-elasticsearch-health.yaml create mode 100644 rds/base/charts/jaeger/charts/elasticsearch/values.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka-0.20.6.tgz create mode 100644 rds/base/charts/jaeger/charts/kafka/.helmignore create mode 100644 rds/base/charts/jaeger/charts/kafka/Chart.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/OWNERS create mode 100644 rds/base/charts/jaeger/charts/kafka/README.md create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/.helmignore create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/Chart.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/OWNERS create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/README.md create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/NOTES.txt create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/_helpers.tpl create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-jmx-exporter.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-script.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/job-chroots.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service-headless.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/servicemonitors.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/statefulset.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/charts/zookeeper/values.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/requirements.lock create mode 100644 rds/base/charts/jaeger/charts/kafka/requirements.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/NOTES.txt create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/_helpers.tpl create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/configmap-config.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/configmap-jmx.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/deployment-kafka-exporter.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/job-config.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/podisruptionbudget.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/prometheusrules.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/service-brokers-external.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/service-brokers.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/service-headless.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/servicemonitors.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/statefulset.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/templates/tests/test_topic_create_consume_produce.yaml create mode 100644 rds/base/charts/jaeger/charts/kafka/values.yaml create mode 100644 rds/base/charts/jaeger/requirements.lock create mode 100644 rds/base/charts/jaeger/requirements.yaml create mode 100644 rds/base/charts/jaeger/templates/NOTES.txt create mode 100644 rds/base/charts/jaeger/templates/_helpers.tpl create mode 100644 rds/base/charts/jaeger/templates/agent-ds.yaml create mode 100644 rds/base/charts/jaeger/templates/agent-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/agent-servicemonitor.yaml create mode 100644 rds/base/charts/jaeger/templates/agent-svc.yaml create mode 100644 rds/base/charts/jaeger/templates/cassandra-schema-job.yaml create mode 100644 rds/base/charts/jaeger/templates/cassandra-schema-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/cassandra-secret.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-configmap.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-deploy.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-hpa.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-servicemonitor.yaml create mode 100644 rds/base/charts/jaeger/templates/collector-svc.yaml create mode 100644 rds/base/charts/jaeger/templates/elasticsearch-secret.yaml create mode 100644 rds/base/charts/jaeger/templates/es-index-cleaner-cronjob.yaml create mode 100644 rds/base/charts/jaeger/templates/es-index-cleaner-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/hotrod-deploy.yaml create mode 100644 rds/base/charts/jaeger/templates/hotrod-ing.yaml create mode 100644 rds/base/charts/jaeger/templates/hotrod-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/hotrod-svc.yaml create mode 100644 rds/base/charts/jaeger/templates/ingester-deploy.yaml create mode 100644 rds/base/charts/jaeger/templates/ingester-hpa.yaml create mode 100644 rds/base/charts/jaeger/templates/ingester-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/ingester-servicemonitor.yaml create mode 100644 rds/base/charts/jaeger/templates/ingester-svc.yaml create mode 100644 rds/base/charts/jaeger/templates/query-configmap.yaml create mode 100644 rds/base/charts/jaeger/templates/query-deploy.yaml create mode 100644 rds/base/charts/jaeger/templates/query-ing.yaml create mode 100644 rds/base/charts/jaeger/templates/query-sa.yaml create mode 100644 rds/base/charts/jaeger/templates/query-servicemonitor.yaml create mode 100644 rds/base/charts/jaeger/templates/query-svc.yaml create mode 100644 rds/base/charts/jaeger/templates/spark-cronjob.yaml create mode 100644 rds/base/charts/jaeger/templates/spark-sa.yaml create mode 100644 rds/base/charts/jaeger/values.yaml create mode 100644 rds/base/charts/layer0_describo/.helmignore create mode 100644 rds/base/charts/layer0_describo/Chart.lock create mode 100644 rds/base/charts/layer0_describo/Chart.yaml create mode 100644 rds/base/charts/layer0_describo/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer0_describo/charts/postgresql-10.14.3.tgz create mode 100644 rds/base/charts/layer0_describo/defaults/nginx.conf create mode 100644 rds/base/charts/layer0_describo/defaults/type-definitions-lookup.json create mode 100644 rds/base/charts/layer0_describo/defaults/type-definitions.json create mode 100644 rds/base/charts/layer0_describo/templates/NOTES.txt create mode 100644 rds/base/charts/layer0_describo/templates/_helpers.tpl create mode 100644 rds/base/charts/layer0_describo/templates/configmap.yaml create mode 100644 rds/base/charts/layer0_describo/templates/deployment.yaml create mode 100644 rds/base/charts/layer0_describo/templates/ingress.yaml create mode 100644 rds/base/charts/layer0_describo/templates/service.yaml create mode 100644 rds/base/charts/layer0_describo/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer0_describo/values.yaml create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/.helmignore create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/Chart.lock create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/Chart.yaml create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/templates/_helpers.tpl create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/templates/configmap.yaml create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/templates/deployment.yaml create mode 100644 rds/base/charts/layer0_helper_describo_token_updater/values.yaml create mode 100644 rds/base/charts/layer0_web/.helmignore create mode 100644 rds/base/charts/layer0_web/Chart.lock create mode 100644 rds/base/charts/layer0_web/Chart.yaml create mode 100644 rds/base/charts/layer0_web/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer0_web/templates/NOTES.txt create mode 100644 rds/base/charts/layer0_web/templates/_helpers.tpl create mode 100644 rds/base/charts/layer0_web/templates/configmap.yaml create mode 100644 rds/base/charts/layer0_web/templates/deployment.yaml create mode 100644 rds/base/charts/layer0_web/templates/ingress.yaml create mode 100644 rds/base/charts/layer0_web/templates/service.yaml create mode 100644 rds/base/charts/layer0_web/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer0_web/values.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/.helmignore create mode 100644 rds/base/charts/layer1_port_openscienceframework/Chart.lock create mode 100644 rds/base/charts/layer1_port_openscienceframework/Chart.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer1_port_openscienceframework/templates/_helpers.tpl create mode 100644 rds/base/charts/layer1_port_openscienceframework/templates/configmap.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/templates/deployment.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/templates/service.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer1_port_openscienceframework/values.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/.helmignore create mode 100644 rds/base/charts/layer1_port_owncloud/Chart.lock create mode 100644 rds/base/charts/layer1_port_owncloud/Chart.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer1_port_owncloud/templates/_helpers.tpl create mode 100644 rds/base/charts/layer1_port_owncloud/templates/configmap.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/templates/deployment.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/templates/service.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer1_port_owncloud/values.yaml create mode 100644 rds/base/charts/layer1_port_reva/.helmignore create mode 100644 rds/base/charts/layer1_port_reva/Chart.lock create mode 100644 rds/base/charts/layer1_port_reva/Chart.yaml create mode 100644 rds/base/charts/layer1_port_reva/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer1_port_reva/templates/_helpers.tpl create mode 100644 rds/base/charts/layer1_port_reva/templates/configmap.yaml create mode 100644 rds/base/charts/layer1_port_reva/templates/deployment.yaml create mode 100644 rds/base/charts/layer1_port_reva/templates/service.yaml create mode 100644 rds/base/charts/layer1_port_reva/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer1_port_reva/values.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/.helmignore create mode 100644 rds/base/charts/layer1_port_zenodo/Chart.lock create mode 100644 rds/base/charts/layer1_port_zenodo/Chart.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer1_port_zenodo/templates/_helpers.tpl create mode 100644 rds/base/charts/layer1_port_zenodo/templates/configmap.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/templates/deployment.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/templates/service.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer1_port_zenodo/values.yaml create mode 100644 rds/base/charts/layer2_exporter_service/.helmignore create mode 100644 rds/base/charts/layer2_exporter_service/Chart.lock create mode 100644 rds/base/charts/layer2_exporter_service/Chart.yaml create mode 100644 rds/base/charts/layer2_exporter_service/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer2_exporter_service/templates/_helpers.tpl create mode 100644 rds/base/charts/layer2_exporter_service/templates/configmap.yaml create mode 100644 rds/base/charts/layer2_exporter_service/templates/deployment.yaml create mode 100644 rds/base/charts/layer2_exporter_service/templates/service.yaml create mode 100644 rds/base/charts/layer2_exporter_service/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer2_exporter_service/values.yaml create mode 100644 rds/base/charts/layer2_metadata_service/Chart.lock create mode 100644 rds/base/charts/layer2_metadata_service/Chart.yaml create mode 100644 rds/base/charts/layer2_metadata_service/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer2_metadata_service/templates/_helpers.tpl create mode 100644 rds/base/charts/layer2_metadata_service/templates/configmap.yaml create mode 100644 rds/base/charts/layer2_metadata_service/templates/deployment.yaml create mode 100644 rds/base/charts/layer2_metadata_service/templates/service.yaml create mode 100644 rds/base/charts/layer2_metadata_service/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer2_metadata_service/values.yaml create mode 100644 rds/base/charts/layer2_port_service/.helmignore create mode 100644 rds/base/charts/layer2_port_service/Chart.lock create mode 100644 rds/base/charts/layer2_port_service/Chart.yaml create mode 100644 rds/base/charts/layer2_port_service/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer2_port_service/templates/_helpers.tpl create mode 100644 rds/base/charts/layer2_port_service/templates/configmap.yaml create mode 100644 rds/base/charts/layer2_port_service/templates/deployment.yaml create mode 100644 rds/base/charts/layer2_port_service/templates/service.yaml create mode 100644 rds/base/charts/layer2_port_service/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer2_port_service/values.yaml create mode 100644 rds/base/charts/layer3_research_manager/Chart.lock create mode 100644 rds/base/charts/layer3_research_manager/Chart.yaml create mode 100644 rds/base/charts/layer3_research_manager/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer3_research_manager/templates/_helpers.tpl create mode 100644 rds/base/charts/layer3_research_manager/templates/configmap.yaml create mode 100644 rds/base/charts/layer3_research_manager/templates/deployment.yaml create mode 100644 rds/base/charts/layer3_research_manager/templates/service.yaml create mode 100644 rds/base/charts/layer3_research_manager/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer3_research_manager/values.yaml create mode 100644 rds/base/charts/layer3_token_storage/.helmignore create mode 100644 rds/base/charts/layer3_token_storage/Chart.lock create mode 100644 rds/base/charts/layer3_token_storage/Chart.yaml create mode 100644 rds/base/charts/layer3_token_storage/charts/common-0.1.2.tgz create mode 100644 rds/base/charts/layer3_token_storage/templates/_helpers.tpl create mode 100644 rds/base/charts/layer3_token_storage/templates/configmap.yaml create mode 100644 rds/base/charts/layer3_token_storage/templates/deployment.yaml create mode 100644 rds/base/charts/layer3_token_storage/templates/service.yaml create mode 100644 rds/base/charts/layer3_token_storage/templates/tests/test-connection.yaml create mode 100644 rds/base/charts/layer3_token_storage/values.yaml create mode 100644 rds/base/charts/postgresql/.helmignore create mode 100644 rds/base/charts/postgresql/Chart.lock create mode 100644 rds/base/charts/postgresql/Chart.yaml create mode 100644 rds/base/charts/postgresql/README.md create mode 100644 rds/base/charts/postgresql/charts/common-1.10.3.tgz create mode 100644 rds/base/charts/postgresql/charts/common/.helmignore create mode 100644 rds/base/charts/postgresql/charts/common/Chart.yaml create mode 100644 rds/base/charts/postgresql/charts/common/README.md create mode 100644 rds/base/charts/postgresql/charts/common/templates/_affinities.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_capabilities.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_errors.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_images.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_ingress.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_labels.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_names.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_secrets.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_storage.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_tplvalues.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_utils.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/_warnings.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_cassandra.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_mariadb.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_mongodb.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_postgresql.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_redis.tpl create mode 100644 rds/base/charts/postgresql/charts/common/templates/validations/_validations.tpl create mode 100644 rds/base/charts/postgresql/charts/common/values.yaml create mode 100644 rds/base/charts/postgresql/ci/commonAnnotations.yaml create mode 100644 rds/base/charts/postgresql/ci/default-values.yaml create mode 100644 rds/base/charts/postgresql/ci/shmvolume-disabled-values.yaml create mode 100644 rds/base/charts/postgresql/files/README.md create mode 100644 rds/base/charts/postgresql/files/conf.d/README.md create mode 100644 rds/base/charts/postgresql/files/docker-entrypoint-initdb.d/README.md create mode 100644 rds/base/charts/postgresql/templates/NOTES.txt create mode 100644 rds/base/charts/postgresql/templates/_helpers.tpl create mode 100644 rds/base/charts/postgresql/templates/configmap.yaml create mode 100644 rds/base/charts/postgresql/templates/extended-config-configmap.yaml create mode 100644 rds/base/charts/postgresql/templates/extra-list.yaml create mode 100644 rds/base/charts/postgresql/templates/initialization-configmap.yaml create mode 100644 rds/base/charts/postgresql/templates/metrics-configmap.yaml create mode 100644 rds/base/charts/postgresql/templates/metrics-svc.yaml create mode 100644 rds/base/charts/postgresql/templates/networkpolicy.yaml create mode 100644 rds/base/charts/postgresql/templates/podsecuritypolicy.yaml create mode 100644 rds/base/charts/postgresql/templates/prometheusrule.yaml create mode 100644 rds/base/charts/postgresql/templates/role.yaml create mode 100644 rds/base/charts/postgresql/templates/rolebinding.yaml create mode 100644 rds/base/charts/postgresql/templates/secrets.yaml create mode 100644 rds/base/charts/postgresql/templates/serviceaccount.yaml create mode 100644 rds/base/charts/postgresql/templates/servicemonitor.yaml create mode 100644 rds/base/charts/postgresql/templates/statefulset-readreplicas.yaml create mode 100644 rds/base/charts/postgresql/templates/statefulset.yaml create mode 100644 rds/base/charts/postgresql/templates/svc-headless.yaml create mode 100644 rds/base/charts/postgresql/templates/svc-read-set.yaml create mode 100644 rds/base/charts/postgresql/templates/svc-read.yaml create mode 100644 rds/base/charts/postgresql/templates/svc.yaml create mode 100644 rds/base/charts/postgresql/templates/tls-secrets.yaml create mode 100644 rds/base/charts/postgresql/values.schema.json create mode 100644 rds/base/charts/postgresql/values.yaml create mode 100644 rds/base/charts/redis-cluster/.helmignore create mode 100644 rds/base/charts/redis-cluster/Chart.lock create mode 100644 rds/base/charts/redis-cluster/Chart.yaml create mode 100644 rds/base/charts/redis-cluster/README.md create mode 100644 rds/base/charts/redis-cluster/charts/common-1.16.0.tgz create mode 100644 rds/base/charts/redis-cluster/charts/common/.helmignore create mode 100644 rds/base/charts/redis-cluster/charts/common/Chart.yaml create mode 100644 rds/base/charts/redis-cluster/charts/common/README.md create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_affinities.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_capabilities.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_errors.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_images.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_ingress.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_labels.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_names.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_secrets.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_storage.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_tplvalues.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_utils.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/_warnings.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_cassandra.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_mariadb.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_mongodb.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_mysql.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_postgresql.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_redis.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/templates/validations/_validations.tpl create mode 100644 rds/base/charts/redis-cluster/charts/common/values.yaml create mode 100644 rds/base/charts/redis-cluster/img/redis-cluster-topology.png create mode 100644 rds/base/charts/redis-cluster/img/redis-topology.png create mode 100644 rds/base/charts/redis-cluster/templates/NOTES.txt create mode 100644 rds/base/charts/redis-cluster/templates/_helpers.tpl create mode 100644 rds/base/charts/redis-cluster/templates/configmap.yaml create mode 100644 rds/base/charts/redis-cluster/templates/extra-list.yaml create mode 100644 rds/base/charts/redis-cluster/templates/headless-svc.yaml create mode 100644 rds/base/charts/redis-cluster/templates/metrics-prometheus.yaml create mode 100644 rds/base/charts/redis-cluster/templates/metrics-svc.yaml create mode 100644 rds/base/charts/redis-cluster/templates/networkpolicy.yaml create mode 100644 rds/base/charts/redis-cluster/templates/poddisruptionbudget.yaml create mode 100644 rds/base/charts/redis-cluster/templates/prometheusrule.yaml create mode 100644 rds/base/charts/redis-cluster/templates/psp.yaml create mode 100644 rds/base/charts/redis-cluster/templates/redis-role.yaml create mode 100644 rds/base/charts/redis-cluster/templates/redis-rolebinding.yaml create mode 100644 rds/base/charts/redis-cluster/templates/redis-serviceaccount.yaml create mode 100644 rds/base/charts/redis-cluster/templates/redis-statefulset.yaml create mode 100644 rds/base/charts/redis-cluster/templates/redis-svc.yaml create mode 100644 rds/base/charts/redis-cluster/templates/scripts-configmap.yaml create mode 100644 rds/base/charts/redis-cluster/templates/secret.yaml create mode 100644 rds/base/charts/redis-cluster/templates/svc-cluster-external-access.yaml create mode 100644 rds/base/charts/redis-cluster/templates/tls-secret.yaml create mode 100644 rds/base/charts/redis-cluster/templates/update-cluster.yaml create mode 100644 rds/base/charts/redis-cluster/values.yaml create mode 100644 rds/base/charts/redis/.helmignore create mode 100644 rds/base/charts/redis/Chart.lock create mode 100644 rds/base/charts/redis/Chart.yaml create mode 100644 rds/base/charts/redis/README.md create mode 100644 rds/base/charts/redis/charts/common-1.16.0.tgz create mode 100644 rds/base/charts/redis/charts/common/.helmignore create mode 100644 rds/base/charts/redis/charts/common/Chart.yaml create mode 100644 rds/base/charts/redis/charts/common/README.md create mode 100644 rds/base/charts/redis/charts/common/templates/_affinities.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_capabilities.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_errors.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_images.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_ingress.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_labels.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_names.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_secrets.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_storage.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_tplvalues.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_utils.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/_warnings.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_cassandra.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_mariadb.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_mongodb.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_mysql.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_postgresql.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_redis.tpl create mode 100644 rds/base/charts/redis/charts/common/templates/validations/_validations.tpl create mode 100644 rds/base/charts/redis/charts/common/values.yaml create mode 100644 rds/base/charts/redis/img/redis-cluster-topology.png create mode 100644 rds/base/charts/redis/img/redis-topology.png create mode 100644 rds/base/charts/redis/templates/NOTES.txt create mode 100644 rds/base/charts/redis/templates/_helpers.tpl create mode 100644 rds/base/charts/redis/templates/configmap.yaml create mode 100644 rds/base/charts/redis/templates/extra-list.yaml create mode 100644 rds/base/charts/redis/templates/headless-svc.yaml create mode 100644 rds/base/charts/redis/templates/health-configmap.yaml create mode 100644 rds/base/charts/redis/templates/master/application.yaml create mode 100644 rds/base/charts/redis/templates/master/psp.yaml create mode 100644 rds/base/charts/redis/templates/master/pvc.yaml create mode 100644 rds/base/charts/redis/templates/master/service.yaml create mode 100644 rds/base/charts/redis/templates/metrics-svc.yaml create mode 100644 rds/base/charts/redis/templates/networkpolicy.yaml create mode 100644 rds/base/charts/redis/templates/pdb.yaml create mode 100644 rds/base/charts/redis/templates/prometheusrule.yaml create mode 100644 rds/base/charts/redis/templates/replicas/hpa.yaml create mode 100644 rds/base/charts/redis/templates/replicas/service.yaml create mode 100644 rds/base/charts/redis/templates/replicas/statefulset.yaml create mode 100644 rds/base/charts/redis/templates/role.yaml create mode 100644 rds/base/charts/redis/templates/rolebinding.yaml create mode 100644 rds/base/charts/redis/templates/scripts-configmap.yaml create mode 100644 rds/base/charts/redis/templates/secret.yaml create mode 100644 rds/base/charts/redis/templates/sentinel/hpa.yaml create mode 100644 rds/base/charts/redis/templates/sentinel/node-services.yaml create mode 100644 rds/base/charts/redis/templates/sentinel/ports-configmap.yaml create mode 100644 rds/base/charts/redis/templates/sentinel/service.yaml create mode 100644 rds/base/charts/redis/templates/sentinel/statefulset.yaml create mode 100644 rds/base/charts/redis/templates/serviceaccount.yaml create mode 100644 rds/base/charts/redis/templates/servicemonitor.yaml create mode 100644 rds/base/charts/redis/templates/tls-secret.yaml create mode 100644 rds/base/charts/redis/values.schema.json create mode 100644 rds/base/charts/redis/values.yaml diff --git a/rds/base/charts/all/Chart.lock b/rds/base/charts/all/Chart.lock index 16228e3..ef0c938 100644 --- a/rds/base/charts/all/Chart.lock +++ b/rds/base/charts/all/Chart.lock @@ -51,4 +51,4 @@ dependencies: repository: https://owncloud-docker.github.io/helm-charts version: 0.4.1 digest: sha256:c23f6f5d58ca92ec95d87fe4d97de87b8d07cf9b0da3f85b6eed9d95e3c5b27c -generated: "2024-01-26T12:45:44.958867792+01:00" +generated: "2024-02-02T10:45:47.952156261+01:00" diff --git a/rds/base/charts/all/charts/jaeger-0.34.0.tgz b/rds/base/charts/all/charts/jaeger-0.34.0.tgz index 058d4ba0b35aed145607e57059361d95ba204a17..7881768ef6d87a9e1df6ca1341f9b6f5a42502fb 100644 GIT binary patch delta 70691 zcmV)KK)S!ZtO=~01(2=|Zg*>Yx4Zpsoo;V?Yx}Xh(E$M-vyKAX3jyx4YYr6x0oJpD z5ZMNQ={@>?+{fd*tJP=!masC;5=6r|rtQma;{uB?CL-?t2(gBCUK=zFIaE(6x4urw zEvdI!(PCMiIF$lShENd_qqV?uX6PRH1R^ehMGS0f5wpT6Zy@jG-51tIObrRD5ZRAc z%ZW$DsyU1d>4?TiglX0m;R6-Tn)$QWMu**hT`n5SXASi;>AFVeK{`?jfpjpPWU-^_ z7%dg!9#&8&2PMP(d5%aDPH53%b&5RCK&?L^(pWwwu&N%a($$^Qvp}8eiUF^dwUP_{ zTmd>ee$Aql{trZ4e zyazWo1LF&W(?Zn~BY;!^NEThnTD{4X=KD}BvrA~UjBWr-7e^+KXP>I1b{l|}-^=Re zi<=`|@)AXtIhI%0sEu=XjbTlTmyM@?0}12$<>?I4pV4(CVVemOcVdY*O=4f%V6^y) z=CeF;(P?-F;O)UlafaqNtj@dS87Y@+c(XKr5!0Fo zIVe*ICc7>vjQmAkoC>A5EGK}>mb6CyvouRzy!&OSRP;X0s=ZpL%|uXT-9FpDr*4-c zQ_*o4%yCrS^X_(Ux4X1kA-+TrqVyyoV`Oe;oUE^qwMzpK%ka$hJDK0hLMA~lz;^*w z$9b!>$z*}_%66Gc5-#RQ#p<+wn6TW)9h+7z!l!#L;o}@x@}qo=5ZAm&`ZHfHjd;yV zsUP_=S&(a9NdKy_hn9BD3~KsS^-B(;bxK`l{HlAI#T>WL&8nmYDkSN(=wFQ&HL~#A z@3Lcx%Qhj0TgeH-j8~Y|wW1iVf3hfPQ-?n^j`TVpV(qr-wPb`|%o;O)<1wz&-oLI2 z_p_c7{V#?0rvL49b~?L-_&>Y5kNV&Hc$9Pnqo70wT-V>!)Dx#F;*nK+B|UM4YUTlH zjlX)1Q?~vk_2)80$7k|_CF{Spz17XF|J|MK$NQgqd5rb{)X4yz<=@-cThd}V7NBMh zj;5YZ5=5v-w4Y}T3ds6@7f8;-VGhH{2!2@P9xK$e)qFL%kR=k`HA@0z=jB|-@~L%9 zd8$MqTwULRuGR5cNUeY2RspGind1fyMS*k{lFhUnr(ktfo+FlEpH|(fiVpP0;(-aFMyU8dEPg>_f zwB|~16|5H`>ucW8S9{9#|4aY&4gkyc|6Xr9FaP)YJCFPSeLO4Kf8@9Czo+4c)((IE zezc+?NP6c~aoZHHcbikZQ;=!TrOvw0PQ@M15(FFW5a+HAs=?@bGG$OHemW+4enVZ=cxKfFm977BCrAt9NA=y8 zdkXu1K9*+dJsIEa`G2>wo8SL;w{{-mf8WdVu`FVXDFdu%;cbcxV=ldC=l-9_*ex{F zb*NLb@*#*21)TEkH8LdZT4IEw?sEA7`V3#90|bRZ&ItK`BnqhQv!uYf3QFY4Y(dox z)VjmvCe7#ClDff^+gp}mk+LIpwjiMF{|ta6{y+3_L z!QR>4{_yB*cv{k}R6a1HEKTs#3;gp)CQ>MjKz;^R#uX^yWCSUw!l)itNAK%eHL1CJ zduC1M()#-Cnm%t5uNB{;(EDaPA%wNFs&?QECoP(PrA0DQ7`_kpf^>-W)Dpqb;&3_X zc>5(cH(y>Ns9662ibHaJ=lefj`NjRezx}xW@8hX;|636QNX(HrsxQksJoX>fk}svm zS{J$Q>dWqOji3t-Knq5w4>^aim8%JgL+nHQ$~UX0KJSZMUill!FBXI-BYetbmBXQb zl&VgVJX0>cn|59r`!nQU(8athg_#zLF0Fk@W^@EkyfX#c`yO&Rmd!x3R21T3F!%)M z4EA>VbTM~aEoW3OyW?ImTOMqHq#6sQZlEMrjrzCfg*#~j-0embs()K=_2_ml>?^Yv zR$bL+H6OxleccUV3GJ6qfBpDHIclwcZw6pYgqjTM5AR$bUT(6Y=U3YV9~P(jivy5#cx`1R}I{@L;A`TriC{Cs$H{^Qx%Nmb;c zWz7_FG}NH5E7exzZR_Y{@92DVc6_?`YIuJ3+lljT{Q^!d;7Suy4~(dJ=SCfWb#I{+ zHnk02+|^dHdr3_f_I~(z@BHNS_~7mS@bvuq)8n6Iwhvct)A?O5pNbpbaY8OoQer=l zL+9-HHRfUAkNR0hju=n1c{Wv^2JKWzpJ3KyGsS&tfGZZoG3z4U2%Hv>3E6i#~ zvzdLoHIwq}W~psZo%LC!9B_AkXz$I*>%*g0=cBXJy|dw~!{O-shr`#-ezg(vg>c)> zsVJ%uEQB{JkKa@?hMGR~@sGw!cle{#+M6*Jm$sLSLshfA?sl*Lq5vx&+Q|)Ujg4cq z{+c_2qsQ`dEi?-zPj?a@>aK!W)w9ns54g8wqGhN-{vK9ZmEHSlBTzkmBnz~P#7$00 zJOKN%CF!b4(0&lsXVuxdDluQh_H6l#TWMprZaJJi%eM1!9NbZtGejJbmvdHVd*Kzl z);KIRL95RUzep&zn#}B8!nv(8Jt`;1GMcS*zve)^LThxtvFHpn_dUd;%Ub(ajY(H= z|2K`~U9lc0YfA|JQwt|8zf3`E8f`mdUyb%!;%xdt=0)z83tEB=|3W;t5Yk zu&0Wlx^bQ!{vBbOx`TJ?Ot|K!srX!%rTl&2!qj5qb%`*?(P{NHpE+f~2zhD)e#(3~XKZoq1Stk~(ffOvcTUW%b(_o>``D)|4c#QbuwEw=B=VLMcXVC_vR8@7iX5V$LJRi`e@D)gV#iwfh-&PFJCF{Splh6Ow z>uf*Tf8NjY(GmZDv%mnJxxRh5IG`n>(K5aKO7TGJ*8cc_V}hEdRrSfULTu3bFjkBY zS`PU0Z15gNoX}EyYQ_p(q5cZ`sn3iVx+SAAttDuP&bDuTBviy~L3*87OSY6>ug(6A-rpeV9R$kr6nS8~Jk z_*6Xw`45GEkTUF31QUODJ23#3>3{pXh4>#kok#iaUY*seBo;rocv$s+uTjOmY<^Qi0Bc#c^mtkPN%>IC8t z1*<@G*U&i981Y#*fgwc=0N#`FndKgAx{BJ4!@%J@>`;y7uqpTBEpa`rH{_*$zTI98 zFO$4~+wVGui+n??E{j6jRU|C>1=l81;U=MoxPwOKGyp63+vaC?wbLzd85ib2lzZQT zN)OyTfmWR(6+W~zDPQi$oJXMO`U_SH)?GQdDmzlAD-~YB+xe-aJcFC%?8)UHoGT*T zg_}jdB|gJ>(mNf8fx~$iq*o(uNYP?GM#=Gi&094RzcmIlBeg&vbhopK~J3W&M4Y<9NfYa*TMV3l8B~Zhi z2dv=I18TbUfLg9S6{A+K@8v#BUunme1P)%l-?;7eL8RBbkHh}6LI(h2@y%D?~WPnrDB zS<>xzf7bB-FZzFO?>wIW@8wx8|GP`zXqq5O3niZ@8B2IY3|>|WE%`daM$?EZX-jQ# zhs>WyBE+spa>1wT#d9H$+ExjFhZ4`f#{s2!DAuw-sDQED0wGz1$N;UPgmOvAF^swi z{yE)VT7dtQQ9nh!blc%Oe74_kSs-_(=n@ac(7!-Yke4KkIVqbmC#g$xHY%lN#?~y& zZF4P`6I-s|Oy7I6u=a2;m+Rcel5~7)X`0IT|E)Ry*U|qL{C{?OyE~76{Qo{4hyFLW z2e`TMo$7m+AJ-oeM}ENC-Q`c|`oAsbzZ&x2)^@KT|MegJ|L^5lP5)cZ`S0%`R(+P+ zUj>ZiVpYL;CkHJHTy3a^94euL6PHV%P6aNPTBzVxc0Zm>#<;BFnIx-yo%irx>M8Jl zok`#pZvdT82(|x@EyO-zVk~|W3dIP9idkA|$ic1+q*4hW0wb#i0mBCpl z`Bz%)Vdnla#{c$Gf0d2jN{P72YCW7hTubL`-7H*=3iXq4t#Y?(HdO0&w{T67Lb#_) zR?Ba8@`zbCL=G~#25P-qmntaBW@%-HuKGNC$rPx@cl{Jy_AFR`KSNhGA8#o^R|?S1 z&s8v;eK6^{ikPjDYs1reP}9SSth5m(B@fr$^d9J{rA!^I$STrRdy2Ca4tl zon(SqmbR$7-P<+I9ac%}^S4ZnV+eq^l^e%ip?|m3ICk$IGvid%e$0&Xm>DNU#hsNV zT~4>1l%_Tp`XV`hY3gy2J5NbdLk(K4|GTyHAIt53w(|D>JG;A&`9JREDb;p;x%3~| zVfsPc_@)Qndr1D_CP7*559grOPW|Do{Y5i>I8plC#2*zvA4cAf&p~i)d)spUe|r`{ zOYMJp1^b_V_tF0IULJ@2PvH*8p}V_#8=#WL9+nl5rG)=~DleQX*8gp#|5%>?qtnmF zf9v%h*Z;jdA07EW@(kdaXmteE$s z6z*r;y+4esA7$v&O!`s3x>G+<1Cf{|x{vxwpL=;$wf|Z({l`Ps=`3N9x3d9#Zt4$5$^muT`K3}07#y@0s+>OZY=Zxt+Qh- zf&u~iljbil0rs;OFcTAh?#^TUzx#Mh1;L=v%7^!805c$&^pI|A4q}$N)UiW4UU$dq zrI7MI)8)|eB~ysI-gB>;0u=1O5|A!opw#Pl{YHSNh_V5oGuYed5B%p_lU{$P*WKxi z`x7*Nfx7*z-RIrT*dM=ues8z?qPzQI=lOVdcelU$qBrS154O;MxYzBV@vh$+H&Ta@ z0ciF*y8uYyIC|h=beQ8tXm^Q^!`VAtF6x=Y$G z*007PUp~)kYyt>>W>SqnRwfPkZE1ArNx&U7SL9P(n~a2XLmaWoayFjz)|naG4*USmddSeBBLO#}O!1o~95 zh(iJc?+RbwISMcohadhc=D*rfHA&lSwwRCGJMA6RPN{Ytk}2`%<+Q!E)rmi}iXXf< znl>)b^%Y5f0wI@WY_gS7lgmVe{uwsr5N8QATL8@=4hLXLNJM7-e@*#Me#A+mxBX0_ z>HlU0=a3}cB*7?{!|1;f6wDyA%JTw~q>Q5Q}H$sklb{p0Fzq_-u zozMT$?`~~B^8fpIHa1LVF4S6TSvNK|K!PS2!<#LC#$G`D3zP^|O41JTIDyHvF>4w! z@2)zY*-|8f({xV(h8foYm_}R;05&$1N#wQEvzZBrCnNzUB%rdGg`SS2X~dIF zsihU^a4(u(VK^NqF-R|H8wPV6Vcu#>DSX@MJy2`D2pNt;)Jof-?Z(DNV=V4#0QjtAC_5sI=K>wwkU=&E_V!nqhy&B^zn96kJDej{Qusm%@jHwulr4 z;HkW1l93{T@W~SmMT;=p6f#M!F`?ZDSoH<)@|(b)5t0U`&|zHCmQmsX@N^pY0ExhV z-mfFx0~iG`2>^*SoL4^&H~B~KAK*Af(TG9+;+gm;X1a;SfRlmGIdR?ah6HE;P7w@# zO)x{pk_??Ec1o1~^4r zhDK3<0x*Xkzzo4y%sD<#-FA;l@9GF4`P;!N-%)vpC3O5B{f`GoL7O6H;Ou8v5T>*iY zTu>Bnc!95IbFj}p@7IC#Lva8`cphtv8UR>{0R})s)#ryqGXcPV0PJ*o{H;j% ze4fO97E)6rR*}&W{XfG2c+uJJ$q?Vd`+ERhbo){#Cdd!LPrtm;VOE5_iwIw@3J7l$ZpqRN^z< zhpN3+qT6bTwrIPdKb`R3e57%N8HV8j3gNYK9^m87Cj&~b2(TsJZSi*u&k6NW(AXd(VjQpQ$r|#)i)uYDpM;gWz(dk-B@sk<5Do_#s&Y-jtAO zD5CSUrx&Qj3&wQH5Gd)l6b#jT(Rr`u07yJluh1skn&6Q}| zfZA@@aop0mpF>1{2JpH?93()|yo?%F(sC>0f)i)-3`bMi5V7Or-;&@;IuIKNBVZ~r zdy;_48+|_Y#7K*cAiM@4f|n{tvkuZYLy-us+vr* z{U-rRP{29Ubz>0+kfD)`hH;%8(2k=6Vd0)4s6+1o6+N$!i>7Cta`X-}p|29MhzH=s zix+&0TSR;Gt%wWFUx;DT;Q}Wzt_6UU;;9I5DCM5|M`1$%lRyN=@;6cCd}!C)G3`|aU>k!ME)fHma3J{u7%-$p*+`$E*mkv^fc#_;d_~E*H1{4 z`7`Q>r}@cZ816_FblZnGYRH7Wj&&r*OM8u|Y=ZB}n0nw1@61>@{RuY`!MLO~lBi|1 z%S$jg_NI$u(QbfLwIMG^wK4i2{Y&0Q@>&DX?0{Y6mQqr>XO`YFisw{3YIhrcfLxfB zd+dULiQqBHW{-%&{Af#!K3`is4CkuizR(L%VaOYh2&q!e$f%+UhI0?j;a?e zmstm3L0bq?*6KEdB2=H_72&c&JTOk?U2!r8`eZI=RCb83EGjy6WUv-xzN}mYD{ zk)oKMz&2Brs?>o*`aop9OJ}nR-#$Y+CpSzOo=d? z81o4fWE~#>x;1FG#|!@gF~fAiKBb|554!7lj+vA+=qH#Vk^wwdAd-gu!zqc_0c2Hz zg?u3Z@fj*=f^Td zLepAe8i@^S>1^1mYQwRXHgXGdNtK4+LQ?%K8IkcqB~##UH-K4CZ03c6GN^=qNL#|! zfVlGpLlGsB)BsB2Sda0LesA3$?}uuxxsSBMlXAbq2D5`UUh* zNoDVLuakbrr7BB-6gyqno|!Tuwl`R%msM(kK_hEfMn7J?aTR-W&Gs9nqFkPTY9i#z1B}QM zHz)ki6;TEw9~n7DWrExs;X>?zGs2~TE0{>XiM^AIi$7SO3@ zc%7C8)N1i=$Os~w4yQ7ons=1Gy}1ETp6a?&q(UFps@D=p{a$|jn8mkmW|mJJKofZ2 z=4Ml6aeJAEB!CbR)QdBJx|iR41RiE+PJi$G;pwcA0uZ?Q=9$o&52~lqvSG2w?x5qx zk1c)_RXYR)O&vF>nKo;NJmO8Zlyt>Go}V0_p1rgQT91|n8JZtG8Y3BrSCB%7d%9_+ z!YU}Cx<0;3%0IJ_`|)^`M@~42t%YJbP?SNopq^3#Bk@lCxM^~K41~umc%m_oe{n)J z1i4X2kxld0Mo_JCx&xI1`;dV-yao(jAOOJxU4c1{77S&jt3!T+Yy&vq@%tu&2X0S zUztP)s3k=jtxqvh-DbvC>O|>*!w8T>1OXx-PS7Qalz~@BDBWxb+KE`%BKVI_=5tJy zYxN@Lf?9UfBq?d6m~ue8^PbIIp4C_sz{iKIN4^%g=x3GZgYE=COE9 zQ{W}%zur!7*FOLCI=%j*|JS`dA3wIg1($d}5G!JWLnM+K&pBEA85)3Z+c!52UaB$t z5Nnrk^Bzk~9j~FvwUpgukqx%8@4|G^lP*Sr3FJ}r7TmZ;DvrY z4e4EfN!&~@)HK19l%DyJ;c1!D0=0-{C;>Zsd zLD?zkSs6qrOXVg?4t7)hQ8@ZIfjDNvmuBm=pXK|%yozh98~b%Uf|lxkcXR$fz3$Fd z|FhOp^aBAsv&&b_0RcR-nOV;Uf8hUT@7>$mwvmPL^Y6<~F_nGRc8^I(z9gx3z2|ik zH}y7g^4d<@eY|-rh=e566u~7(JK8wE&;ENb07#Icu6CTXHFuxJB5@fEfWgdQE=R)g z;)MT3{nYVTKuJ@TOZacjn_o_jPS5^;hJw@f|LM`;i`PfqD7c3{X7B&Kf7Z@Ub^q`0 z>^$cGdXUG;G3gxI*_ALhb)P3z5OhpQc^rw9grO?noO6{SD-5gInXo>l3&x~bRw#9o ziDDCpi7KeCafF>7Tya0p8yn$ZTjhZuV~D5Af0y#-76_G&e#>#X-ENPFp(BsSyaPK`NWoV%z>P{jS>$Iu zlJdloGrQfc;~e%eGlyzTkw?_W&_Jj$`y|4|B*f^xcwbLI86f@D>Qr@5$*C1!#&R_= z*vvL$Lg#qb!e6;vT>)G-&XNJ0>|kvT-o=B2U|1WuDl+_K`pB1sf95N9Lwmlaj^VW=GwBBP@D^jT{c)9%m_zs4jaS&FHE z6%P$8f-a`>`gxKaFH*Fq!d(ztJYFri1Ia#T6*@PaS3;M|st=h;;B(AAU=F^_&e zSLNMh+2)$Xo*DFae{y`T=eclnBbF&QxUNrxPXc;?ml7}YeWnv_rGre^H>Wc}qD2bzo~b{W>@PM)RAr0ghX0>V541amQY&I=k-44GvzJrbQJ zx3AU|CXNmye+6YhIja`#HY>Z_&5(t(oW#iIa)lregM|2UgE$u8+bSKDS@4n!k1$QO z&L9sg_26vL)zh(-PodxHICFJm?Psl4PHRhtuwtMkg<^f?7x~24f2i}lb$F)5af?%s zuf}}etCn_EPC*Z-W=Pt z>tPP&3iC{9Na^JUsq_by;1FPK|n1uS)Q1JN1P z>)=YsU{=%6-SygSZ+~f^%5W8CsC5n9!ZE@kOX*l!^O}b4VR%Evq6@6AJ2{tQX9Bt^ zk9~Jrsh+#X{S%(PO{kzz+O2_0UvSj`e^!D^2ce$}SH!|qkY(ViH8kv2dpWOWq|=;% zD(=<6rHgo&_;M)->)?`|Pc$+Wu1SC_xbC*@YuJ}*aAl(&O(bOMq+`)^5aqgGaTd#P z<#wR;>%I(^EUbaUq-iOK9_Oh@6B0v2Z@>>3jZy)>afTAZA}H3w+38NeRk!3Vf4ERB z;tDEHlavOz1#tnTXVo~&Qa&On^GO(vVL&6QY7yV(3Cb}khi>U&O|wLkn6d$lve9MA zuV^%7gGMY3dT^+D={eg|VPgNuJ#0{Iky+o;N^re+-o6L8ax2a$gm!tjP7hZF%!aFC zpxeDWT(&*%a`4%3RhaZ@FRQGtf7af!*DE2+hN~N1F@O|U=_I9Pg?f{7n+VuHl_$IdL`)t^B+IIhC{S7+r$_(RT>)*ojOr(4SG`fask}xT@&Rk=6pU0BF z|5SXPXy*0mnJ9t>z^2kwr$eH6Mvcp@``C{)=2JEF>z$xL;O)=PCoUJJjijqIF!utN zDCzF)Ud~Flki3ru2$I{Af1uXGh4|Xc1QkHScv!v3EaF9M&4sHltg7-85?}f@l?XVc zBv7Un3I6>{!ctm=EC28&@|Q#0B)+CH{AXG@h*PHloY^#V+pU-E4)d5R@n$Lpp1*z9 ze63WhxdzwRp*1n{x2V)mr905wd&#=nvVjgt8H>VY2lSjSh~Pfdf6-_#iVFP@bAdKG z@?hNt>J2>Tp74Oa#dw>*G=iBy+^o(jc1z3DsC z`*L8JX>wZ313N+ue_jbpMb^E*RTCPhj^39_&7495*PG}C-gz$%9l|x&_p7Uvxgi;w z%WhKzVp#{6$Zt0T3$E{0wtL2VM8tTeQkY zA8IF3eR(b+fANP@vG{oVIwVRRO@q=!gcNz1z@0Llp_;yh?)pdN=l#jRRU`?Vx*%@h zOx;>j3}Au6+3EL(&x;#t5JSAmmzct~CM56$mX(pAY`(CZ26EzJW)dz^Ny2t_$Y#Q| z;KfV@COy4&jXOLDS2?fdH2&)dKO3`k9_jn(l3{3Nf9!YEGikVta6d)Q^pnnzjw@eP z*#=7dp)$Jg6CNpZsJzA<9viM%Vh7y9_X<%}wsm^bz{toWBk~^}ksB3{I^iMhK-ZtV zSeyY@H3fgS7Oq9^x^<22i14JJH0Oh{09;e7{L zp{R2^e=tSoB|7Tix}#pMNI^f#a6#kCGNPKTpSy*_U(PO1k6s?XIq5(*6D}npbr-uZ zvf-MkrpV!1SWRK+C)$>NVtKd%DpGBuShpWv6ra(UDwX`9re-8bS)ceN&50z~yPamU zm-$yTZ7z5DZNoJqtGwh_b$)s6*)C6OKcNAQeTs2sw-TY{?`p&vJ7J%D%7ug(Ok8l%2jq7~XcU&3W# zG#gk;u-#EdQ4{U4hNn0(m|2kO-$K_u!bh!*&XFu- zE;~jl638sT?%{4!M~$A<$gS<5hoWpAe`|Vbjk~TdE70@|*|rLo9iDm$TzA128XKSa zB&1f13e?{$;tIk06`S(1BZMqpa&pu6;ocMKlV>le2$=pN|&)AA^+ zn84?wQI3SYaqjJt4s&oP-K{VxjLXuJvqjPB`lcx;yTnyqU(gMK#PNX@iKF<$f8jU5 zqnM<_gN=_KOX)~lwB8pEc(T1u8(UBb6Q)q{CQIWi&EbAQQsUDnrAY^fUiNpZY1grdt3%a*@LEe~URw^_2Km&bt$UcFTL_wY_#*{`t(?+lQan_W(Ql zPm01%o_Ov5E>+)aw|xJ*fVj`T`)=3W3;Z4TyVlMVw;cq#&)UzP?)m$B0Ho+)&ui}h zJlpeH&+0mO<~=QU@ZA&I-v5raUE2R{&)sW1p)P6ls7v>s^?SkY4tciQe**A}4t71+ zUc0sHwVu}Xu0EG*| z_cZc{Ba&P>w*SUOvDic^C#&#~3%D;lxwb?~KA^IIMwSQAuGg{?=5QVg_21`b*pN3& zN8uLWT1Plu=- zMNR1q+9RN5NfCPZuG5x9-^7yg%d?WFB!o7MSQyIhS9B~?g)CyI^vBy(*2~aa1YER$ z_64 zT$Z{_zPo8(wqJX)e}s!8Rs~Zk(j8pXkg`<2fP`K%!FZ%TgDdoUf^^pr03%-a8!&-b1B zn7#ko`};eU{Lk&Dt=9hI{(p$abzMgbpgIC&X$>^c#b0O3YmA62*KjZpAT3Ohha`oF zN1(pLKo%D&u0@(COJE*F)JNSJ1{bXH+03I0<`yA3fAfk39{dkR3dCkvH(LvZvs{ z_zwbC1vI7mwDdPaJXh213y+8)b{B(+3p1aOSnMS?FCQdN?f)p;PcujueNlv@f zfDglvf8PjHL8cr?Am!`vq_0pWlBO7vgjV*yD%f<^KxF<9$6F%6<57qLd&mcSzwLN& z!eg4Gj0%Se6|Vtf+OEN5y$;;U_aH_0j`|BAq9Ub%e4_#|$oCU%+cn`p7nGBWdiDCN zdS#QYLzvXeEEDz;piyAf00low64}@WjjlTzf3nkZ_z#ItZADbDyb=tFwQ-b^4;Zn@ z!OMZ8=vs8(;=LR|p3&x?mq#Z*AD_NCd3|(pe);p^sT!WnoRJRcCH{Fi;8a{MZk@iq(ht^70<~E;1u(~%#FI4?`I)yb}WhNF}Wc6%qOY7+cB9qv3b@oZ6@<*Ek)V!(Mn|Ww%!*#HuHA|+b^#ahuyd*|6}rrz67)>f-le{zJn zA^l?gM@s4TBgZ4A<>CJtj#Cp|TsCS)^M(s(lsv&y>Xb^D?rMncJUsVq>m_rXq(?AR zxY8wc08Lnmyw=)UXL13SzvrQ}RopbS)AfR`X*?<-Zx^A;Xwv+c^=7LnF*0FE!k@CvUbJ8n63cZ+1 z!SS7m8}`|i?d%>FCANzs+80tI#HrXkOtc?xSMU){hw@ZWO-!UKUMr>0f7l54#x`u^ zwKm@CUXIJDkv~sQ#R)2B>TSX}rm9BYQWIZIS-kbpYMn*S8qA0g>GQ~^aVna=9gnLS z^T3UD<8GW#Ic@U&nt6%dtWIqAUu@_#C?ex2_6)hP5xJruvV=l6Bik$c zXbcl49m*pKu{`Vd0P{Wrx}T z!dzDTB>B=t87D(7Qp}zp2k|3G0vJ&eX{n+JT&()QLGdUYqX3~Tt^;7mJth=LLV-p+ z8w_>W8@=~N@{mQ2iC}=Xv1%!z*EE@i3yesa+L#c$uLYez^T3TBe-$avexE04qcr3W zG=?Mz8rAhvHVnDP^+SYUgUFT3NcPcOlmB&1Pi~K<#O+g(W(h@sv;b6+1tpCdT&v;g z0C@ysXa(pt%SafGJ-OCI1vfECNEp(vC@$v$gF?^%NUHfq#4g&os{r{X8c|%8WNZ9R zJJ5Kmd&%YJdXAk%f4@;*_S+lh5_ZUJ?8=41SFbeptj_yW z&eI=QNX1yBbd$hYfE$z|AZfrqR#fqj;MIudTpC*!0G@p9Unwl!QLrTJQhz0~}T4ZqXMo=IQFZ(>X z)HYkXf69+oVDzY(0TQM(i7@29318yL&k5~! zW*t*05m%x~f)R@t!ma_zk1gtsvM^J#otWi8k3v86cbfa8_Td! zQH3>Sho2u*xKrb-7wWKb82mv;*e}z4bQp^ifAz*lAByOzW>~)nHJfWpOsXm#X^TL8 zlCfxzNDAsKz}71nqKr*l-$VYd1ClxWP+Kq!g-d+Im{Edi9f~BwJeMwAb=&0Xu(>LL zqg=IN!9wTalA3~21y|*bM4e0C>{>2X)wGS*L?&_&PN>PGU3zJ4lG!(1ChfqJU9F`) ze=o?w5A1`%uT$jkGx`E6f!~^yx=%qrGijecX;DenLNH>NTML53H;rb zl15YrGN28G(Y)~Nz6(@>3ux?Qzi|E1&s*ykz}hzmb)}LKN=cEo3>DeGWg@jhMM`c} zG>x9T)WG@C>$k5C&yO&#^}%R-X<90ke>y&SarEou+r#toqtlavM#u*uC1U7)+b9(~ z`|ey)4!q(SXa$&HQ<{AOh(>-fA{bI zD!F_SQW`^B{vDNvs~Z3H?Ynbm==#kZG0pW7&KltU2pf3PMyY#_OcY*}DC4f?Z-Pe~ z;07BX8!QUwhfAHkNi;ea8~>%``?|`&ff0h{qI9O zZ4Z7SsXs)~*H|KtsN8Ru3y4A%dk#R6OQz1I_@J65Qdt^*`eaOuOPMFjf46~Ik$w`~ z8)wIZb1I7T4vK4p48vUgNW&4NRHO=|q4`;trV?D&4=IVV`1iCQANpBr|6dMi7}G>} zX}p3d$ejJZxBs+Vwf}7IwIBEYLp&cpHot{yHtL|Zrq4o}j$?W-l4$XVv;*HZF`sDj zTj%IQtS@fMQO(gx(9z{wf2|6v?sB!s-J%TYAv|Sbh&$G#J+(P&xRpnCvnV9-pbCF{NiegObf7n;&U~x>DnsLIS zv=5E{7VdwGMujS>W|u_jG^n#O$gHn!YjW_aV$$@IHKmHQ&6f4gr%$f+rLY0|WF=0n zR4+9>uf87hw6-P&R9A04-b%JfGw@d`_rPdYeJ_kvyZC(|v8G-b8>?a7gk2ZbMl%wH z+-xjLVKe1tDXK0ve@)k>$kvwWcRe%vbCX4Wm<3b08l4#0%A?Mps+)U1#v=)3#G2d0uAJCEKvlg3FV^RGno|9Zk4yV|akz?yiC0F2S9RdkF3t zG%&ckyKijc?hxD^f?IHRhkf|Ysr%zr&Ci*tnW^rcetXt>Iyp>L+2DbfN1C}Ty*C^- zoE8zAmR*9*9*G=9>oF`86JOs5Nn2+@D$7BKm^FPVRhQA~88p7o37$T*1Ev9-^ZJGc z$4>@0A8TryMk*D}et29C@_;Rp_S>8tGS0b`c}^T>EKM?1DGf3_Nf%7QO=VPQ_V?4M zCmv{%B$fZ!*_`n;&b>Qp2E*NLZ-N;-ziMe`8F;A+`}bCx>oK{Ps**u8V>zCny;z{O&=Af1sY`oQ78 z5BXNyh&1!QB=OEo$VW?SZ|fZ4*6wS?dFKZzaJCmd(`vRNS%u1oo_C7M*vrtM=Zam# zZ)VWO>{G>ipy7^W2MXs-|FkYw*Ni&)z&F&8ZJy`9?z28?gIHhNcmrLF9*^LDzz zuwgVDJI1D2q$e2-Y$k7=&uyhjrRl~?C#Jl;R6j6BGZDGn0QcmN;kHP9iglfR%a)jP zWqsDjN`eAg2FkybErmR?Gvu`VIm8p^;utN@s`1=X8lX7&P*zmLcybfvx?U}ob!cNL zbafSY9E1uC{%f)QHIO`3SDKMCEM;j-D6hP(P}p9aTY*%gWJ`FBA0DS~v|>=t#9$Fs z`mbb{hB$T>AnB-)y0771|3GlU;Dx;g?N^l=gMus4aTn`^0}g@{3)sXzXxhQ0FTG~0>TxpmW@6$+mM;$iP=SD+^+ifhnDl7B*i zqyJheRLEK8te}46igD@Pe$xR#4769HNG(7ZLV-pNolY5oEL6MW?BOvwP^}D+ey?sZ zhF2#1n>obXhu$R2s*}FIa1~R1soD;A#5F_$p_2c&JilAPO25br`mO0xBC}p*1;#+S z@8a!C@Ukv+A?EDrUtNfn{p)kNhmq|rwQVcS7_+qxIGJr(5@o%7X+=O)hzKm@@UYLDahm^2R$Ej6G1-*RkR9YsNk( zMy9&EH0zJ7SF<9ucIfJ&oxmZKWcy*|F3!U=wQef z1@p$?z^>b$V|=vTU-tyDG%KjHYLbGFy;NENE$g$CJ+Rc0E~j*RCp{;RK=hGp{nY=MQmvecbp=vtuCrmZUCq1g=E)0== z90n>NrdiE|aUcy|_a~BqSj1+DMn)*XFl{E&?09Mp4VMdJY)-Fxl|HFf zKgQ`(I#96n5Ne}(wdpyf2{DNnsB4p=>UG!}JiLx?_y?Q4Dpho9%uC|ZipDD!#u-gf zyE`Jos8>AI%D-jp@d)5V=Vq&uS6jw{qF5J~B5LY?8v-W~9$o#cRLN}zq0x96R{Ci7 z`XA&NEb2elM1Z3oZ0n3HGNHkR6W8SBEw$7Nio6LLb;|dRu7z@41JP_+%zS@9%I3&f zkZ0me%|6cFpOZ+<02RxbMuuP4h8cNfUYu(o<8bj($=ergHHNb5yoyyCuU`ZIXyfvO zM}qjrCLqrtt|50WeHFUbD-)TYe0 z#^kg~5`1%?^5=1%Az!-Vt)}VDm?@(xrG}wJR470y_BG49{kEb@r9HpCtncaiM^npP z<(Gx-af|~-wwX`gaB5ZIphL?UqK6d9b=mos!cI(3H%jVvXw9%MQ=S4f z&cii=(bG{iPK6kU)tEvS_gzyW=$WyUFLBpf`L}j20mhdUl9bx6=cf9kz9p^{#@vBC zY9O*gzQ+G&DtKc$;``{&B^pNO<9x7jRlJdu^q`B#=a^tTj~V3^c9@$eJMZP^+sZHJ z(^Kx^18_&_0_3zcI7ilyYrHFAb`<}22Vap)5Tw1GaNjDL8wrznFT^fNcwOrPU$va- z@neq^=dONSQ%9-bI;jj8RPfTB3B?_uR(vAH(r)R)Cy7qtOAA$qO#LrR zLs0p%#p5fj`s8i+5jq+udxW9;g33b=qKoPNrfF?xq6_x?ZleA@#&b+-mzUhCeE$b+ zGgkQ>-8D^v?SL#fuOyXJ)IjugQlQ*1jbJnp#jH5%cz5n3AS{130WMhq+y9?;ENMK# zFZ`kUj6QL1Ziep#`b*~4Na?(I_`TEvi^jUAp>pQvl2vLShX~wbYB23geBQjDn|hB<8yzNV{Q$~(9T$5rOauC>nqf$+ z*dwkpWyi+fwQxkNQC2B@vM-=?Gx;S+(w0V@Ti0p<6JdE_Z_I&uQO-RiJImuD^yWgDy z;a6gwnC6x};aAZposp2Pt^hJbw{7h>m-!S~W8GR+>^$3Fl@%P3X|t&}mEey@F0ia@ zs4X$G)ulaXd;%;bskAVw&8v`eF(b#izcv&N)3L1;TRaS0T{R|8thaIVYOePNu9$Nc z1i-i~mG^J=f8_S^u6pfMNg9AD1NgmO5mrJ(k!GCaGoAVv$5U`?cLAC>Uz@JZc_DAW z&E@&|@_h9nI{U}YkkwBQY5ZcRm+NM~4qGj0`ccqLyZ4-YhwhAX^bOiFsc&$mx)s5# zPAk2aofJmIT>qoJGc2_NAI5p_6U)cGhixyvk9;^w&c?DEI=_$@Gp2XFts2Q(uWQs* zH6Ml!H$sL~;5vrqK!99HWGkLkQ9EgUn%_rpL~k-iML(VK9&KI+*4(*F#Z}&xKaV*> zec&Oi(&AAl^OAMXFDhaJ79-yR{;Oam^7y;v7*1zLV`srFx(JI$_bLw0Lo0px%QBM= zd;F2KC%lq@^GV+LT6afN&+dboV|i8EUWG^gPB4NinTko_DZrFk?yB<5Bl0+uXJaRu z%6LnjSY}rNtB3uw^LAj`R~cT8UBfvSs}(M{6f^6oQ`>k)hNB*z?!s~G8eFQ?CBLGZ z?goATtZ|>;CA^)zVvq6MDNN#87C(pcRQ22_2ts@ivJGa8k6(f%)k{ja=J_UzkJ6B#A6fKa7k^oPx(s9T7CslC#kF#)#8GpCoP7I%bD0KvB3SGUjB`c|sX{<`s84%&^k zbs~8yo6A+U%s&{@#a5nf;6?MUFY-(E)cm!4%5d&@0hJhoq7~7|ZRhd`bJ&Lt-m4_vl;Wh1&8TchQ!DEA+&l+6wlog?mw$vK_+AkT=?>O3TOp+*`MyQBOqU!oVMkUg<=&5o< zX7cZs0h+3?<{_Q?p%QJ8P%k0NV?MeXjT#3f|7@fE99H%|VM_#mxYfxr(_LIduQJ$_ zY&lP%kzGe>zLFj)cxDH1TUJU@i%E!I4cUrg7<9C$yfpa-0QYs-ffXi_yN8%h2I}$pPJdUw^aR~p zDKxI$B(KgS{JFyXlPy`Iz0Ze&Zwt$CsA3gp4mpS{9podMIedPS;BbPGxebQl_%`BD zQ_E3@ZA>*+Hl8WK;~F<>xTTATaSLavkA(>rsT=HZ5MNia_Ih?M{LSwz31jXM(yzXH z3>b8^thKdeg}hGqSxs7M?F#>s`q71?CF~GoLZat#sg@yk)fq0+q&< zX=|Z!tPC$WZMC0)-2Dgc5?WN^i6p%uO6)Z52X72DPaHC^gLp8Aqor6GCeakHoMlus zo2Zjth}X*o(D15~BG`WIdknbKK3Zoi1O_Iz-J|fPRDVebx@PNTIw-~kuC5TKFFwVv z7rJmAiNnOi+>{92j>Kub)}W;1_4EHJLV*CH-veMGzMS^F+eGPyGvFos^Y5x_;^&1B zh#vGDt+VSshc(a^LwdC(bW#s_=)Aqt;fuZyBHk+Lhc-1#U&={us>$o_dq2Y?0HJ<8 zEX3#w!8-5{$nx7b&Bpk@MCkBA9hbdTJloHuQ}1Nu z?^@Wou%Xdhr%?m*@pjlRciYq^z&@sXk>KtmRNZP%I`y?Nn}5}_0E&7k)lHoig%iDF zkSzGNc=uqo`Wfgpn}5@-E6lS4U6-H3kD;qChp+34z$o;dG`bq`ylpooh*e%pc@Dv*CHct=Ox(H3%f5Md> zaZoaf{A%x>6UY%@%_66U`MHVAsN73stwA{NT^!?wEUK<2uU$5DUYs3beS2sabyzt7 zT&y_}?%Az3RXDQw_&m|wy%CcU*~DEB>3h`7Q&?cuFtNC5lIeiqab+DiO4(+VCAf+w z*SW25E*gS8`|GWH!f^oL7xb^q-`g9oQ)t+{-#&icKZnU6ZZ0}gkjvh)+gM%33Eq=j z@cY+f?@;=)VGyP7X{^5Yv6-rqM3S7!a4_%B(-=n*w9D>nOGNa;4ILW*`6@abj;2^* z%^u&W`y6PHZDpO;^z7|4)(F_;!h0$Q*m`s}V{gArXi$TpXTZy@cJ{MQ@=NS`P9{DDb|?#$eskiVd~>PeGP>?x5|>w1*9(r z&-sC^;K4kc06yFTr5102GQFW8y0Y@Sy~^>1x^+($geHr@MC9f|vc?q@(Zrzcxv>Nh z{Ayp_t4YOs_=02b(6;fVwyx#p0A8y)y7D^oQUisxfBp z_1im9e08}wYnwe{l+z+CEqo)+Bd_hOD>XGzr5@lb^I^v8nNR+C;;jE$N!5ngx1<&k zqs0E6Rmh01ISHw(c&XQI&&h0V!1~DyYA>TKsZL|q!run3%2<5=er+pu`ArDat-_7oA-3-X1{9uiCOkfd(h3;J@vvNZGK*F@VeW#97;rV z^q4}}$zO?qLDZvGRzEOI2OhAQw^?sqw*;PFehj?vOZMo{hNJqnq${T9guPv#PLjHI zv+>4rZM>!3)AyVf6GVL5amP+hL5lH0fuP}Pr zM1wA7*2ihv3<;1P4nC9|o;zqp?)_U1+oIGPevERN5HHieSrap6b90K-*#eylM);=d{UuKg08H4-8{R2?rbmQIj=)&KH+YfKw5|i=7s4)8W_gU|* z#na6*=z3ypN6l^cCRs8%xeW6+P5sfYRbuh1j}tJ9;f07-@Ig0)5z$ z33vSu4Z``AJ|9CFH!v!a%BdyVP>qI?@EL5l705OmA<|Wz|5o7h4@)6+OM%cBHYwUj zpX(jTH%e36ecy$F{Q!xdyZ_a>iYJ^lN-80ih47z7wpdgSo%6cbS92+qrND4URg3MI z&cv>7@2x2tzlKgSZ>Le02{99T0Au=ae_p@u=(-?%KV|;OxD)q-yXO4OD@dz62%$0B z-wh(rWBen@P9!515<%DT@$8Sy$`IZb=>jbZ6})AkF)BrOUV=o(XR$~^!ZU{2dgVbH^ny93Z{8JnxcA3~Sd6fJU!jw*I@cZ)+MBVZxW!CC= z-MZOOj5VYKsVy!pt(e4bx!8cNa3TWv+-z4?z)yi&Epkv3-t?d{E~qXHaU&Gvgkd`X zDrzWIv2#JXKpMx_xo|oxD0;aNBhDPH)$10J9&=537>uPqK*yTF&HYHW>qm|fs9!9c zZdCU>S|SW{u3Q2 zWV+&Z0j^>{D~3=vkPdj|w{jS7@werv%pPk0o2i{2Hv1UYXkR#iO2H;QPHbNAy`rd| zRy{#MpS0W9Ro6JZ*Dzu)pm6jgiXy?5$NEvTR_xDu@7c8T=lj z0Ec|JT*WA$W~%|98hW6_P<^QDXB z9hQ?$z37ant@51=iz(YowG}3lS(I*j5?`aXkQMF2i3^rtJ)SJTx8o_OLMPCnc0-zQ zhL)C|K$ut0eIvJ(CmJz#iY5Ebue2{)XJ5a~-~$-8WC;zh_ZZXRPN)eKc2FB)CHM{xOP&5ln$<2;3$EHBKr5`*LAMIySu-!<`bG8?#O|vd~zasU>&!jyC&msi5h3`f!KAbC! zI0?|G+(tP%ZDMCjR<8-(1-qG|jh^+xHJiZDZj7^7-MjvCx0)6`rCY1CwSu)SNL1}d z#TK1#vid1aLvT_%j3s_CxTNjt&^vTNbkV*NGE^p5Ii4W>kL>SlHo zwb4GnmIrr}F4t>sjk2C+%~YmWdgTW=Yv}7l}#znhAJGa1BGA(Qoz$^bx>J zSc3xVc!)#b|FE@x|1dw@8{t2~fol*W$LbB{pdq`+zR`L&HGOr&X9 zTb=|$$x?ff?Nc(dp}mN{Dnz;$u|mDN!>eyjmSqS|nvT-BODruSoI#Na?)Th87-Qmv zQhMV{k&d0H%&GkR>r$4^eja#m%Z(|9w{~2&;MMo7<+j8qlnF_rG?V1e>Q0MS0i z8ao?cNJVcS|1eEvo#}6J*;Ty7d!`wUrxm|qUvJa5_P~%V!DI<36#@_C*vHmN!O9QX zCk8+&49lz`nd?)vd506YCCtT&M6nB|pXvlYR!Olv7V^{!quptrS&r&QJ}g$PMcp1v z!&bN{m543Z9;!8Vmii8c{!1^?OZX7?!ZYjf3!|IAH~T8 zsj0!ir+V*uuBu~atnchf<){m!D$-x8VAme2y(jFarCwc^EqL=g`z-~4Fp>A}Ld<`N z0PCKB|Hg$2o43cN& zf8=A4Nn7!`&{x*vx;+KZBZRi4GU3+*+|RzoYX3O@>D_BD{?myKu^rNy-trlF(dS0A z6!ANTOTz|`vw9=~cp!76P-=`rHFLld; z98eMU-2#y5C`b7vsMARg!svHs4ipQ~9)T|Vp-!i@hBvxUt?bkA7k!E27-G+z>v#{o zWEmEtcK}b~nius{UTP`leS817w_3RC`+VfC8rp)thk=@WmE?M;P=8-~)%iq5 z{i>EVif82PAsRsKuY^N4j^kceKUC9?DII-Zdj!a>z7BIrf=jxcRRn?-BV*6z@t3}@ zOA3MfUWF}Ad%S_x&goc7`2_u}grTm*-hq;EhrSa7g$(XsFi6UT{c~^GP4j)5_sPVt zROI*Fc)Ui{MotoNxW6_wZ1t_)0rgdJaMaBHFa^(?_~$>J<8RHcm7(}GF7@wB)iu`5 z8F0<)Fg#SUR037Y96w%QnWerS~PVdh6zO1*gseyt*MNO^I z{vs@X!xKGjun0l5PSpt+;v`$&jn{hq;WH0Hj#--N%ZD6iLWVJizo#$0CD&ov^lY$3 zg@V%BG<~z(s(M*E+Rb)lOs`54Vd#k>r8O~+%0=9#N%-D@>vekw2zCEbrGuXb0I?#E z(h36IpVZwX3b^W) zScsC7v2y(RAeZ&(z9__x(YIdBHa7 z3n!jQgo3TW4)nXCeACiE;O6Y1Iq-BmKJ#{M&9OmMJ?$E$d6}u z6CL8(LO7zS)mTy6LVx6|w>b##C5>iC(xO&Ltwmgf4Sf{*8ZfJZ74$-c0{rop(Gn9x zMIbm(qMJ+mb@%FKIj7ne-S7t=U-fyWY?OdVG4oTWi72ZnHRWAlYS`%;yMENIPCSzb z3D@Lg$A}~F^|_e>=t;+PuBQlhZ71kuEX{0eki*JEc1Y(?iNae#pt?~<&y$D-8b$yK?Li#|6S{d0 zEy2^_a8=>ZkVLD~!)v~P-wJ?BMI@#Cjh;jvmk>wJ<9-d8*72?d#T1E#txzNi3ELuA z$$5Z1&p+Bq#|>{H5x-~iKnWI(2FFm3cH-lhx;iJ&gupi|iV~1AD5bGDP-cezW&Ek3 zrD%r65bZVy+heaQ19H5$EXu#bIcrq@NOK>euz{>dC#fSBPN)1aaF4rFDZi2J&MO41 zGl?1t&)66Mn)7P>@%o+j;k0f!F4hg&lHba8LHAr}U4OCnBLehbG4=IGm@~vFj>x%c zyz@&-E)fR4wp<2&ynF%0+XR0en2K%OJ2KMARQ!O=Cf?#22rfu|_N- zIa=mHKuWz`QjJj%A2&0TvxWtkIEg>^D3eTejK~VpVy&A{R@+J;-s z^AG@Aow|7ZsA5@x>nQLSLexsgc8xhpy|s7w^i2krGI!7^9o3&E2sRIW9aDZMo>$tG zA?5Cun+=qP>Qt^!6ubj<@SHfQQ&c^Y>uFK}S3ufgE@$D`VoX~s3T`Kft}k`+XGa!F zc$VrAg`3zX-h&K^z7Mr0F6>5Z5?O;&TF$`riG*!_WyNEBz+=Db(igu1#CwkVR>$Lt zx^V1FbjJEa@odK;zhULm9}sP^Ejx5rtnvD}pp?OK4kKc-I1c9)6S$QW@sD4_QxwI* zvZC^ks6V;to%-ow%QX8xwh*$m_=s7(l}4oEml;=N@+?fVF2qXBhe=6}CNkZsj#R)K zy}VzZAx(Jji(HeOOZ@eZdNd8YSXz3p0Z!B)i!@CnsPgn)lY?i=_)rPMl7VEp&v*rc zs9bWAJ)B4=*Z0^jfF3Q5ff3G=yHnqS$}(k=)dLLda8w+#%J++N$4 z)l@|dHeqs1u&R?yDTw6B$7aa%fPf)VWSHt{n}c>U(xHi~jDCH&hM)&zmIDZn8`ex- zEi)OdV<8+K(*CDnB$Gtv3>^5--jOmtSdEn4JdJIwZ@KleuL|xQx zf;zeng0oR;1#POmw7K-IKS^0Dr?=G+OId{CoR@wuNUCQ&RdM6+lHM3E=>m(99^tn( zTutY!dg^Fb6vi5L|F~Ce|MKvAX(@!cmJA#{+duAdFk+=ZYG11xN))4<9#21cFw!Ug z)e9i|8S=_~s9}8Yz>w_3SUhEe-jF&GeLUWI9KNq*?RwqLY z7>HL{6!!mo-T8(N1EMyOns?!M%LK!8iw4S8-H|_*hi2C z^O$c1+|ykK%ZPnz)JAvV#5W1csxHp{5=)($ZHmBTR~>p?m6pu}_W6~uXE{NeS-_9d zAkXv*MTWU2Wn*E?F!arzLXArNm||4iA9?#}LT|WEiw&Ku5sL%DN-^OX_Og3~jl(xyqtGwlaPQ|rsUn~&2wv%up-lorY}tKM zM)IrR)ZuT*zb(5fK$M%~C$0Kxf4;AVhap_W&Z>|qQYIljA19aXb#}3caoQ8VoEv>P zmM%wGNo!q-7~|nmr8OLZ$bh88KTUmI`MU_>@>Jd4G?J+kgtL{+ln%S!+Fo2FvSF*u zD3x0H!cHYb>I2YTY$KXUAA^r`uWWXwh-zqAh^N%0M3SbJJMh)S>}iOo`3bfqK~%V{ zIo=ha!#2y2Os=9TssV}=?zo!5c5?gJ1jXUoAPoL?P-0?AZy2T)T2BZM>Y8!CHL(_k zFCl_U)8w?qs``ApR{deV5T&x%n`_LGIKaD7{FTPA69~q7LQ+eqHmxT9jwM5;sM}1Q zacY@f&X@ct$v!Z3Y$ptjD0BNcEK1f{>0paWibxZgGB5_P41HfDR4%>5i&q1(fv1R0 zUin(Wmg>!I%8IO|YKVA<^^Y&!Cv>S37GvIIqPz^p8Q=5qZ2s|X$evz z%-3zce0P~4z~B10!HGXi#{4+pO37vkNk}#%44PL08|Y2l%uyu8j~z@o{qVUG^0w@r)8lx5e6g}6b{N-KE5A2w?TwZC(#MSBG3{S6!-}C3hP+PO) zI10|y(>oycry=C)ofGa!8V`$8t$+pSt?26t8+px4#C_3y&m3$jT$!#+ z-8c1rI!3B2&t3K_fMwIwV)+~Lew|R*sA$fz>*)>%^znzMi_Cq|P*v7kEiMGSo=yqz zTU^*AC7jv=ieQ_Bx?%Mx{u43^+p%S*pXJZZKd$`vXXG#b{>2_Io*gVriJuuB32@*a zQAKdR&MmlguCiS8;Aci7b2ufp0VSptQ;V97U)ri|5?O$F`jLc~_KSmf!5il^BQF(G za6cE%#d=0YHZcG_eZJo|p}T39#@1M=z7T62j@(>NEjZ^#j~d2cLU0kTv=}r}?al=) z5q?0pE9puY!Hul_xJye?<(zVJj>%>563kB6EYmY*ENZOEo_)+)Fa3yPTZD6|uX3Q5 z&VkH>a|oPi*y&fSl&$))`OeIxF>-CqdSqcUCD*RPuHcL%gfL7|{s|G6<2RuFYsC0(fqp5y(gXl8(G)gxT)=q;6`Pt zZpn%_v9~$A-k^oenm!l>fwBLWRYmH&E;@U}M1=H~D#+I${rm*IrUKR@a)%E(_U)CB}9r|vH zVBW+JvIWXr{w1p6kVrxbk?)K zc62`l{^<$1ziW(AZI${d(d){*zonIPW-`)JYQf=5holtSQtD67l5KXQ=D8_4dT0S! z&mNt=Z`B=im|cruvj1HrHEqEKkkUEW2ud!>H>tN)So|G(IN8NNbhMO3(0I6MZ99|k zolv{(O-=mg`ra+PR{j+7AujaJixW4tsagFuso}ok3DC7rRJEAeV!Ihuuj!c4qjW#CGXPff)jOL3lb&K%WsiS>hwM zGMpW#oc^+iCP-lw*@~%6od>39cS$u9vDax9?QQ_!^Fw61qlRn#*=Ycq8jZu5fSF&* zK}<`tW3LRXE0+h)RvZ=v!t_Wxch6;^uC)xOAk)&&Aw;3mssaJ6Wl4H%Csx)L5Nqyy+W+2QCR)lp z$B2t`yha%=6A@LP?yt{Gm|sP8XBjK{-DJ|9I>Pq!tcnvE$64dHa4h1inqV%^dRg&) zuGZ5SK`wl}Z|{e3ngY+_`*L{~doD*~*BrX(1l@OcdF{f6f*OX`<}$iOTB0{cOSivV z6NA}l9c_wolVE6g0f#Up_`50{6P-9hZmQa=Cqpdb@OjJ~X0%^R$dpVF?@ zN$#xEoS9`D0F4g@-8R||{{p7b;>vd4wA)kpC=?`*uRqlXD=fqU?XQaSnH!iVQo6mo z6aUbfgdSZfXON+S32M9Soy&SW4Wu3)Ur-o*u|vmf&H~J)0iqbYv~G=Se`6s>q{iV6 zZTFu3|3LHNpq!BdA>HF%I$Jg*LGYyA*c$21;v~+y)`{D!y3Ek04U;`}+3(!7eFBiw zzi9*uPg?vBY9$BK7eegC+^uE8Jct(zcC+PflXA$g_Kkn^qF2x!Hln1C>DfN=GnCq8 zT2x$(94Z(A6|Xb8wfN5N2J6xb%-fJ-xn?iWY$gs9N9LmU_+2gcC3BrrIYwti41l;jE>XD4F149&Pf@!Z&3Rtf(m z_1fh$o}Tx1kh#*=W$%`!?an37rzgebR!_xcPbe_AWN~(S@`6(*I1w|VNDYd6);JpX zQiHqWbFZ_~f;(Fz!hSHsRyuDhe1ygXRj<%Dw~p-(J19i!6A@(*iR`^2Qqk>WyIJgV zs2i_(OEGxp3qXALw{HWcBE1G}N6X{9VD7_Tb6i6&6|QEO3dq=#Ksv6u52550AJHBZ zw-pc}DiB^qK7=XTbg+4X3>Q5jn%{6vA+8-Xn@_lVhNHQLy%uTyQVh=jnSqNzX6uGd zSX_liq0JP7kS1mryUpjkWN^jL`7t;p%ixMKhdssLe3lnwPsopZ2O>GvXte zT%w5X5n9Ls*F;40upX6&qid7=9;e%3ixI$6s-60P?{~DtV8z8`AI9_@>LlSHjn5YS zde(3Ux$bO-O1sA3(#ixg_-S8H(*@`Z|1AiueA!OOv8b!NV>y zS~~u&qK+EljX!vfRGG~9CW&GpE0&!M;S76!N|6jUpY2+uA%gM@9)Eu@^y9K`aRI{< znDJs%xaxaBl>2KJXA#tMQ7G%gu7q$o%O||=g7R`cEX`v6?O3C!xP3RT9x}|x!j@1N zKl7kn+qVl=oTd){AT8+}9n$1v0;f^ks89pXOeydV8$4?sLA5CCuTB*8?t;`{xSp|vj;H4do;+J>U&C&gf1(((q98+oiG1Au?YEgm+H>~ z8-YZJ_!=IKeib4XiTSjm0h~kmXgDLI?9*;o37TbEZ=J${VyLb2(v88#ICRJ$JB!9A zNG_Z!UGovFb58EOd;TZ6hxEb|+Yx3;x=Tg0&PDaue)}tH?kJPub=qYRup=l!hGEf@ za$h^kVPt-Q2IKh7U9x#5H-~+2{gzu%JS}E{El=##LcB365R#vt{J>i8>k;)5s|jFD ze7j!fwM;%VVup6kR7^wXUtcL+{x_3jpgQjeQ+1NnmW;@pPk}g8<$Mh`}w8vZ`IhD5z32Q{gNlvV`t-&^Iq($P%lMI z3bjWkV4+Ez9SmSELA9$J4q-OlVpHzwTHfMH_)Z31-u$YESGyY|;ni`0m3L{7jsV1+ zcLp}_Gexu7KmcK#>k`9tfvuL{z{&7mC}TZTqwVb^sOP2g-R0NblXr zVNAiVWFhdEuyOH+SMcumo~uKsiu&B#%Fgp9v-Rk0#oB`E?v-FjW zIa<~W+w*6W63NwlCyi(8+pV`kuix%`u=W<2J`!O0Y2_Rl8_@N#v2D1x6#(E8Kxpp6 z|B+fehF{%Yk6n%Y!+|?m=jQEr#X|aSuQRFncOC!yj=Xn#d>!hC<}|ApEVMlt(-$>9 zzH$FqVLda0FuP54%16^+J+;-VTXZ3%>B@cHvOb;CbnM6lW7A5)SWH~*w^jLW1NX0M zT@}Fg#`fcD*q;|iteg*(@bTsOun|~p?3m6nQz1o#$6VsZ6py~(oNnx!)RqNhv;e}- zq^c7A4^sz6|KfYD@^O&i7#ak>mg`zVE8X6=`MpAi;)~vdIkGHJ-J}9jF-78=hqbBJl6hY{4aOk$>(HsDN)Y;_~V|HF# zF{8I%8D;Q!F4QUzHHqrQp&*__VRy+AT-8twUl(IP5bkyl>x84<5i1dqpAap*M2BM( zp=qf87cttRg^f*v!JgW5l=8v3-w&m3toQS!fq}>Om+`E=ec&y@Z`!iU{dxWLloEJZ z2QJ_G`&P+5^pM?ir%MKj;l6{RWYw|Nd$|;z?2k@bgeIb(E0veDMUJ`t_K~%fUoa!p5U!jmNt3B` zTC9@oEm?T+IUzduSSdK=K=}K)_mX`E2R8vVW6QAg(HpN7Dbmg#90mX}R+rRCKlOV4 zg8$mF|7Os5hvy*LXI3Y%Om?}Xm?L zTW|^BA7nFl?eTHR);eU%rH*YCF;X7sN$+*whHcEQ#YAYnz+{L=NV}&G5&VXO5c*6e zpC-=7ysLv9A<51<#et<_h4W1s5}VvS0$+~yEGV{;cnggKK5V=h1U@;#5QG#S|14i!1L$L?%}7}6 zGz&<-H#q~3;{;i{v7fFYhTkWWYPbs{XX3@uQW$c(Fe_k1`fH?6LYbt4&l_?asP&i% zTj@fS|FXdPkS!V8DY%0;_CBQhftpxo+GLUoroVk74lOTLeer+)bOwibI_?F(q}~eX z)GZ$5m$TbV?F+R@MVAO;qw4A;9Wbyu?kFlU+noW2c>S7=$K|R>pw$kRi-oL{h73Fc zvy0oesd{=%lvC~m5C}^2nc#&UEoY3eardd;TT5{Xl(jDb_C$j+dw@VCTYYr%B~<_B zc^znZGJi|Jn#g~K^2I>IBVEq$efW-{fml8xXVBH6^0!T{;oOPup#NU8TcPr8H&{en z!0yT0_SdB^c27g&`B11o|J4oPUiBh55-qe*yYgx<_TGB@)$Z(6z8AW@49vbszO}HG zJKL|<$Hx`C!ZYIZ2(HBD;JTqfGoNhD3|%oNsy3%naHm8umpiQz7R`!Wt4@ zYuYMlmK_qZ=Rv-@WzWbp4F%&>9VH%%-Om%vU6re!G;9s#Dz9_!bY_Bw;X6G@O1;6T zH`CV_V6$`EiMS_#3a0D$ZSY%LUCj=GUKc}w9LZrQb=lxZ1dM8GyJl#KvX26-_H+Rk zVFl~=X6Z)=}uy9q(gvl1>ghtVg79ID*C`9a7hTw*(1SAV@n6QDP_X`4*;-0Prt-QlB19~ zc}BcK1A|jul)n-Yx*48Rh>o~`Ks~4Oo0(nbdYfCtCe=rv8oz$CI$65%53vqYpyN2L%eTT4~oAFvXC`*cKY)itJD~jTTOWqj~+0Vjo>|{|u6Dg(yaD%Hzz>vf-l`GrR>(bPc%gantW%Uc1 zqCkNMQJ*2?Hz}mVczQXl zYjT-zy4sD+O4C?>VVLY+UAP%Ce+W9Us<4-#6EKk`td}7&c)xN;s-!w_(IbNS@c*;- z?rV|T%Hrtn-{q&M-LMvrV0SkR%%pcFzfERH7HckWfL!*P%mKBn?)Koe97_f|!~DMU z8t3)SlblZ_$(G&j8#K&h2Y+8Ow5?02R4SE9b%{cs#vFNnA-pN*gMmuvh(kU{n9mWf z>hDw1KhG(S={-43sV|Jjry8cLQR>~fl;?b0V~PU7&r->nqmiO;v-}ZnFIap@Q%$M5 zpuLzX8vZaA3^*H7%zLGGMef*itQeueyP6z1YO~YiY~E0#;Q?|P7ESk=4ET1bW`8Lb-Mfch8KV$_|Pm_U-^F*phKE_WT$WvM`{mKc8O zjK>o)>s3-n7-wPJw-ovHI{8vC6{D8VZy+R_*8@Rri}Z&JU2X;SLXSai#Mp(7Q0`Rc z4BnZu-1~R$Ef{fx$P98wEPToCBt!}rBpnHcWOtH(7&SW9(@aYx$a?bCEyr(CXmGW~ zOVS8q+$YT3CoJF_GqFZQ6M?7&vm_&h9Jr7OD<+jQxT3|)4Dv%s#6F3XB;l0sTHxP< zJp)kRcHayCt+*>fq}tcO-1eEGpKg0=`9PG%$@ zTx&`m<5B=Z5kLy@KEdrv&9~gcbs1a*+p>j!Ef2n@;z|NWi9Y}?XoAbpF`L8H!~s<& za#M+cxFX~dbIzx)qVr;B;FOkjmV%@e$VNzbX-+e?sH(*@5?zrIwE3{Qc4pE4+IBk`)>QU!#oD{1;%D$$TC2)lC;afamKL|yM4CRKP|2E^1B&pAS z3R4S=#mMs3cv?#zPpf7;HG|L-XsYQU4cVm@vx1QOOr8V1KWOYF@&uN}vZV0jQsL)7 zW08*3N$p~HiN8WO`#*G!fwW=hd*85cv*B}pJc0*)VHGAPfB){@`h$eR4Zvv z&qbmOt(&L_P1ac%&oB~fDF6&jSv4_5G0*IBO2HCH>1Cu%J^0una3+kQya*J3Vo|X$ zaS7;#>X*Q&eK0g;H*!boqE;e_sGuI~^(1v&(lqRia%r7Pf>0uM$ek;eBBJyL5RLM& z`4J^?mOw;%LK0DNk*DYd1&s0$zH;7~m>ZvGUGAr$Qb0O|Y`QcDokHJj+IC6`j@pi_ z0?LX-dmF37^5w2d+rd46U9|3h%&Tdeh;97bq;aKJ)Oga^r_Eh&w>e}%7E$h+!x{Gm zVi>KQ<(;1ic1F(f&W%hs@vMF~aCD=tX%wOPli$?b&$HnDPjMf%Kp!*jf1ixm|2)}x zjQ{!|59WU>GQDk8lfji^mZAjyqlyOFP_)o+9LnF24=nvz>5V$D>o_TYi)-5$3-vM6|6_M-{kL`><3ByfGrs<7R&#Cg$62QXJ8*Pk zvP#CZ(pZ4=NGfdHj>(9BG^33#cs)M!v*7wK1j_~bn6>_&KCSwHZ9RGNX#epb53c_e zIJIpED5{;eEzP$&c4kSlq_8PjRX8iCn3o4=44e*EgD~R4sP`@?MQh(esi74bl{B+o zKJ!uV*RmhQT>cCq&XXTQ#RTVE|H>Tt0sTK8>h6Gi?c^U)cez&Yc{G%2gAF z85fr8tLHf9=i{Z+SE%c3QrVi`ojg6_U(tK^-h5&;doKCJnztpNSoNwD#SOi7A2-dv z=(E`Ve=q*OHSyoKo@_mN-2Wfqd5r(`Cvc3t)g{If|5tl! z>oNZ0gFLs&|6k)YDk-E^dmuk(foXmX!d-km@5^1aq@RF3<9w<*q72;BB52U`ndX0i z^?%Fk|Fh$N>^vPi|F@nz?*9++tiT&wy7hng*>s(C>2X!FW4z;n=NMR))F_d{eYgckq-|w<_bzfq(durj4P} z3DeC?;FFH>F&onQWVW@3&y^K8Rep(%V@7~Zj%gOdJIT!;j0H6&fs)ywBcH;f!h(E> z=U5x7%R9v)lPL`DCcw1h{>mw>oeI${xiiZw?{lwzd-E*7|8Hgc@xSJf|Jq~rU+t~- zqy5)|Jb3?GzWc=4zO})HxdVN)xA;>$3#|WJ*?#=5dDj2V*!geg>0|ug2YKdO|4XoW z_}l}1d}^Ns)_+-4TA+_v>wmjljsLr~{q%AFe~<@H5DrW{Nt9f7m6DMQA~N0qbv6NW ze~T}F)SE5~Mn)m&!W#OTt`)x7)JkgFI#Z?|kZCOU+yMYQs=e>Z-%oZ>it5 zp4B!3tK|P&P%ms5kA{PW{k`KC|2#W*v-cyGTZoTzR@YBX_g=g@ z`+5KPUHGh|2}-(RxXHtL}p*M_l*bPeuNo3*uk6KEX!v&Jx1;6-$GE=c}vh@8Cbc z{R0}St9nRhM|&qHzZ@RFY`}Z?$3KAj0|w(?Wo!D=x*2R1gX^Y4=w^J>W!Igyx83&I z(CmhBlMlf4q46y=1A5uaQW6Jj2qFd74`A(GheUBU?7Uxt8yHX$fa`Cy8XFrDg5L^s zVh>U(oPtv~1osM>BuQj{&A-*p6@Ib-!xw+6#P_GE$V3={Pr!9qCa~3JEplB~Vinfb zG{lqJqNy;0Fbk$grV+%v=B-cW>RA@FevAQJC96gmyM8hmtDUJ0f!C}|)C{^;h4Idl zN040jTn$+!jcFSC^_4DbL5=L&&~_O2T@mSQIdYwqHU#J0vTb~S^T8Fzp~t(Lzw)!#{%;?m z=kH_I{=e0FI%fa(=>PRFkL$V>^{<)Z+k1f5hJbLAlnLguNb@qsq5K7ML9_?72~Sd2 z2X62t#%B{7$Ko5y*yXmLz+r{$QlAq!~777}LHzn~*bDeWah z3_8%vc-rKD1Cmy^!+J@XE93pMA-yu<|^b--kbe<=+&H0jcV`>%VJR) zdL5>mqTjdE3oS4wj{?Z&dKTONZ)XHD>;9*;Q?>tp-EKd|e|nh5w*Qdli^?hRPG%rP z9;w|h#x|+7br{amfwhkx!P}27y(+(hPoLH_7g4;irfjb6H)ZeF?xg0h`)9$#xHU^I z<<*+BRH$GcyOzmFI>X(&snh!YSx`oN5nfVj*qEGn&2f5RgoBv1v5kwg*cRrJ=GJ(8 zOJ0#OL0t9-rku zbIE_&+Tyl8V6*K1wx5jI|Lr`=e-HC4%l=1`h;69jHC2}WoPIn$0N12qESbH8aVU&w zkNg{4S7n!WSsaZD&#zECx_>SAS!n&=(g$pR#`Oz{$CIBtiVrZ<(960 zufiy*-T_xHFl`SoATj2IRkxbj@oUo&0~+VXdLibO)!)<&vM0Nx*6phesZo<)1aod)j zc&+=t1vV_NdI|jDcdHLcoh%};r#lOOS@Xm1f3AQ;3-SN){d}%IX6gUhJ5~Fi?Vat% z_&*Qwto#R_3cF$4r12$m2_HBPrvhA>F-XFM_DC2xPA2)<`UZS-0DL0g+i%_d!ylZ# z{dBVT<9^5a8^CG{-WAf_wDeDoWC7^9-ST> zzCFQ~C$%cS0lXzc3ap1lH2;)hpeZa^kxkgx?@j7oHC5gM%swF)WSk2%HeTpfq_l9B2(lxjNP6Jru&3FHI%l+p4SIuUBZ9`I- zgbEs~<1jS9jRdrJF&~|OhSbo9y^}Za=AP5i=OQX7tWpsTsz8)+K_aO&cdQ z&HOiuD2c6q)?9YC4@uLvt-}tL5!}Da`RN z@YM1DJA`_veWvsOCp+!dlPdq;dd&a(FwY9OOMRRbwL5g2rMHZ?8UqR^nsuMhTouP* z8xj_9=#Gpdos-fe3Vp&mu$@;`ydT*TDkXR*SUSpogWXiw7#45T))u6%s-2R42O79n zHUL)Og=s?%FSopw*TTk566%M&5$MK_vM4%YQRt84$CUOuM$Rp`uNSgrz>9lnahmQBgPy z1z!w*B%ckFOiOgyEd-qLyRpOhqrD7NqpG~8c@*X(y-Gz&1nrODY7qJZJ?(hcmvIVzR|6V@a-US!n4ggOD1l5_6w#D7Epeu~k|8uMSI~r%JL-)APohzb5=rrg@k@+zqEK=oNGfpd zxnPM}QDF>6BqdQqBVZ{UrU6Z9a7;-sf^`;Cw>xrKOuY>N^xt*i1^TE8Jyak)L5=2r zTx+T48nMU(OG6SBgL4sK$~v)$=9q5&2?w6Az#Wp9^znAoP1Kr7Snwu)Y1V^14t;Se z+kvo`-)E0NCt4wy7_c<_gQl|J5y6{B;A~ightUe@^{|&KsW+UJ+$UHvLoL<~M@SYY z_{CXK&KOui9T#)24D%v)4v{ZbV92t6SR5&PK9&f*WpmS9(apavruI>4AAeL^&ILr% zdQSR0>n21DP|+R6c=MuY-8V~Vjl%RxHdlqgo=8a6F1UqV7a1d-1q#;8V7ZJ|iD#9Ck z*MULUAGqc{HgUuPGYknyk~=tmb677X2qXz9PNJ!WHOO)SkqkIl3Mx+3-&Ca3_mao>m|CQpX5_|(2rUEdjFdG z87cvC#u1S&jZSDpeZf-mT0TP}{K5Wpl2Xob|Fyok%J+;UiS-hIB{Y?%(heNNr4KqB zLg}43*PC~OB`jk7(JR@1a-&Q!1I7hv>2eHIMz830;o*Y|S2XMoLarn1B&WA@yrEgh4)p%4u56MW>`jdO?G8ZxO1&uRwNX`*x zWHGsg%SFc_Ng1vR+28^!yx`Ze+k6YN-bdjjl|8tr>C45w6gSU*UmnSK9rT38JIt3f zBBK-PvpA5akd_0G;MH6Ht}WkbMRopi#{uA(?^DiC2Px$P76lz>I{-ZrMp;U&x9Vu4 zF7zZbMDu#6)_61Fc8_=aamLltsyYNLAMKr+A^3RPa z_;WAY?i1@Ky?O|LD8jHvjPmA66!xe;@+0aF7`x!ekgZEeH)bOA>3EM-RbRU$`$97V ztf<0fsG+La!l*GyDh@m|X74Mf{t7fl`K~;d!6}QVqC-X1D((#iq#2M)dFBgafqYvG z=+JX)p>f1AxFRuHI0iI8w+Yh#gAbV$ezQ1dT#K&^>|9}gE(f#e6o2y$ynFAgfc*yL z!3(vT=}57_f?+iFf6qvy-Xg2$Km-3(uV}AFebIr&Tc(+!Ui|;0B#7jC)53*ZYR*8v zR7W%t$4nR?mNwNcfc}jWBNbo(0NbJ)zKkS$S^$_Nx=~6+w^1NV_HjJ+o#tJM~9>LtM?!t`H5t%6Q2exWm zGMA=WpB~09(s7v3EOvXo5ltbBeanUL7h)ueF_+^^N4u0UMGq?ifo54=*1b zfXM29{NMOGF}z^^|mM+Z4~M&}e=v8dP778Pg8;+m$7qKr~7wY(?RyQ!KT z*vo0VN`-|8>7X!JtO2!~P9WZLHB~0p=4fQa4CPtel75F|gmJ)%IuK-N?{~?fEEXgk z$!XV}aj&H9Qg}JMq-h!klzW9TF7Jd>A;P%N6;nrojY16RB{cK5l|Qm^Jm-drq*GKG`ixu(AXOuvRJXg*Ue_$|bCO4vHi&#glDrB{rpL1ceI$oeq@mBX*J49| zF9(U~mm!z0RKPW*x>SYbIe!?UK#KF!AemrZL`gtXFQuCM6)ol*NEK8}!V@eh^;?vx z+p6a*HYC?xDuJZDfvP}9mNiN}%Tf@zQh9%Y%aD{d12qroFE`h7&X^6O8az|8P!PXq zLr$rG6<&mPBL>AjZ3ZDvA~I^e+WX;u)n4<}!I9f;ZU23H@9PtFC=6NrHS2wC`2N=< z4!$)qH0D`KaRDgZp^8z5oKW?5(3`NogdCBZ=)Hbr0z@5uxN)`m z*0A>+>m^RxSzTX8Rt#Um>PN)Z+4skXulA464qkrR*q9B@*p8R)Z}JGAX7rYDx~uE! zbr4nHH(ShIpv1+xIGC#+UmxzhJo|p{_1@bT61<~N9kZ>itiVsP&xYtof>NtwR^|QH zY=~fjHz^2Aw}s2G`R)x#V#88@P+9)hW)5He3K_KIt{8v^_TT=@jn=XdhiH6R@HpJA zP%DF45Q?V_DLjxM2(?~cQXp5dSHhIKNv%>2>xxMWDvzxY%O6ppyhJH-M27MZRMNRg z+^Aa})g;%9lEXS~5J}30R19dwH-QS@+W;|0S=Ju_fe+-8kUriE#avK-f}#!q9tPAW zsZ+o$d*}J-_NdT?mIGoI8&}vva*;cwV4&5f7<^0qCXg#s1@4bSC9qp+){=~kd{>bN zs(Qmea_jmW7V$H-gUxFd6yllWmmWtf-}S7m6618Lwu>{Omesh@s41H)BPU9%M!4Az zGk*Y)JVl^Xhg?s>)`3QU`^T_hL5%Rob|-76wPG5_^s`b;qox_8#K8qab(_R#af74U zN9{G1iddiLfLNOIMnxZOAX6>jq!efbjmHz+CF)yqPNAiTO#_jojs4bs^<7>AjeQw3 z+(uocMq^W7Oa*4g4@NN=hQ6GQ%TVs~VcgGK%VHzHtXly}H{|($N_P}zQM3s$8|w^s zLVXDJSft1;Ho+e-#!ZA(qkGMJmL~*Q?*b? zgvKZGOtz74f+a9Ij`Lx7t#|d~EK&z?k|fc{94n6U2Y98TLfb-ir5v`R9z)7RenekNvMJXu`$6R zrZhl{v>Fy@H?GeFTBj*ypsRV5o$Jq^(&I1XQ*T%eoFor0lk(| zX?RIDz--5VEEDR?4d+bndFuRfu5M?kFrA%W4MIWXhJ4ulKz+R8!NcY`mMGWOCoZfC zABNXIrBNq8zsc2-4#Q5f+58X(VN?Cx-a>~Duie?%+1Y7+7+$BtFmEGbS2Xnrr-$f| zqWDZ6*`}rho*gxerE;8?G#hy9pXo@G>Zc2d+_BStxC0IEt42OS2fgY!O)!wBo@k`> z0qiQ)r@uGqc!sN8Pt2w%DrqLpUCBs};G!l$eMdeaaunw%S9>K?8)mB+KME7F@ejl6 zoEjvl>(wn+mbz8K5@t!sB@@Ori&9^5?U|eo-Gv;^FNO?2(E;By+(7eceTvdMfQ11Y z7!TloFpkiELA8cUV`asVy}89Tu*;KFg>@pU8fIK5hvQ0NWW=&mE|pn}M0GW7g&_3{ z*#k*wye6QKHis%Ncw>PG*5!vHbzVc{*YmaoLOrT#r^Z#n?iLxBJsCO0j4`ER zmt(p`6@ASxDm{T!OEA4d0}!2}m5$=y(1>uMsm3%UKPVZEwqK{S|4tjLm$yCE{!- z3@KZjFu%FlZWY|%*ceZ0d&zN1*H`R@(DNJjg3j|-Hks>}1(la8w(Bbz8ppeTnguU3 zRju1yY8K?FT3OZ&r!q_^-*T!RN;jQGMzn4_&GH|=@ibp5x1Q#8{mo}iLib_zq#J(# z(tpd77KeF@5?d(oC&WjUh}yfX;1Q_sEY-~S0#7#) zn+7^CR7I51EoZ(tqlm4pP-U!3>F;lghAg;o+%MJA#^rQ1AcCt+Q?qn`6>SS%u#_I2 zz&i^qI>-Y4n=17zf4^Sq@Rd@=o0iyaa9{E^8_;ODBBCJOC}6YuGZU!pofqw?8?mn4 zlU1lG=rD(6zhu9ovmx;`rP_ej@|bbUW`t**1#kmm9PY689D zBiiZ@$@MyJV`qi7<;umkFZO__QI<7wfvf27HUw zmq?^P!jB;bw9XR)5<}Zl23r}=NEFFaEQ$3A0R30=&7P94lw_|p zYd#wevlzW?P|TI25RE)I=s_qTV3f;Jk_kkYcqK+7wF-R_gGfhcP->L%DB7*Fm`c3L zf35~$#D&Euuviv<2Q+oHl9lxjM# zGt7S%)_UgQ^pd71Ap2=3LS+{=P8x5tAIKL&Abm8=NgrwCRy6Y6U1`M0FNBq589uOX z`F6iB@A?<(YE7=W?-wOn&{j~!n%ieF4+DzmQcTS+x*!~XO~}2853I}G zG>cuqT-1|MWfdg__Ac=+Sg+@5Yjxv>2cSQoWnTw&l{*g1snGY8{yCgC^N z4{3(cTM1ZMKAF0=&{i6H&;!EtZ7upDD!(&V(&t)1Hfz}Hnjxu>#Xk_aWv!_@T*P`P zPomW13zj{9;Dslw@2K(!3w&VMoX|VwHUYmy!g30y;uo?syG-($*@3SJcq_1I;8>R)B{QjYTw4SCynk zqY;vw6w|N?dX-02gWK%R0)$Lop_Qw3&`3is*ZBeeo5c^*HSRv0v0!rXi1J~fAM+^o&6j&KU%A2yRYtRN<{{t zFl)TO^<8!YO&7AnQw-)gMqu>Crb4!tQbA)J*^ocqZ9RF0aTyxZx;Y*jgzmDS^$tAK z@5V5qJa^QmYCD!a>x91N=(DK#mR@0{UqUf|DB)&KK)3a$65(RrpFR?NcO)p^G{1(t zaUZDP^P_o34nX|ePa=0=?np!jPNBmp7H0?Ep<@`ludQ=N;^6Em4F#>Q?bv!T7h9^l z`lVFav`v(uSS>hvQ3UUmzYCtho7xIX?NMz;)hR5T@;&^Vy6KLXZ& zQ+}gpLuAUt0m@lQud7t}1IkrUZ!Y&wykS?{WjazDg0a`f4Bjg%D!q1YL4gf>n2CY! zmn~)PA%;lq%6cDhME)omHx?vvAe$v^N8wDXQ5vzhFGVXQxjEz7%{#?ls<*%_LUa`1 zObymihUzxqI!c#U|3|~IA76$kL&YwCiQtl?p|T$o44Hcv7x3XoE<5%e<-dM@0|QDD zWlUNz8nr6|{QK3}Klk^J&Q1>gy^p4ysBf;=s@8af<$V!l$jN$e^mAd#ncpTDE7g*# zw6RpRV}gOQN>Uc4TZM&b5xS*lq8h|y!AY1B#>LzPBRJEXesVf4Q7#7Zld^z+W;Fp2 z`04mH5)5XJCn+rjRrXmNQ(ulgOCvO5GFP5k?Pu-WqUnBaz#i)UYmVD5iE#V!3j_N| zj-Q4^o(7eERFV#!_grlr6&mLb8$VJO{iEcWuN>*dyz$L$y_h$Cu?5X{eN${tV|P>) z5B}G-b7KDJIqX<27cTcKlqdw9Bk;)!cc$*{Q^T+udnD z#(#T|XGKXFlIv?JP${PHq#DLb;K^A@kxfSL6h(#ZC>JXU@DTO(*53N+8P5|2DI;B_ zG7y8@BHQ?8=AY5RyAq%j;3ABJj@*PCfFTtGv&^e2VP$?yxlqZ&@|XI5HtFL>@P1bJ z6#a;GNmS@Z@h`Z^cLncQN2)5W#Xfy<01wrwv)q|PEBnm3rnp(p4Y{m-mATfZPo3(! zQ_@#GsJ1aFLZL-;fe)-7u5bNl&}-_0AYlYsY=CI<<-?P4qEM5YJ zGO|3F)pWWEELGNqVH7Ffr!-}$=QtAAy_{7V=UJ0`SF+5hI~dDE?G}a{(KI5VtNMud zC=r=aFdN;A=Qv+|^~>Jz+k>}1cE0+`K1N_6Q;8^_A?ZS)3i-e~^i9B=NvD%* zB2Xigqf046%oqiOgoQEM^@>tnKka$zfd*fDNI{QY!7z+7p)GJp6gh-*=7)G*&vg;X zFwkd13MvK7IsPm5UFluk(ERy2J`DteVKMvVx(k{N0V@H&$UQT8Ldfr$qZZ z*0uU!Ow}oxOEDzoYPboj2yYgPFtQ-hA^=u5UD#1cWE~O&s_ijY#)6o@YG^kZ_9j%! zUClpzk}J++p!qv@{~maM=bgU?4PCRe+*iX%?O~vQfHLwVastV3w``DT#OYYC)4H76 zUoFZebQ}l8wCEq!pB2Vq8QXjqUfb;Fl)+_4uh1!=EXS@VSri#9T~mcoKspYU*Q4Pb z+5;>1a@^=B%C@?fedcb9p6zL-#xT#;rvmxs0n&yfu?5pvJ$(F_Pt{$zo4#)rmiG0N z^0^OxOaB9(>F575o&MPq$sIz$Pe1>+TdnO%{-5^K?Wd3Ve;(xd__6sFT!zCA>OXs7 zM5Va-d??Z44`>IzYT_Dfe&y_6C))kea+IY;GScN-6Gud^aJ9PK;+r~Ns}wWHUJM74 zC5@~JHz3k1_F;D${|tvGS+5sfL&Gf^lT5*XRjj)CmGi>L)TR%~TSoAEMj~|!PLf1< zojbo!)jF0JvJW{1F69f7NzqD58J3uaqwR$>3KXlxyB2eq9xAQ3#Kg*orOO$vl*oi=c z!D-WPMBZ`ib|iM!E0SFud3hTenA#c7Yv{wg1i8}5I8<30mD?O^CMOF#9R~E;w4#FK zjjK5twOWzN1~v}uRuQLTmvi)k$M6xYGehC>#6)~&AUj3h}V z(yh9~7(b|4N|f4nQa7}Dz|hVKx5A08yF{cIXqJVQ+R;r1r0h0ZT(H&(mzGw|c$#^Cl?JLlfo4qg z7qk_rVv&yGv3BbB>td*7|I1qpwbH=-4RsQv(}w!F6%&uogFMsqf4S~fGw|Exgq&gj z^K^TwRmuO|Za;bYsQ-J2r^2SRtw(VvyC~hNRZf_m-s(ygI?6sL51Z%MHadlO9QIqg z2S@ona|5xyxp|`Q9dhY^)KKVzdj#LOL{yA_#u=$>*8rDhu?UAc*qg@(9k@|u!2f~P z0bQbPa5xppWZJ;Z++FiPBG>z;$UXLNRjH?Y>64o8Ow;+`#%e zpcmrTU&D9LVHY-(Pj$eIE5AQr(D;Rf!kC%>AJCrN(J;f zp}wHO^R?Dydvj}Zd(-p0&3JRogx48R5Ge`Kh!ri8(5ooy%Y%qdw0Tw0%Eym-`0HUj zeu&$E8~DJ&7#f=mee!`%pLA#Xlud&(eNFs)ZOy{xyLZr79V^p-=g*P_F5}>*LDc% z4!ncbCbTzU3*P5tl}%T>wm!&)jS)2-W@CC4g)z-b_EVaG%V6zY%l+p4Z(pxf+QBf2 zLo!mf5Csa9r8bEJHdMNWybf3DqEaJr9?`4^lOXj~fJQ@qH5e=r7h2H<4PcEo;j7hV zvkK9%J}c{yDAN(7Jgd+tx;OGH66oDYDl}RkFNL+=;x$;?T+82Wlqlnc^7)HKP~?%& zlhc73dMj1l*JNka$SHC9%2W0Ck|aXE8_jhpcgM=>XMV%MM%lg~-h@?UbY$uMG?OXY z1IYPOMLo-ZNNsgYS)=qW#fPecn`zc30>1qg_78u6?{G<|3bD2hGERjxCUD*d&SH(a zj}5J@X>{6~&?q2lbl&B&sg#=q1fA!K^^6T?qp+;3u>i5_j173KsxjcOj5m@oB$z>9 zqm52u3=fUXVoVJ=rD&(I*)VW5I`6E`KGa)^$Z8*d@L!zC7)!46tZS8O%K>Z_SLnrG z@9lb_u4mgRR%({8PFC4bMM5bVFa(aBgmj0Mr$dHl|XlTFFPY4DYQ(JpB@Zt%JQfO65~)!Z>{V2r*N# z>je1bADREThD3s3@_5QnRR(a6KpN4z22sS1EZ%N$X0i#Z#E&F@t}SZpxNy-EKk_n8 zQ}tKj99$k7HsHEN&S_W0RS_ZOFm^HZ+#a@ngV4P!q{NCZD|n&MWI!=bhV zJ(L_C9LyMm;1;0|5Pik3PoJz=nzAg5H<;$-h@@o5r|v8@)H}^=?@?{F_%pBm=fm*& zcFLdG`k$SxcE$c{tM%mR&ZGY4As*`(FvHfh-cr=Egk3-nH0hdgqCMzbikunxrRwc} z$e4o3K=)yI?N-Cmd!u30ft#H3M8UWk9VnE`UMsM&EIo;TQMDlyS{(ogOqbK1Xbi*XNal_#G`ftJ48{d8V z-8#a!VMW8#J=tN$R@-N*TW70Jbg62zFxBc=7<8bqy4_e7;HONGsDOS!V7KQ412t;} zEP3zV!swZ|$D*?nK?JY%_=07zxKoeH3r_cTtWG#TP~i_~TE(&V^~R?5^*IQCW)E7m zJc}MwdlTpZI^~=p8qA}V`IK{3fOZf>^oXWDjfH%tO6SF9!>Vh{0@f5uOv@dp;&lSZ z$I3@Sr!mRl*01>yO;4!5v1xoMW>4^(5bO5F;jEp1oMCISQI;*ka&s8dH|WiDOvD}L zUAcSC2Nq_Tr@^6-$B(kKPXtSUM`dV(O0h*rblX6?2$BlKU@wcHM@XJE_uABb{VsW& z%O#VWF`#Ojc{ZL2M7NyeW1+Ku;EeO%c57?5 za{k+HJ!wCl{~qF*Y^J1m$At48NfO@74|y+hGO0i1&A6_&GRHi~InM;~Ft;QfjOSa9 zR9=aIG_|9Od0f%ku7cJKU$RrPMd`08(mZDlm`2*kA0KF8_UC-8)%+s%M7$*M7QNjsNyk zzIv4Z9^jc_|H=I{OvFD4AJ}%F>*xUmtKO-m6z_Y&=@AhFXw=&bH#TrbSLs}NHq8B? zo{gE1cBM2`$d>aOtD*dbhanT@q)mxJypt%!{A+>pdqc2mTs? zyaub~s8pYSP57R%{_~TBO2KwJA#j%bH*WvY-fBI@|9Oz7uuq#qk+g`D;Vr5ljaAuA zk^ak5`3W(>LM|SblTt2&=j5d)yRv%IBLy!*N2zK}KG;vfAuo>kwLGt%vMfQU-1!ky zGNia2TxxE|IAsxU?{hDNzvwfa|LY~7BN}iw54gC0&_*zW|L<-+c~as3TaWVJ13eY_ zuP(r@_L=!|!mL^_jRicdt2oP<*9AUS|Q~l>FnZV z6XdaubD^;&IP<4ZuJL8eMTPr0(6Q(Skon|2(!>- zq;4@vY@}5iLPsU6YhBnH`8+2!9agGDw({V%PJf@)ZsWDZZ5tcPYG692v#<8sGp5pY5=l~M_P+70OSe3A*WGH_~K&nB}EtA!IZ zfH;=oAs+|F{#uAJT9~{=+mdBS{%UyL46nJW{&P-s=S0qHWlyZ^hM1SH*fwS`=0|IU zhkO<~|KF4T_vw?d_)o1TyN~Dphj`|HK68DMH#oVtWP~M4Mp7b3Zd7M)mnyH>;;xU= zRO2`(1ICBpb=gF)y6;&MQjLe+7{Eox&O{D=i|Z=Hr962rVs<(wGL}qOHaeGM{X0fG zHI|)oOxske$%>3NN1YinI@9F%$%1?5eEi=eG`pAhf9ulNg^Zd9P0Wx8=s=4t@NCWw2E+G56z zFnIrRNuo)TtRxmwrlcpEP*%B~orXiovV2P_y`0ulP3^I33Y^lDajdB@Be~bulo0Et zJ#|L9&m+=(ua(J8mUAtdB`u3DVpYuRwpqI@Y7U9B7KU~yoO5Ytr@>nd2lg%vFc3sf|!A&qnQEqiwu z|E_veIjK)XN(AlK`ZSkIjF~zVe-RWue$2({?Qv_rsXlFbFJ!3=@SdehS)b#0GIuNl z>-v3x$P0E?Uqsx6{I;Mz>cY5N&)y`9pb2H>aT8ZS&-6x|va+5*CH`v}=-0U|jw!RU z0xvQy*icfv?z(<3uPfF$V_jK;P;0x=KlcYT$Re5+7NANBvtF0I1gkX~e^aJJul7A3 zb9_{~82eTtgZiq|!V0KVkuM^`dAX;tPu1ud<29!Z>NcSpZWF0tvA_%q$6>f#*oFC` zEU31|Jyxe5mP@ycU|EM1AyU;dJp>A#(WeJM$%|AyTa90<dL0UAHQ;pr_|1P>e~CXZ3}{55^Pdo~()$nOl9XP!zy8|!`lmQz{>A=vLeo(0 zXe9di&0(SlXuL3|FMdo}mb{@umX6TH`UjT&laget@EdY{y!YmZl=v71tz9X=2kh#E z3K7PA-nkr>zkVQT8T8@xS%KBFfe?ub1mjgp&duLTwVG7;%{)+Te{)jETKGp9(IgbG z`X6v}qqCE2mnR}%Rsxwy@R&J6$5gMe)uV{jy2dsi=8Y*Mo1GX@#xz#%Ky6>jkNJ9A zUejfiPO@N|>^5ZyXC+h3yE&BO%cCrcj9@p`O7XE&F$ynfOgTSFS(n=G{E*1G!9P{{q2J*oOHfx5yt1$g1;evtsS?O#0+I|oBoPL? zJfS{|1Kxq{md#|8vLpYvQ|YU0Jvp&W3%ez7yqeOME_u6EYP!PB%PmR9#RAhtsb1;R z6kw(_F-Ea<$eGx1xu7+`o%zSdGvBA2pAJ&W2P~?9QvEswf0*eF)edwjU_BMkVnTb> zuTub<-q1wADlb#anN_z$g`b=-|ALD0lC)Hi3y^WSn_4=alLjj-O(DW5^j2x4LU8kw zN~N9Yv@&&WD=kf%+*x~KC5D@2STyOBQ}1zS7oOXZu3V|?r)Gbf(_gK2fFD2J(pAky zNbb^LyhWz3f0GHX7Lb;wOE1$zmJh@01>hh-fN^d~Bs-aK(5@Dm>x2rkWTlzH5sfc9 zcfp^+$G_hEdUkYpd}<-;Qnpc+ODv2wrj5w7dUW{m?BJ-{<_{?wmKk6#q)~88dsRsV z-zzDO3nDU909Xy${k+|Kv%i#{j0Ot@zlmL)9PPche~13cSrtvEul9a;wRiT<{k@~J z!=uxa@kz4g2)U9x2br@|&Ah1g4uAOJWdHQ!?DX*H;KkYT{?Y4$7kj4%hi}h**n4q$ zcwE~}ML4$7gLx`-oYWqlj4F3&NC71OD~lk7q9rj_W73@YZeq-O|V>H08*W zed^_>okFf*{gbSVujU__-aszsXcJZu0DEhiFuFx04r zy%7+jT!5=V=nt%xF+UxL43jWpMmS#}z_QDOmzc0Qq3trb0j#}yFMs%%z+bQiZVwu(>+7qPF~Qf+-q>ht)Qx4*QGN0Ne`}mi z$I!X=^5^~I(}R=!muIgJPEPmV?jN5#KYG1)@b+~7*V9h3sjz%-q~dEUBd&`u4h}dp zxR6xcn0^6{iFVD>-o&*%v$)QMScio%E!1s>BZ&qh3Cwp#>=m_89R1|+OW)Hr+#be> z{6er(r4f2feWjR{Qbm-}STqCXe_znFnP%~vieD)-2Vq*T^|iIwwUW{VCVZ=nd#+8} zDp%d9$GV??XP-zZt0rv2#C^Ant-3KZYj1j`BgbxKKa7)K*3m)s0us9!-3HOBcs zzqJff39nnD9kdv>apfNr8eYmjg&o}fB*39*o?&0 zYwvk0*0b4#Q%+8tsag2r)X(tJoW+7(3;SBX^r23RQ>|O48>;PJ@q>vx$beWw(2J~} zv4x{LvS(YpDQp(XHD)7I&~zBaD2e`<5}zJT)J9J&sT!#;;Sh&is@TLC<^^^K3}Zl9UCRPiNQ$&+z}+YCWySe}C9*?L5YRe2}MV zz$GVqU$e$)W`;{E+V_N<3}f-YWP>UrysK!()wJVk+G*7J@HPfC8hU)t958m#=m55& zZ}@UU+_DXowT;ySq5H-5wL!-G_81OSRRpeqKe*o9bb1!megX>y)cvtD+@pag9Ve=Xf64%Ce$(z-4Zw9Jcy5>W z!#ED(zO1jV*xezAtyZfAt~(^xt{#mfiyJ^|oni`{v1}l3=_O4qJI2q=w;rG6JazlO zCrK^r$1J5w>0`$J-`c6h|7h=QwIAdEKghEJM=Dt`s^Qh7!_|Ps(9OaqkPIXt{)J=| zo}+IX^t}NGe?Fj51pSD0l^;wP_ctM>5fR}fg+v}Ri}xfBoE3;^U)?gSCm0cx25=RM z0sO~?2ZwPq0v6Le$+6rSqcEnPh)xU2@@k<%wb9eD!~v75JH? zA;EAhk|Zg+L>bzJE{#8geHH~>n)aQPUWW9l_S5Hv`te!TQ@8$)_xE1D+4qLQlKPms z{@Ytye>>aT)%CyqjfH?NZaMBk zk7aQH1Deu~`vD7FW!H)>%l*^Wme$Gp){2tq(m&S ze}|PQ9EMtVgS{c~6eB?*Lhb@rVH822j{iLtMeHh!`#8}DhP;44Fp9}A^hp$rfcqqp zbF2>6Cg>PsTuz-u9o7i>TB(W8CCVmvLt=3Se-2_UNEFF|%D&8!WDu$k_}1W64De^F)Ly6v zgY)4?t*&zjWBjZ=Ih?{--f_;)&z)68#wkr0+IEMrpLK~~DUwL1+0+Zl>oeAmC`m$I zF|Kjd_TlC=O`8T-7vn#J0jcOh?KlXAbI{W>OCX>@=o3MM!n1!pr9K-D<>Cr9f8=;# z7?KMLJWG|Ht+0yKS}ci|dmWWc`pt}W);(tpO_)o&G-UfIu~(XM z@sp%mo9SrvnsT->v>C<_3J&KM2SNsP4&9L<3lC0nitb45>hA(Yb*m^AqNJe{SFF+6*bMsiG@lKx^fCtymD{=!4K z=jR5_cf6JqzwXjVj;X?DcD@G3F_mOdq;uik1*GLaH*kK?d&|U8N;!?if9(*4m@*Dw zx1uR!r^|2E`vW~UaK3#fdIEzfpA8chFNke}sf%Yk4=Mx~^a~>=d~V=;alSOxh@vTN z`f?jgnaa7f44(b)1oEX2+6|n4{B&2MLQGXm!4~6F#oc&rb%yWg^_}FFWj@QpRMnGh zu0{=})QvLy>*qHxpdO~m?K9r(Kx&;=&&ZUy#Yj~e1)8`gLuxccl+PV=^R188x=dbSQB zZwc0wenAXK=|H}5Cunc^+`xGpMt59(nom`uQO(h6J*G-6c>q6gf7;6;bsZT~ino!1 zDbWkXIIe6l)Ik-G_Z*|>(j~6nTxd{d)$v_GSBo?7-z3Z_Q5*BbET&}?|rU8U; z=N2d=oQM6`#RO+8f1u431Gr1#z_t4wL6XuQO(oOhOWgac=xOt}cLT3OE+kD!sONv6p>=g3?LGdf>4rs9gF z7h&8t@0u&7NjKLFe@09d2|#AIdjqFf8bhQ=F5RV&aZ6o3e~v73!4xwAq|mRU@9TX^ z3(MnErQ|E~O9@AFqv$g)z#BOKruEI%66C3Bn439e^n)IprWxHtFG)^$NocR!rL5B|rAmmMu(?Ab=u}E7F!poeeMFo*3G?3!Ha)?hjKY zP4^uzWt#j_e=sG|>~4svV9LsQJjdR5`r=5jbHie207Mi>vTiS9iq?7w3pONYOKC(# zN;uEgr@orXYn$ld*u~xW{SBf-ehG807%1CW3$l7|THA8&*LS zBoauPhL=<#QVCkAD0YDySyFDjCroX(Ti>*nh$)q~e|}#5)Jv7+K8!W0BtneCxaRI- zcI~WJ`yyPK&t7TZmlo%xo=n{0!7dRay_bLwE`MH5}L$|l*RTY}j$VQI{`pX~yc2Q}-~ckC>8o}aFqLZjS>p48418NF3S(n?_x^k$Y<3R(Iw%!v2S>^~o`V<=fsu+Z_K*rxq+GTUpbEINP?p11 z&YUpkRNUC2r#cHNkwGwHJH~=9;~HXl6qcuDe^~7HPp5MD3t{PH$kPnfvEOGwpU$O! zQQA)+P*et0yJE6<^o5{oiJZ#{&Ng?0iaQk1SYbcD3{w`P;popK4JAwBx%Fg-Spi5o zlGq%QN%yF7P=$S|#|h7M9vx`>Ab0-;%o?>eDyWO2IIAX{t4ThGcclh^tJDSW-znvZ zfBMMZ1Iwe@EjZGbO1%BsW>wi<|!2mUjE&{5#bz2JOUFm zMekr2IeYP7U&@g>)Kh+|Nw_?5Lgy>IQrg1Bo~?GrT_48^Hjlda-8ro9MUf6c=o3yi%oO~HNr38k;9)?0GR@$0dHiN}NcEa-Zs4ub0kEDV1&mVDr9GD7aJv*jf39P> zi-)-_pZwgw`F86*FjWc}Ke2-w_$Rvp))O>_^)NQrLd{Y_Q^}Q#1lj$};`a@lx9aF{gfp zKFYc-wJtstwvao^lJbN?15;2ke=vpL0l5t0J|-4e_G*DC27?~65G6_F z24;c*A7laob`?WkcG1hCXtY^cuAz{dz(p9AHR_G%;g99PY|InO!nL=b%ezy(Xx0XK}%r6!7K z1T2Na6uHu|Tx)pw;&w+ai>bE(@Pfp6qwWtdB%mTMmBLj!h%F1zH*jtPatFJ_n!tSX zVCp9>kGn@dztDn#Yy)HSf87d_dwiC6lUI%ZH|IV2N%#lakRftZw8J~Wa&M2^`+f|U zVr^G%r8=v73?i&fU|%-AZs5Gh5}|$eC7`#!+Bi*FF&Jmuqo3G*))HA=;$KLaNG`}P z=~^6G6qg+3= zL<2K1(A<6VrVau+`BwI`7$7pKwQ=8kK31bOl2-Y_+vm~aEXe+(%42eA|xPO-$~ax#2M zFpKxx5!l;4%d=vs%R4@Y6-$Y~_!Z~%FX_2~bIu~pS=_8<=XO5~uU)DK@n3vEwsv+W z=;J0~YNosY3`~7VZvHoLPPVpNyIb4e?E1kD>9vU;^q%hSw6}J*xBRUZCA(evbobe_ zouKD$wfx|le_roNtJmGye)eQ{XXhz>c3zLESkUxxmigWd9O!b&NW@xWYUAgnRfu<3 zDkh3m@-UGiD!XUag*Og&baAZ3lp5%%x_6i&qw5lXT|~_%gH{&bQ6YU+uRO!pMvlI7 z$4F)pqY`)b{3yY50{fY}kqV|%XzO`4zq+l@H&Mqqe@%!y_~o|2_b67*e(q)KQ<$1( z^D|ECe}c{L2F?Q#hcx<~f6$wsnY#rX6(?hA#^yH@Q(w;JcLV2_ZEwrF)Ah^*OwFt`*JtG8#w=FyY=iow!V2z$a9^K%iI%n z4WHq;f0y&I!qhzO{(njD{xM9={L^zOGX#WaJREqmUeBPfKuBEDrEr``lK+co(Z zKg)A3f@h8??aA_n#i9I}$0t26x__I;EVW(t>}a$nrABg2=H&r11;dc3mJ-C&U#98D z4V(`Z2w}WfI7GXVIWeUzD&7_dyMYrbieR+be;GE9%Zo1ylql2Y&-6SfP4O`$`^_AL z0qZYVN)J!Kj{ds|-An)XA>)i>XIGH8)LVQRQrt zfBT?m(_zyqmh|9+Qe(1#mK`eK1{o&NGUK()_bg9FR}%8SSl8YrXsJf z+fS)-Sl7NF#^_wqNS;)%7xv);>&i2YvN*pZ& z_>UqkiHByd%7JQx3~UpM%$J*BrM>(Cf3sf7SL%FsWEE5G*_4X51#^Rnrw8{HyP$heGOsQOdVb8F8DO*(-{)0tf zUYL zDW+H?N%*tM*Vuu}wsR52K?jal;0&oC0THC*0G<~+ctrA}kj6s4Qc3c3Qa6cyS+>Zm zA%D5Lt|}vG1Go1Ml*~Rme=V=w_F7&GOZaR!ByrGj6fwEL2l=0_pa0pQ*VG3=!br|X zGoY8vIE$i&LjPgxQ#8jPkV|#aE5KK)ugfl}!R#g$mFSnJQjl}5Al7GUlPWJyY3If| z{s{u%iqLM}b-`k9ch$@1Jhhhsgotyr77hJi&2q>Ia5{QJOm>`7kKDW8k^XI zp5wUi?!+I^A#qdMe-F7xNAK5bQlu7pg~`b)Qc9acA{RijSc$yc)&{r$dyogy0iB{; zu@21(IGWR?7`GrIBbwr%5XWXUcyLfEe!PEj+RGvpLAXcs1p`+~YupT#_6pnFyG$7< zz_^;Ii41~eVvIvbj*`beHL-k4Q7|3n7;{o%$0lKBG|EF5f1%BM;cKULi-1{gaK}w! zlW#r`;-}-kg>l)N#M4WxhSf6fk7;fSgzFnuB67g<}0QXUz^A^a?cy@GD z@_{ue)gE)2FdIOq@Uy9iGS6JpPg$1ed@_>BMyP21bHm4@N-nZIHl*?np!CM8PVf1=j8s@iJm#@lN--E#Wc5D7_) zDME*&Y^$m7-~L<}ECeY^vYZ@uxy=uWMFNAt0GJtI1~UntHtU7Ze=`EliaTQ%J*6iSz%EikiX*j?b0}&TT7YT;x}?K8pFvsVZY_CVVR3WERUn_GI0$EY1pM z1PCXoXt+25RA6)r`<;w2Hkx>&V}J)Jk1+~(%5F*QTO197<`RLVXhM#n9Md65y#X?U ze>4FuV!80Rp+8eH6iW$BZkA-ym{-C}$0#ENNjOhN>23s%ESZwf5+ULG z!3~YFI5z?Yu!zf?h+5cnq$){)U|5`u+>Km;Cnw_#jVD>7iaaD?oHI@!Aw8PcW>J8m zt|Nq)5>CS~KS>qkG2p5q2M+2>2*vI-f2J7+5usOn1rArd2G+D}03EClZLZc!<4qcm zDc4U|L~C-VK?C&N*Rzww7)ezHQsosyI2DZ@f-vF_H?42J0o&cFZMd88QvUzVH&7k{ z5f$f^74a{0B)Ka(in#CSxZxGdM-OBq0?(!lDmqC-vszvKcy@Yld3bi;d{ui@f8VHV z0F5x8G?!g6NDZXrG_7j+#}WOgp)d z`LHZGHE)b*Xvz{rWl|oH=d0gimi_ai7hxM&_!-8!Gehn2Pb5hbWX2 z!(LrmD?_T#1&sXvsfglae+;I^f1j%#k4{?$m*2LIS||G)*|Ym55?3PkE)u>0C;|M; zQm#P2kyblo+ITsQ6cUcoh#XbG8In4peAu|E|QWVTo4uKcN*a z7`Xh&t(qCTGA?aGMttb1g5`_Q4|+XRDmezY&u6|=OYnPlg8Gi!E~c{;6Ll7*Pl;4B zI_Q5Qr8!rxGrednn5T!Ye>*T+Pe%r^F$hq}G!NHQLxW;#!?i+DZI>qxI%a7tymun2PC6dV(P45EF1J_L10pq%zmk$;e@`hnFV&l(GE`7~ zgh)}j1iCaOPTpX^w?7=>Ii8b3-<$<4$kpW^YDLYIc@))CzEQqH`R+w z4{R@eIttl@#ucebH#S_CtJ3)Y0#*7Es@b2RMLtrcUP_C6e}qCk6TUI9IUV(%t_H47 zTM_y+h2+MwPFlz988)?xU(U==`$z9O7wvQV@J#$x4e1wWr{@=Iof%g9)|<8^S}uL0 zy<35DYfj^pQ{Qx*y1N4aUq>vWuZ{A2@TwR4XQjaE!^W#bv}V_AJ;AdlH?_0Y>dxjC z|J>Me>b06ve{bwM?#?DJz?*9}YHn?Jx>2(=cZt?q=T%L$wz=tSZaVIE9gNntc1yZw zwpL%FwT9zv*RsC3o3EVRTCTOmtNE?zRG4$d?Sw@+SE0v`+wW!0STknHD`%%x+FIS+ zb(d?+?B%k3Q>|?@c6V`}Y}Dl+x;2YSI4v>d>2gy|e^rw|Wu*$U@#5kLwrdIX)RdcD zE1OltO7*!-$;pvbvQ}Y~IX~y{w9XDs-jscSwLIeMP1xEN(L1{nv@Vrwn(`n7k6wCj zS_b4Hd(%8sw1Ak}o90PgESK#~%kWsVH!UXs?oIpG^nxa7wr{M99RcnT<;Nvp?mrl& zaxVDqfA1lLn)kekc=RTsNXq5)W0<8Mmnp*e6~2+OgUDqJ&kg#TYFZHpFlaz>n{0;w!EF9to{NuFgO0137QIEZax-E)C?H8X6I;q z<-L5g!dDJ%XmCrL7fYn7d{D6NOtAUjsphF5!LEr7(^%UjmXUM6U3)f%lbT ze_4g3eunS42(U9E-hetR?pJ;0p$D>XIA6)t!@)2vC3jMiO2<6o?M+e^VZgFFDT#a% z`?+h)NzSh)yYMatQkof*x0!U$6rN|8BTr8N5!4*-SeL~&u!L_WSG1-3qM6yTH;zP3 z!!W>`nb#D^IAueU1|E8cpt4^G)Wt`sux> zLdw8MT(wWAP;cJr>*FJqa1xb!hk7igEU6k%WYy*DoSW!?hQoCv?U3;a8Huo3oZc+9 zVoH3SPS0$-8?ru(Sdu~zrDztM(}N5@ffVA$Aym_WdF5PV=nT?fxGrXs85vWUe-DS` z14Caw){|~B37?`#s)Dm?e|8mZ9B*P6&Qtt7_QpWgY^Ri<8J^V-!@ElrV@vr(jLmG^a@YBcWiBVfwU`gZIFXx65>4O-azlWO^rd76zLmaT())4)f>haN zlgvSi^-Q{8L)MT?(8Slp%4#M$e{jKIK*JHAkTJ~$lh3*ggs_|hF*;b^kSIkTXco~Z zP2iRa7ONQ|cw>=-K?j$ItMWMW%6!NvsHo~#xVp2t9CJ*9=?h~nMhzOW8)G!(J%^%e z)CGEPvf+@SlMct3q(L{p^{uz?#!Qgm<~B^%R+&nWZvdr*E(8k{e=F~O{$(j9h4`Y`>J>|sPl^z8Z;^PS;|=OCdXhfo5*N;e zh1i9eH+&{fj0MCZSz(_=$#_U(c!S=`j`gp?f1fee-~Ltm>Uq2Ft46o$)9p>KTW^qFquzbByItF&+dK8mT77q$b~ouxcXyLEwq4)S z>3^C~sn_atyS8h)f4dj%R%ZREnp*$c&+Gq5p2h3Gu(BV@5IhzMajASbry{j%OueymTQ#R%bGH9%;`}do%GQ5z zG^-dO;qjbc!TNV=JB9UMuhn;+*Z-3|h5ONDG}1pC&X%)ffB7`=;$S3J@;;5yn1sRa z)X%xx4oQG6wtNqhzmsj8J#5HJh#MA2rJ5J_gCvcE?wIc?lS$+aVipCz%L;sJl@-Nn zfU4cZu?CFuDzz~%VmxH0T~^?+Xh9jY4jR+_G8vLEboxQh;(HxUkN@9w+?rFfYnJLR z9_rdaha?D_e-ILH5R%BDH#ACE^vy8vVwSL8DqZ^oYvrW^y4%R=v0EA?*WdIR>uWba zd8?e=HloBT@?;ski8o!cD-u03cyVbda~H=VJ_5NDB*Qt=Wa|Z4@Mlln=jW532l0R7 zV)SF$n9u*~_3cKj$p5z*^=JP76wk^Ev=X?bL4UwGfAD|CEFKS&EbFB>ey9G1%uOE* z@i;fQk+%;>1kCr1<4JWL*IHRYTgreXG;-jW#gyI9Slr84c6Pj8QlYKUh(k3(jR^WgpL5x%(Q7R9oe`V~gu!f2t`XZi$ zK%RuRe`JEDn>BZ%u_19^x2zHK4}v5fqZs{m?Dr|UrVPp5yNKM7Amr+A6I|ZtkqD?a zV7D+N(FA@~)>pi&DvwOZSvBy*Me7?Dj>S=zubWC7%SvfH3?dY$Z(`!nGa3iX?@+Fw zCrxm7Yc=d^Cb1zAAA#f=$2<4pTv39g(B}|)f8#KmIF=rI3wej1 zEjf%ylmGD5 zpE^3WN6xa6bGR_wrDRp@OJ(~};*fAOD7wiFSdtdhEoaIz4OZrP+L7BN?^Fcq$Ee2+Gjk0(TNKPF@C{7(& zF(q-IrowYiel`GzuL8~t0Lp8s3Fw1SXk2}ruFY5V_sS1M&x4f}OF4%$r{@@Fw~_#6 z_j7>T!9ETI=a{V9y(o1O!UuMJi zr6YlIU8jkEgEtyC!L8MX;-?t4Cb;$8<3M&$#2tP}d2190=uog9k|a4nf0kaUSe6;p zP)(vNc%d!L&GG~s2b7aOn=q*KS(cGhsR{3Y(@-!$l8#4}6sEPTtU@@d_V<-Y?oOod z0N1v12n8apmq#(amOn<{k}}{N-iZ0FC=FVw89^W2se^GGME#D2bJ&lVe%-#K-dJ&_ z73AGEsQ6TG>7)sja*0{df8{}-uBmK%01@Hk2m?jtKY$AHW}b9d*p*M$APm}Mc?|+q z6?yv%(OE1oBI%$Bm8!HJmli>`Sb61cx)nrN_DAL!Y{EU6>x}dcC0;K}DyN3G;tLSy-c zi`I7uSA91i=;c!TbA^gTKx9BlDil!1G9nk+fDr&Onj{7;yrZ6YEzbXJacY=U<8fp( zmF3#vTN`hjq-+ReoDNtV{LYonl@&N6iHN?FP%oyb_GGx$A$uHX;|*f`o`uUINus>= zI2?mn>S)}2iAmNie~kSShKJovm~clKM=G2cha-axc)%wEtOb$w1Q4OkrdwI(-BioX zF$05hcp8_?TSS{Tfh0}1`dVPcxz_7T`Qt5}yuO09Rv5x~grbWiblqA}RBBo)IvVA0 zf-VUi{9^Gnc1dJc{-7{IF;JkU;-oV|||Ao^LjXEju zuGcLYTxlhTMw63)>)IFr?3np{Ur&ku6PI15k;lg-34W(d*m@JlAf3X2CV~HqX*>Zk z8VPx*JaJGrBN(!kB{X}j4>o!%E;yuewFOzWV<|tLf23(y03U{TLR&4|$pU@*$5KKj z+rlS_N4Nk)*CfMZQGwQ*pMDc+PD6%ooxRgm>T)mWtL8#>OJlxLPsizKoQj!~p@!oy z>r88gZ({EBLT-aEr30^F;K1m&vd`mC-dobkG3A`S2^ALil_^9YY{{+J*L;!J_ReMI z6zX?Se-*zB{I!4o+kd(K`s>$g|Ni&w%k_$t$#B6KGSPIoTCaTWhS82h-8~gmb18Hh z=I2oBA*L3BP!IX0{K=3PZ|i%a2M_GZ=3~2pdKl$9w+lSpshCuG!hVWhjU{`1xw`dV z=s0@uAJC9Y^vP(WCOOIto+=2y#{N_6XoW13f6Pm~%8p-{VlgS z#?&f8QA!wJTuDCS2mhoF<#Sv%HCiH{I>$w!pUW?v|1hA+jLjuk ze-_nxvS{Q+vjcG{a!A|~K(%fxW(Vr@} z|LO)&_4jL``#Q;B<*2c30f*jtckz7_Dmc+A;?==nr}gboyYTkx^!%a;Ztc}8@w?MG z%1T&z2BV#f>5RrQ(ny9TE zL%#bKiZWiM3Hde_=_aK+gp~M7!BEyC)!gC9;l<_A;ql=`6Sge*^5Ss+?d8Scak~k1 zJbl!N49Q(E91jhIJQjK5IObiEJ(5Xd^vz#Un}qAW$aDFIDe*}0gK&6bdu_q_G);`Hp@`J477{(E?G(LVpNb<~8~ zRDsS(>+HOJ(KR-s{p%y z_B?-n%AY0q|K)&&BQB}a(ZgNA7x4f3c740R|Lfa3&;H*}@_hVQeE~PYu!%;6QdawJq$1R-{CqmVfMdyWwhG#2@E5^b?Cd-4s2y>u4DFkW z-|&(u(jNQ)X*`ZR*lysT!LT##^@2O7*cmVy`S`cMa38fcAi53oOosoANhktLkkJTn zcdVbNfR5!;-UjbSqEj#>Y?#Z#ggx#BH1vhV9zB=Df1U_^z1C!;@q?3&yyk>P+jUHZ zCOumO5P8t2f>4N95SbUlSots@@)FxQ#LbwuqDj!x^i&@;O;wqh1s+87Fv%dTVh5Ug z4JHtX-)3^az8FBy4liVrIe zubeBQf0T&$F>i>mTxDj5OnQsI<&`z~11(|1qfmKSfy!m&5o_wAu-r56i@}s%?wmfT z!lm8FKCT6k7moc|>dE?N?SR}^!Uxt}Kz}S(in{*xSuuY){=cOBm(E=uhIiWhviMrq zqWxdJ?iS?#mfLWj<^NMW1%aWxYMc+lJ9RA|f9~(g^C<7*Y&a&Pl5EOli73r!lh3hS zEG=vSyy3ispNvM$S=*Rw3nLD6_$;1TSn^a|T05VX;Ee?aG!9Y>BW#vX*BI{aE!BOo zM4ZVmRMCJj>!S>bT;FkOPR()6T^5aQ9C;F~S}XM%%H{4f$4NF>Qb~7DxBxAJfWN;XX#Ovpdmnlscs~DkU3ZHA zH+FWO`TvtVwrxL(w=X_N`o}SNWjMg#h)sF2rP)3JyKcfC%B}PNplMKN_NU%}K|E&H z@CQU*CI(>jquaFW?yh@pX%~per@{I1e@n^oqbWG(uCH7QXbQHnWt^X$UhJ)|A$V!= zG$Jv-SXY&PeS7(?(>~u@{V4xx+QvPE`>fQ_>6^==_K)o&QB?nI+LgnT?@rC42d#_N z<-wr@s(v-?N|n=gsw4-MFD>7w8r6{X&071XC*Nf?)GuC|bF*AYVIbs!SQgG+e<4{N1_o`_(@)fi(PZZ8+Dh(KQGbxDXCO^-LxA< zEnzoRHdRq2|E_6k0vsM(es_4(-m9z{^SnBo1b!tyTMyqD1=WP*^4RfueO318^v&iE zmuKgvXYKQg!*-`oI%NIL56&oNBO0dxO;ok-Pdj|2t7}{9^7X;#aqIA8fA31Y2iw0= zNS&VUfA?ljHlKTmtIB9qe*EFb-c%HCX`P+@&^lLK zXqB_1>WdE~Rw(WtwK|=%*2VXGmDP{@>wPs0y44rW)sLd;eKWVh@0%KQ(1Z8yVU_nC z_WlRfoEPi%+WVUQ>bIBcf2;7@Z(m`mL2cIsIBDU^`?l5j{_?zi+`8ER{=NI#y;0)y z{GeE%o-1(t%jMz0-s;-g>G{D+cU|>@X1@QLp;x{mK`0Ir>0KHVF9jC+0YXLMf0p^VB1}PiPb0=Z zh11o57U+Jh<`v3{j^DrRhUVZ|Z2+ef44otoGqO_t~W-Nxa zH3;_HuORpuTRm@|9UbnsI$uHX@+IiC?z5?tF72VK!QLw8G4|@(T41~D>&~j+z|GZy zCb3#n73TEGXAxCc#zU}suv(N7`DL7`%Q0iA6S97viWR06f3^+SKRxN39?1nW1+N^O z$D)LV)Jc^uY#!)RutL`VY;gRU3}gu)y&$A}xmQbr7?L{a#3_!K-6Gw|Q5A4{j^{eyv#5ymx*9XWgV zjJr&t#J-rKe=S+?(U2YO@0~|`Y6(0Zc_$c7@ znmNT*KsnkK6fx9FjeckJm7G*WEOY)p{O^p?_?QGyf4Q!OuqyU}$A(Taj+`N;^%&A9 zbv_L5Vmf3gh54V+`DJfaoz^ZWOvb!7DPEAnO09Pn-!H0^Bq5gVbdJD2$ol(Xcqd0) z-Z#clNMu-=K$R(#5sw4z6yffu2Or1{0oxlvWmT)*6WmtxyAUujpz8^PxKV*e;-&kQTA@r{D+<%{4F&1#F;9-`M|pP zqNH}wXxJsGH>m347-KQZ7|lE5&1KR|Ua_Zp^szor6UWr&o;#xm@-Wi>0NjaGFg3xcgWqC>-E ze@Yk_33!c0L`WecN@m{kSbR=NAKNZK0g4fI?m9Cr_DeSf7||XH!eTi(_Rs^}hE?>8Hu&K@_vr$d*SX&SNl57k+^w$)?}ber#0kOPLPMP3 z*@CpIvgNNNx-p6RR4pj9&5K(vMdak>;mf_f*MCTDxWE5$3N2ZJm(f0O*mlqZpV0*4 z=h3?$Nu8<6zhCc%fs+itmiM_H)h*1UEAfN+>;LP*>#9$0s?j(M;ScD?bOc{|zupD@ zmxcEK_~Ub-APK0js8r_Ci4#2@DncP~lD`0T=Hfh!>l9xAc?rt+X-v0kI@y3#5fQDq zCx50Njc%q_gw_an75GC&y)s-H9pTbAHCqw3s41HH1O78+#d%$dc6K?-;#3x1FpAsC zIFH&X#;wDj=C=3Qo+b7F5%aU?(p?N>{ptRHwR*ki|JQJLp7sBycuW;t=&scpw@Xu^ z6!o*|(B;n_A#*)Kl08)2L)jOzU>j!?rI>ayoWEjv3QV3NRC-+##k zr^-s1X#u7|=%7^^jE?i4?^ID#E-@44j_selw*C&!yGEghs!9f=5F7WwkaG1*a3Z4a5^#8h zk^y}1Id91{UiO$*Pq5B(qd3b{oA!(em{%oj+qSH^EMQt=`AahbGrYhvNBDG(FjL)p z1Ya;j#V4$#Ff$EmfydgEzkk~7bJl#))U3vo zKm2s3j3UeRthB+$b+6D<(-k_U@i2(6nKv=<=vh8_i?M>{mMlBdH}g^Cb-yf4Mb3e_ zSfj*4dMYdH^nxiA$yUs6*}y)fOZSvdca~cN^z-LCc28MZq@OpGI)8JhVlF#Vj-x^a zez3P_sRQYwtnfubmw#JZ5JcvDS~L)4U`lgCiHF{0YpF<@!fTFB!6{z6EGGD6*SSKa z*_P_HT$w`7rAGd1G4s+^?(1~=rF!oBtRzh0DbFzV-y;r&bNU<6r{K4p%}FE3!Ay7S zQtf5ta+)tv{xUA%2C6bwbk1lhGE^5D%y$pn+(k*}noi~|`+qfzEUQan%opAv4rX0< zxV(Gc#cVh|Pj&L>bK0B!YH0JhHm6zuk>9!G#AgBN9_C`7&72ut_C{YZ$MZ3tTS@s; zHuvQoq*>nqKw0rb?Se*)?x^Se7t{P=c^r|z)kn@IaRPMM1s@Y`*+#xHAP{r+xn8;eH!K@GO*o zCG>SLac#i?W2P9&X|%;zJS7?Dh|?z*qMuC`6vyu~NPmWs=;d>m3mc{R#OqmP=UIHy zC}DJXX@8(R)-yBS!XgfpfqyWcDpq+cw<^M!#j{AgrrznM$Xq+ z9!?sz%o=NtNIKYC1ieI_+mHoD-3ysV!U0QC6VKdj-$m$ztZ#?(hK7r9(J{V7qT&)z zn6B7nk$)}hSPul4OLF|_@??g=usElmQmPnnvp&CUF~U?mPQpB3D^$uZ{vz9>2eNRJ zObag3-pfeHQ(`b;zBXfKNSJS%<-WALs&t_TQ!S8Qk8jTn7Ny>ND&&-fG%g;G8ZVcG zuY_GmbGe^SLoF$;y{;CxZ=Tuv8x8!qiDw ztbc8MB@HFP?cTM^<2d#eF2U4B=W5l zh-e=*9#|X2v=`h_UnoldXWfC*D4YO`uqH(A@|NuCu5B5&L!DT}iKw=9(NTAQ+B93|cJ&xlF5?rF?&Hc#i(IHb~P^vs%5@sg#|7Rek1RE;)~m(xghd2_`)W zScGrWn1vyYbKb^Dbsc(a9QhDLW?trfUOec+`(Jfh*Qw@{X0_T6(!m%bhkqKVh{l3$ zHH|5)4oQ;IINzuy$+}}%D=TmoQ@*PVk|5>Ef{BUixV4wo3pi7It@KCSFDrEkScHTc zM?Q_i3D>NGs27tYjmKU(j;ZwmTHsG2G7LOH@FZXnjWOW_uUUkgV2vIb24O(rAe{(R z>U|3%fg2;zaJHPgj278BkAI9A&f-2Fl4{HgZdKQ@tC&id>qx?1G7)n^Ob1L>?K8xx z$HI`^22sChSua4R;1;?aZl*ItXq3=lH>5C5c>l?NZLT;bD(_2Tm}S%`X)PtmwaRrk zj;TFjzO4`P3-7aVc(#a#S6zpM0f*|PA^0%~B0;a1CL`*lNHh_RU4M{AE+9plJnG-8 zB+JqZc`n|XO41WKu_Kz^viLfP`s!VEoh$m1a@erXh8)MKbzF*&a)#DK@FlniyC$Xt zxgkMF_&8_KFwAn~QMC*V0K!y)<_6x@DN>V!;RMKN#Q((+gjSRWc1s?}K8y~k5#&@= zk|aSt!cmKuPpd0=hCk;KTJ-yz^6u`5%Y_HJvBNXSQl;wfj5w^5~H4q%e_XO zz5`c6*1K2hvtXU+yJm%}dB~3_@dlty2k8{v$UzQ8WNp6EIe)Q`p;*7LF?d{xh>u1h zbYx!qXD(g-mF8AgKoaQEkli|#b#-+m6Kq(kLQutY#DMsICeT3g6M5q0^cm}il#GI; zAO~!r@z}bif~k#zqCHs-uBVksVZ20cE5dHoFm6q$kW=a(Ol zq7ybo8Kdb&eSeetD!Ppm3RjA|^l5sfg>sxaiAe+fA=Iuw2xoJ&XX@Sl(Yww?``kV}!=LS+FWToPtt0$>c6xrX)>$uWD37gyffzE+63yKePj|7@eNySs~j)a4)834;#$F(A7&=hd#`?!0o`?X4M* z=aWYaqj`tOpwmISb9ml90N8(be%?O0m>Qdg0@x@*o^^@1TDU%3HYz2AE$hLwzlHG; z7rE=eRk@mY1wn!<(CE2jsXlZk8Mku~$cSZC?SHRpf4G0tUta-r^4lm@vfSC=z;BCa7dq7}19tuR1-)bRUYk2j@M0QG1XY#sjp7sTQ%J zOr=N%Bt;h2l`VBAQZ+~lItGbKRg7HMa)C;w+MF%ig!mLL6oL$<#SWzVh`vxgw=rGq z5r3_g%)+U*QB5~h94#f3;6X7xA~6|K4ne5h9Q>7SCp3j?I@v>$|N9O7=Wl!l_Q!mO z$BBJqO2RA0vRYwE;|R}q!^s8^_@#AxgvY012u0h0><5BO^d3gzlBl7UTT7qQAk?Bu z;VBjie8*LeJHmIK8)d@3AgG5ETPYECPk*9-_f=;L?v)id5K;<+(af@JP~_|dk&ohW zoY2_TS|y);2~E==>L(i<-5^$?ECvY3MVYFN(5!~)+|V%OZa;hK`R^u5ru~H%BcysKm2%{gMae_ zxgqdl5T|1j!ZGm%y!;HXd^k==H2UG-EyCFyyRYi-!+9Il6zh=KXG_v-O90aJNm+h} z3TtRkPL=xSgG@#yfqqAVkloN&&v7i=lW#e*xDK*;6H?;S*x|Yqjk%t6O()I##8Ijb zCcDa(TMcX*x8Ec=P1TyMS$5BG@)01F${#QY@x49@DS5GRj9Y z;1jO~F4XZAhV}pb+Ftz_+=EQI%Y|AZQj33$+axliud#&lwHjkv#Q?^SO@FQqs00GX z%%|&LsoWJ`VLnRn@0VX$*6-JL9faSAI5VIze^)Dc=gN0t6iaI&IS(-Dju4c5z1@-^ z)vP8bg7rxOpLg9@PLm-Te_%YCF#;l~4~*EfUYu8*c?tWL53bFka8gh|&fBenU+^!X z0E>5>i`Mx?XG+zp0YI&)w13ZYDZ&A28N9|;QmEk0t*})y%e%P-&S>Np%YVM&`$`wV z1B4|imUY8@RV+Wh+nYJ~_2v5UNyV4wc74fi*B5u&O~mThA5iaFNZpv;1ZK%KhTnMB^cQ1*D`$%r;jq*Zv(jK*PTbx9_)J09Uo-^yeO zmkoUL-KCI_9t|guc!ckTgGh;#MBJCdX# delta 138695 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 Q3jhHB{|7IeoB*~B052cS5C8xG diff --git a/rds/base/charts/all/charts/layer0-describo-0.2.9.tgz b/rds/base/charts/all/charts/layer0-describo-0.2.9.tgz index fefdc034116b5df42fadbaeabbb0453198a75db3..b9e2a9e1ce227cb59f56d79b5a6a8dacd2503226 100644 GIT binary patch literal 6651 zcmVDc zVQyr3R8em|NM&qo0PKBxbK5wQ`25XJ(LdT-PI4AS$*&BzQuXuLo{VdgSYzr`~Lp!uKDl#e*J%^v$OkCXLoP!ptG}k(AoXT@9cK=4}Jpw%aO3Wq*4*_ zlmFR$)d%;36q3+)C?w@9fN9quNm6`uJD%@45hlnYWFbWvI4xjgf&nC4s*ynXSL^@? zOt_?qi&+3e8e`CIdtSQ&qR2=F4nPZ};)0CO0hs0;b$qY0>+Lu#ATcE}fZC*Ng?uvM z%z>=);_Kp$i|V3-|M9(!=Q|c(qRg{H_zmL;gnGWikPG-nIzYitp>)PvcQaNhkwLqC zeSPf-lt@H4Zbd|qmP9e7Axe*nQQM)R#* zg!TpJ0~t{@P6uAdC+!PEaO?c|qV&;GvjhVmNfOhLXr?*^!+o#o{p@_}x&2t~E{o-V z%)_fM*T)_5-{0*XH06KiQT{(f`7+{tfhhk(QLQC)*NNx|r3yffNq28Qc;4woJNy3r zLF7Mw9+Kz)aq#o5@9$xEr*qJWe%{;Z4m(lU9Xua)cLve^&Q3Jw3=V$Y3wO!h!GSYE zhJq-J0&u&2cc(${m1|o|nW8 z5m!_y-Y(2Z&SJ{2l~M@f_ROf3+K=Ydx*rrR$!L}PraI$r1R4G4h|6&Nbu!(p>lqUw*4L@3~GG7=<0*fcm( z`)y^5BhDCxhDtYscULyZ%5PjMSWq^{T&i^t{AcSNN!&W^)e#7saD{ymMPfZXzjAR+ zM1+z4G{<8bkSOYZ=nr!c{i%snuTo?F2kmxUFU((E-tN{Bn7a*dbL#+X!0!mDOi3Ki zK%iFc$Rc1|X)z!Z4C!zNQ6_JS)<9UK0b(Rm1f^vR(K06FOoo)F(x48nI*;6UwZ7_~X6fkl~Ors%Ex2(F<29WLDB3)8ao0K#5fI zn1p(?uX%)-+-Si$lIV9q6`nC8^d$Wk2GDs)YhuQrFTFn9{?*yb{;QLBC$9paY^X*4 z;qmL!H~rq>#l`Pu=f?q%XhNBu(2lbK5!YU`)os@}h{zQ5xt z^E3j^;@MLtYi8pX)}GBFw1-4uGm^vOt^?525*6!x z{^gh`G9VHIaPyV#Rsh~g6qXaN;c&IKN2I*wBAVAaqnHnfp*%-uDYU!|!2LI}wZF$w zm-B^T)_!w#d2->Y59+h~xWoS6@%_60zqfzTeYF1{qI5iXiB@ZsB6;xk{1pslT6r?c zM!H&}apnn&WZ>M~w15u5duP4H%vAI;sSmfe20{=vLTd^^@S*{&pGyr3Fo*WEt(iU5 z0`=xbceGh=t6~i!Z*D+a7`L}UyM1$GyE1s;_V(rmyg7CY=F~sN5;gvio0zHu_YIFw z+rVx?ZYEegZ9H3i_(988cyo4q((j#}U%uFUqD_TR#V|qzt(KOMG9e*?CgL1pZJBs) zil?`?(Bkl?Shj@wuQ-W*A+a{# z#Hu#{09rf+aH?QJW}wIw0)Zi3!-TR_Wqz!l)n$}$+RGeLG9@(DlhrF(!SGL>LP!`~ z6BUk4YdcQh!}4m_y7$|b(-2zM_jrd}^LKc0a{li0Xu)eVNZ09o{cbDod+R2D&2fo` z`4ZG`oF=zhjh8*AH_#N--k}-N($!bErfV#}-q~^g&Ee~l8dnk?tvk14aGUP9Zunvq z$L8GHZnmaOD$S-B^TmD&;94Vp2(?0 z8%`;SZf%j}{Lp-AWlkS#<_rVfJb3Pxh2ghWLyzSfmF4zde~hvA>O7TvW+?a$`)_Zj zyH~US_V*6_NBi#~%FRvt8BFOU0Ex=PakZd*G0|)Z#~8q~wvpEDXU@ro#6)VwXq;iK z6iv9o>=-&3Tni!`QnplXw&qYK3gp=hMLc znHFODv!(M$AW_;J$otSbZU%p)B&I`(k(QQb1fKIdTI>d1=|1#mBn&VlTDVF+!P3D1 zJMJOHIFdjF+9(W~=#`0EJsHdlPmbSQXjpA5Xv^-Y=bR2fSao2L6gtlyKrqj!f@>Pb z+CE98rc-L0CXM5a+B;c1SLQiqN=AA;2vEQ*1z3yqT8HOfHq9`C7wf;>5}gG{ES9SF zENHDfy*Gi0piB+H{hMt4O}aH~w%_~Aw`HFz^RV=S_43Ih+(g=!oXr>uWp)_Wc+m#h zT{^5X*X`%*P!D_rfk~_lAoo99aQp5jtgmxasbGfcMDPSf5!$iQDKpmQ7);$9=Vrcj z^Cp`!o&h-LTzPp_893jgSqPq2R_6VJ$8|g}7DAO@>82v1T6g-%M^OAi3BAC}UD{|@ z?xR$nS`3B%vZQ`K_S;(xQ+7|F$+s8FTDNNbPvAL^M>{H~sqOo{8z(06A|MMWlm=yW` zXemgaKWY^b^o0w)YRD{F`Lb;BWF|~r^I}<(Rt5Fkb}4_WoLuiXSCmBo9GOY`nj}sh zQwxgo&-om;6FYlsqJd43Z-a!I^c=_WFa6)nE-o!-M)iE>bCL5@2zk@EcXqzCaV0of zZ}|4&3AC9SJqWfZMe>(eJt%g&(G9*1ZRX^i)sO=cPBhIY~YYChGr z!c>~X2(CS0rMq;&RkH+JMnzL|I-(VGew8NPpOUi$)!Fk5v9Wvt_}J3cL2ia;uDUks zwQ2M*Bg=b)t<#9U6Pf-HQk0vg*PD*kv%uSo1B0tx$ zZRW-7SyvIX;0kBK0(+n)rtK;6JHRqztL&VGXxDDAq=WS_FGV>w)E45YRcl+yoI|bz z+j?mnUl=7-@|)^4K_#-Ahk4*5u=>QQ7;M=bC@zoICy>$hCDQFtQ@xP#*}W=$>Q()` zg690nMrl3Tu;TnaN42G$;IH*!ZA`Q^XSyyEji6^QE=VzLlZ3YKoT4%v^wycBTF{W_ zjK#C!g!307GZEa_-|c&>fP(6Kw0Us)??w2%C`L9d&y$?|lJ8r=WbQfrD)=O*Q)5=w zKX7)A!+KpqOeUjNqW$Hl0cd2w+$FYPTto{_n;SkRa4 zX)?dGbf0GCzR5k?SbHAXbV|fMn>Hdpd6{3UXza7qqUZD5DswU0>6Rr|Ax6`COVRX4 z5TdBUw$WimjCxT!iySP{c*VkAsuS{E@>Mjxe{X7g^!*Uvtsr*G|Nf3IbE{3n;6 zpPvfgj`&Y!x7%%=|J;9!|2#yo8xd(7=jW={3hUJO-x#WEF0LpWc~{Tv35Yz5bZV#3 z;uzAr=g*D+)$XQOj`G+vBe_LzIES-n3c*ncC1snMPwH4yEUh2@#xmn+76FQjo2}+z zQ`x^>5tW`FkFdb9KwS;8E$Mv0zKlp?tdJ7ir+>4{;*=GjtXeu&CoZ?MoVo(NG)V_~ z0*CZNrE);RD`ZjA!6{v2Eth4prdo`4Zaf9p0G3V$D6(t^i;?Tmf9n@@^hbRj-!LtKq6`9>I)AIRI61xlJu-SJm*=Q>^l)vwt1)Do%ksDN8 zOe;B9t;|bUaRV;1YfD*IN-o7B|Ec1tRd+Bx^9uuW&gIq zB0J6}X@fiDzrWwD<$vvVcKyfme-Ba?%KsjZ7TD|V`l7jIrcHiK$NjRVy_QyPe(Zb9 z`IQ@h=})F6%S<7%>ziG9wCj3=s=T!pz#x^g0sl~Jl9@aQ0sOJ0LAUZ_Aw-O10Px4w z`ixj{1=^q$sHdOG|NH*lqyP6HB~7FfNG4#8N17ymeVZS~p2(z0e@x`qCjGN19-*3u;_uF_ zO)`lhYZIG9a9LnQ1C$^B5d`mp{Gn~uHwV|}X#egwP$VTBq{AVaiHC)L=~JX0BEODwB?ux z%n_+cJWrme#H?=*a&M-Bnm=u5YxCuUH;246{S@8&FZ_==kn+TP_O#UFhCs>V3X3~1 zB{7X&?D%rtrylkuNobj6c^!$OJT>Ij5ediGI^s+T9&f=G<1ML$=r%Ck3U%c+Oj2X1 znO=y(ZOit`lL`6IA|rg^Z_T@Dw{aI*YvKe zsQ1l!de5AvIXH{@icXNH3OZfCNjh0le^*PBY!r&DK_sC@nzT1Nn^g9-slBQHl$Xz4 znPk`F*eY&+b#8uQ{@5>*J@#wlj%{X+tt*m7>R20Z+CU+==qF@^0K5KfbpY*Me>W4K z78;7zmZ=)k9CUMO({I>hT{JY$puOi?`D>e-zvUib&-Y=^@4}wH1ADsrqQ&2B^XtmpN}t?T|+K}#v0fL zI?P`1mV}6hsmTtkIg+&xISW&fB_lSIXG?1DX9;)P|Lu)cnYpHaAaPs}_9;QxrS|`- zv!wUz>Ry?Xp_Z}0KO82uZXJ zDYO5%fFU)CkHk+sH@`hrLU}o+Y*fa0IYyXh&1Zg>gN9~v1^Ce1E-0ZWr0x7Rm>3b3 znNy{EU~^g-@DfOo&YOS?$DGI8@aE;&HiTRx+#V;|hA>VC+kh(c>@mOPy_<|abc(-o zakZ%T#<;F1i}bj{tRY-;akZVv87*4}oL|XpnD8mpG9wvt#Yci9W4R5I57jjhCc()_ zOQ7|LJSK`rR13KS10KyZK~jk{RH-mO%3y!TL9;6p114`3pCBln(C~?TYTgY~C{U`L z0O$P=6y|s=OUl~v|5F5iR#waZ&d$MZegDVr?mfnTAEa0Tl1u%{U0)iNu3gJ;j1yw6 zTIU*~DSDbUpQb}f2xK}qiiwnG!`g*m$n{F`?0xyBaUApO!ziK_XV0XFQq)}Su3f`+ zDN9mSzmMF!k-UNWycTh9u5^n;=iHXqDoEhLbc_G>LK9)pD>2 z0U9HlGY~&0w^8-J^5k~=lesb+(wNp&b(1rz!Io)uH73d?UCd?{m5`&#HWfV3-Yk9~ zlO)FN!UkxkjGFj>n(2pD{c`~X6q2vR>5AR*)i`c`4008(mjdKmKG6XA1pE;v-H=RZ zY%)+-w=I zn&XB&i$Th)wTSHOrqY~-M5eT-qeTH5dC$l6-uV7E} zc1j~O5+;L~U^pg>%8B{O9m*n;;fswjWO7Usm{MYvn7|km8F_H2aZ(BRS4yWOM*G8x z_O~BdWPZ~p&w_8_j=GBxl0iK4;Hxarg%(;-doTCxGwjMQqoh{p#FZi-4;`U#n~;jKniZrRGz}CzF(A3@$M?5j?~w z6=+z?w1}7>Qx&jp$=}cLP3|kin=-VCb1m8K+Dc zVQyr3R8em|NM&qo0PMYcciT9!AU=QVQ()!H+U{#ol3z*dZtl5m+nx3q$MMg0ckgW9 zUI!u}31f<26QCVU+WXmm2L*rx_>d?&agw>_nd!(RP$(3DLZMKP2+v8{_d>#hl#W>M zaEepm&+#n!%O`vK{eFLccNhNc_xt642ZNp6zYKQw_I6(m_V)G$f9Vf)2QT}7LH$pM zLi3Yxfz!YAAKg~DbKl5=6Z#8DIc4z>-EMbqlH`AO2Y$cX2}wfYki-EceAw|&jAvws z62`?OCH#-5gHTEm#;IWGe2B(0BEw$K_j^tefhT;}LC8Z~uoO>72ccWj(4g-RcKw}> zhj2tOAEMHv^a3`UvABbDu%75H5y@Px1EN{_xdcJKWjt@4pQDuU-W>d`ZaY``vzjk8JM@UJk!P;=+IcQcj#_{#dL^z z68$)40)##IfKWW4@dv<6wnI{n$23WZ;6wB$6hJ6qN<2h8F2_j7PNR?dqkNK-eVAWM zx$|1@18Cq6{=HHnB_T~oAP^gmt6w=0*HeN+lA@oJNs2>)UI5HY{k5>hA&X-Y0G3@{ zzPqr3etu$Hpql5!lyT7p!2k9x2#&my^D=;x%$Ok8I1JNvaDHUz9Zo|M%3lQ>RRM?L z^@r=RDI$MWp~_VX;{UMMEAI=%(3yJoos^hk1NwO1L1+#AN>Ii#97S`K61lmPI7BfM zk`3^L1av${q2@PvZG_`c7KjMGB}hmfgC&m%Sj+>;G7g}FDWd@a_9UjV8It;nA>oKK zMB^YOm=i8fba9~mO$A4379$oT65rC4#WNBMj_##vMdxbbcbW7CswsAR=pK~+?YiXyQA?0pi2hAw-65hk#YDwlMTcqAe`fLz6Jf4 z!wtPBD9F-ijz%d4+KdHB(RxAZbzcLWQ2}dk4IvaHnUuZyGyV6B%z&K@wqL!ax{H5g zgtx8HiCIW4Nkjs{QW)KiV38D|VCAFVe(!YfcuZp|=IV1I7cLhhR-A^!lKqE6B+^XM z7zeVqZ&^q*-|)}{!Qrnd735tEOpnulk|7$rp(Qp0;H}}4^-tfuxjsGq<@j`nXgroI z|KRBD$=UV!!R6(z?=Fsp2!}Ho%L(1-tf7#}Q?uIJ?#L*_w@9ul!sUF~2(mOKv54k~ z-Niw~vJkO2ns0V=HKn&u@~kN#Pe?d|o*WzMr3(@5LWSSl|)nWQe+E<#(L|KXZ~QN_2t3?$>k7`5jBc;%jFTu@MH$4M2sVqU{iM z{~O8LU*l8e^SNNwJ9~F^eCdl1;?Zp^k^gu4{qp&LZ~x`av;6-Q&%j4-h}yL*O&mdlK?$Be2u&);3^3#Zi;VmEE-K&YKkmDLKrm%SmVBYn z7{16!)VMmwqP)No!ZseO@Q3|v`zPfM(7HL?%eN;t% zB9UfsfcAIbC!Jkp<1zh!x?TZHGBNpE;dz)6ETlMK+K_wP9Q~2uh>j@-M8-;h9rJL2-A_99QS<$_OSYjl~#s|H{3;@@@&5YIl|S*7Vz%ht>eg zeO(Zdr7>Wg zq%0w6uG&%BDd=mp5Adyk`NCY?oas5^LxBs%gl|6Ch0PsJZE^F_nfEoX%Wz)Tma>QR zg}{?ibMnofNU%$h$^~vWskB|@jN-mk5c2a&!}p@^_xG}leLO)`y1j0y?UmXweJcN5 z{w)vNP79SO*83|T%30aZ#rG-DwQp0=CaULkV925P7hg_xRZexH z{s=Z)cxo-qV4W$6Wtd$@=6ueSQj^+ zb%&>%$wTGycNdM79pz}b;?I}I7fltb7qiuN^DFUXAk6J;1;o+8)xi%3m&eONnEPWF z1Ze$_&wd%AZj!Pv3yhx9y2iI4TX+3_Uc!Y~zU^TH5#ASh_Rb2@&CbDT=QAK*MLFt z8KOTuDIFLwTp!h?ST9ARKQ&tZ8zQG7jYAR(v|G7IL&zZlGz6ew(dxz9&1iS%n0j?N z6-1JxJ49XOOMpCdWJLFOU~SjkvTH{a_RN~!p5uf1?mxRlyd`ibeP(|NY`@8*qrQmTasGx+Olk`e&_OkGx-|u!x&319( z4q#VusO}JM5VowhB&95*!FkF8!ud%oNP3H-?hx(t?Q+&Ng{1Ls_P$UiOROi!J7bfh zX3NFVWw%OQ5~M`p)wLLb*;1^d-nA--?(e%cmGj@fckDO+E#$~%`7b0%#OA<9A1MGX zk^f%q?Dfm?-_GFK{^Lm=dlSS-!h88nc$ANS%~sg{O1bUMh>$U_UIg1H4*b&I#1j$> zm79s=Fx(LdYzuSM6gQ@gu+V2%1k=-pwZ5+anq{K_TJ)071AF$$h|Z>W^0{SEHZxX~ z2o?z3?2U2Mz$#)xU+Tvc<6@Ahc)s*iA^gj@+HMIutjoCE!fr-8X9O1 zxe;U`sMW$LeU`Em*daU0w(~5CE@6|h&Q0YnLr1e~)I8`<6qhHCBe3Z?kUNi-ClKuR z7V65>R4yblyBFzCefOM~<7`|uto5kWiiJOiTT@N&w{o#oCR)pxE}xkMaIPE|czWBz z3GFSNqC6X&x0$7q(cti19L;kJ=TC%Yk}{CLdn>Gf9P1V8JURX=A-)pEB)+xhi9z2w z`#O-o&*{gM&4y)cV0HbC%rD5e{H?+!lZlte^Kw`z2)v*;602e?4Qo(z&!?XbOEA7( zj9>X1iEqoTdBu)V)0S#!RyZxKQVsk~R;;7sc@p2=;&jET70r)%3b%^NJzGh8UU;h% zY<941bFQ3?W-D{i>|0=>LUO+*VK?rHtFGqmFFzk&ADo|G|8Q`5 zeEsvqX}7vu-1DC?6_&ei>BN@~jeSiqtox;i|!zD4ww#Dw$nl#Pgsi^Sm#5#=rwzK70RK&pbaQbb&mfW;vn zqCwwT>o}xetnE+5XDVW&?Wt81bNshji;ES3vC^7x`YLMVZeR};N-J$xo@PZew)@#b zTKdxG?637{*8dp(`S?%(OY}d3-RLMbGB4bz8O084Ae~XE@W9bcz zC;rVVWdUK-NGEC<)$5RomLIJFD*2{6raUUlh>-{migF4}&Nv)ZlD^d3C_@nuSLg6E zMT||Y0g7_ZR&Z>x&({mI(uH}1D$H}xu9B^x@tS*?G>eFX%5atT;g+RiZ-CsDbkv^M zEN6Af8|W1T9XL5A4pBw+OAFIGc?~s-qp$1DC!3 z(kt?5pJx7V#JtbX|NAco75=}!_ssvF;;Fp^*9K+|&#s7RW`0;~w}ubHy|X2wEx1tr zx^EU#?3B7=Q0|y!9k4uoN?70lr{!8J>WX!ws5?J7M>Y2bL$^&l1-M1JXs`}};8 zrp?Na>L?ZIyU+5YcIo?8BY&cYgbz1yBOdoXQ`nNIi9 ziuRIQb&c8gl(C!c5J7u9gD5jO%PwDb8Ex0ygb4d;ZHPu0pO4sw{E`gf91PKK>$2#& zF$=-zgbxw=ZM{7q7PvqI*p$fWCyajgjNFp+Nm*-o{ri%zbP}_ae6lu{t^d94{Yw15 z{A}r`Ffy1Bm9>-y44aH=lB8+S#a3Un3k}ChlrjOpm(HyZDYD$z)LQ;sH<#+s} z%j-+QQqoyN+SlZl!%Gy>lz03I6+QT`LcimW{+agRzvjhs(v$x&fAZV7R{)ad&@2Jp zSKj%~=XXixJAZ_4I^X$XmUO=Rf1Ne-3r;D^I665x<{dvt**{1iIz9~v?x}Jq`$xyW zqXEOp;XBi69W1R;b@143?=;%lx|N*!ck%2N7IMQ;@d6x;8t%1C~=))5k~fw&O*7QHa5Lj@=OaP1V- za}A0*(5w+~&{)@S%blx_Yq~5d>Z(ysSB!aTipSx#pfkcUfdR@46l6?e=#y`&6Y-K5Tlcf$B(B=PLY$3f4sfc!zp>eZ_xOb@;E{ zBHHWs(O!QW?e%xio@_p^@$c?jdtH24X>+(Ym;;Fb!SkQakxD@VI3FaE1?!y=cDUHt zTP@VrtgYj}{A8NE(73EMt*Fx0uvVZWi`8KBBj)1p&55CJ16JSn`|a`4L^4l78~8fL zTm%zp@OIm%DF=HhLTYPGrb#WR6weU4JiItLzqO2@lx>v%7uk}o*v9hvpS`mFZ*T88|HqR&$_MLX;csFyG!FF6J>wosz>qyis;D46~|tFBUJaj~YI zT}-i1K^OJRq8u4e`clD1@-$06;8_xpty~7^WibW)fSAjdYWrs?!blMOnJDeZEuW3z zkPLDWts4s%Dj&-NW&(bTlWvS>G=fNSi|DYplp)5CP)Y^gL?aed%UG~jQa=0>HG{=EF^+jybDnM#>Qx4XUBo^|dBUwQ_@zJ>sNY4aO z=P6x_QI57BRE z-6B;_ZZPXx&O5}FS%-Y>`@Z}qRP$T@1OxJ35;0-Equ;;5GX&$~HpE_1%%=3a%O71b z;}}PCPPwF0z-F^7)&#Z)o1`otVU`jgZ7f;DjKsnz_9f~2h_lJ6OuWiNt1w&f_Rjqx z{eSuXM`ca!DR<+Z3SIx1-KhkzyG}cpWWKQ(rJ&Oi4NO%nOK^QOObGf__dDmG9%r(nPs3-=|3t)!44d&QxhrekeCp zLxQFTzxkY0)rnjaZ6=>CcF=_3YqgBbM>i_0j;>OPvY^RszkjVG^VHC_VvyRx-=W5FJQvF9P$j>^VO2lYYVF6th(`GTKI$l@DDBF^* z;51zapD&fdYO(zHX}rOg`hQpRKMkJ!KcD7#_Wxe$|LwDsPX1Nhz#G|DNV4MOF!Njjc)L)mfw=8u)`jJUvYh|`eEt|bx0 zaX|del$j7+C56|BiWtwR>6}Oj>CG?~B+Wk+)auYccFus&1ptoDZp8%-Zk$z0XA|pB z4+?nO+x-vQJG*`oPdYbbe#g=f#>Z-^_@m?HH0pRrebbp!ATqdzf+&+1cV?Itfpt6t zMx{e5+RlGk80SO&gL-F`Ct*s1{|pJ2;AS`#H1@}-oo%i7pp}aj;)@lGhN${qFJ!?D zNj<0IrrO=zot@2&rITKvd>r)U6yNz4SowpVw){V0!OcV4SSJ7NZ0}U~|L*QH|9^_7 z%(@@Np9g(^rxVf%;bMsR6mReC4_|F#Jbw8-8If@OYI|pQZ)d+x!tcZF?U#dqgs=7o zqks%vjmG`$!Tw%OwH6zm1tJHgJl1I9sEkZ_2)+x_hwufOf}UtSIR!=3(c z?|Xmu<*S#wuXc9#{;fY4_WRv`*`@kfp5^QRRvnWcApkC1|AXzFz1{7y|JPvq`TX}J z&)ORLPi^3U-Y8uTxVsZUQr=lxGcS)|1J&08ZV6>Ywq==-fgRT!jUvQMI$SWYELFBG z`)oe6m8V}Kp*)plT-DdQCYX@2;lnIVr5J~AF^%vj%1bmLJ5+Bjw2pYIl2{9bJ1$s? zCj@MYI%{hk+0uWizyh{%+d!!$kWoW7)U}&*tLMsjov9K6+=4}KjBuLb8i+Q&2HK_l z4#Z>0q&e9C{Q{|BHu$FOoPr@Gf2UJJ)a`ZavintwC7D+1yn88<`i1 zQYg@=$zO(k`@Mr4LKh9Xom-q`xDfga@qWWhIgZ{}tGwTA-AbNO*=i*-@9JAIRhG6w zK2R=bJW&KeXWIJSyf&k+=e~QsK_;OTcd&3luF%gs?X%ScB#_D*YUr)0xU?-D_!2V3 z*!_@y+o0_LneUgtv`!{@2QG?tFjDm$uxk2=+XaZ>Y?@FpL#tCrPgVPq8Tb5?I~X-J z49@u{i$?jonm)p}j?J{S63b!9HqUaBlIx2&!DM+saTLu97On3(faT<2OpB~l8^F-b z+0U5sDg$CXBfw7V#sJjwsl-|ox?ksAy+k%A|MX&5?afd(#CG^(&xSwzu680v_(vLzfv(1L1_ghe_a3DT}O zL?7=fFs&Oz0Bql0wS;bgfF@)c$1!Y~A;puJaX|w#V38g6XmW5>KtWEcu zPXKJ#1b#!Jo1|<+w1hB{Ww@#%w_n+~4#1li&{HEMalTG3KGzr)TyL;0#A6)nwlkBf=XL85|F8q8&cF%GT$oI1fEpO%aLeHHjm_se~g3Ov;ntW(AU!GOq$qenSa-tgUB+w#$SBbUe3fsV$pVs)b~fO^OO* zq+X~~72;EtMWLMIoWM#`0jc5SZA39WG?bOj<28yyBx#WAWxbG$D2}~B-%Ga#YQ7po za5=f4`h29rf5>^eeh}T^IbWAZ7xbnat%FIJSl?1c6kJIv>nGzgCeBmBNi5bCIA@x< z*#$xt@SfbIy-7wx(t{RwZ!JlQCkaa9iKpJ>TeFo2(QZpM^)6A0-e|5oQ)C~t%nYsr zB^gu;wPrwP85eRCW;o6O2Kd;3YXU6NJkf;Tu44l88=mKzC0@cpFJJmS*%Q?{FG=Yw z7Q{2d-P2;X3R<8!ZSoMQJE;iV&|l?flV(H?>zrli4rX9PZ;1TD3DH}n?Ucb{6tRgK z33MWm$kLdj9}X^ml03V|Qgn9k_C!tXiQLsS*`SXGA^2(-wuBQ?Ve>=lpe8@76qe^C zol(x!&UFZ0KmeKD#UwSJdx<*T=qf5}Wx}X(D!Rzx11=@B;h4WkS(eCURt4^`^UCM) zwniP?)u~tcC~<>DQq;~_Jr)?LFou%=)*xf4V zDiFU;NsNjvP;Tb8ai#^vj=DG#th-g-W#Bj@`LtjtnBGAUB+cRN9?3IdCRix9(ix3O z2+ENEIgxwc7RtEXpY?%;57B!XA}@PCgn!?lKT(hgyE;IOMPjph>S zIE#S(?-CMUPU%>nyD1H(Xo@-FL7)!T;aswj0I;ATM;nrmeF;ob6C5x}xTyuJzq1Uy zfA4TC3f(W|tI+zjK{uBD9MM<_F%lUqPqDBv*m~c54cn$t1W#v5SLz4T84KMKhPRf$ zXfB(J9H?&{2zW3hVHS}T^nE`=GYCh5q%jtx_`HRPKk-qQ3zpt={mOZ*kqv;_j&ea# zPv6yBoWXd=d3ifW_04R{6xEnV}gfctt)4`#9S$8;rPTKBJ&OrlF=5GLibP#h(%u z0=cUbCKuUsDs&A(ZngaZTy!*Z&(BJgywv4l@)VO`0iha!HM8`Bn}a5+zJ47Atp#Xk zsOA(XSpQ1H>O2N!22XK#kpZy?-| zBqf0nG7EQ2bUX032a+jEfjiwoSzO$s(ilGP<)7trPB>%+R;XBX0heULQOY89S9R$O zhAeVH{>W%bLUomOd2aPL9pS)qSV2M;5|G@1<&Pg|3>U;3X$giTi#qwPt#u@nTI5VC z)}>Ert=Tqi^%dYQseHNAP&Qe58P!lxE+AcpqcccVEEku2ZOZZdM*|E#kArE-VwUkk zHk(la`X1{o$bD3RD&9v2O7zm&gduv*;`dwVy~!PsB+=aZ8(ekd-w_LNq!kiuJbvGR zOq|UwiE?=6k;ZLD@n58(7() z5}A_*5Y z{tyjn6s9#6m%u8&_xF2g77IEfp4wWyI~+mkjkPyFA9}wYoO-9nzZ{?D(sy~n4^fwm z$9iu#dv|p_L?;3T`IPw&2fKVrshz4)ct@x-ONMARzqZ~3)a8d zT*N~}Byi8DGdnPwdj9RbJVWX;S%5hyKFFf5??MB{u3@vXp{-(5+Z#n}WXzf`jt`FB z9{aO!O>N-u#KWGk((pht!(tD@)_7?aqYX|}B&z=b4855=r6bC6gAKF+$I&bysba(_ zy`>SE5H6*o+@ynA#trX33n4U{@WhmODPzJjOPc*;?O!36BP#Gk-zPT3+x4UsabsMyLB^CIKK5aUN05u}8AYwYzYh|A?k#>`_Ql ztu_1%ov7k?MAIRvvo4Uxlnr=}<6CrKlIG7Bc!;_qoOW~j9$CXR?lG@_!6`p{Tbz8+AxJ6cElv!ZfeE(|&5jGLanMd;D#8(1 zk=TX`@K!g0(+LE~;-7u^3S^ex`zwy*Oe-#ff{Y6`EApw;C=o@9V~&++OT;Em)ER|o zV|NZ69j2@DEIxFsi!Uv@mb9P6wYK6?Aq_;XhQ~7!9fI{V@h8AAKXxIVT=)azJ>}gY z>hAUXZ-3~5X7oU7i|H+i=38)oAK(}znUKHj%?eWYLV}G{hhL8f?-jSt6(TnY<$M(5+<9R+KvaAmFl9mPY0ebfGDzm(_Vex(%Eps*GS@gos$0-<3NA z-)uvt@9N1TU;1H*N)MTZYjCaMnS-Gxm^UI`#BfMLZ=AB32Mu~mOiAj=11v~Uxs*SvGYl5xkk?Nzc@EO)660``e~=^vb(5~@D{0DD@#2kRoI;}&{{2l)d{VkwVEz7+Ug+NJT{`> z*-;3evOpu}HL;CBUY}e~N7&a3#~2+9jj@bYHI_;(<*P;UB|Np-uOa%=Q}TwIomj0V zB+`rv^PRBBC0S(iFH>XFV( zl`1O_C4uRbqV_ZlopM%g)e(`x0m8z>>1B!=ESq))M0v{T{f5LMor9s9r;zePcQTsO zm(v|B4bSG+^0iKd>htLk{b|HvwDv!GBO3SkG=D>aDMQ^O22q*Cl*SVuBxV=1yWKnA z8V9Wfof;>b(?SKER->y`4*H)q7@-XsgE|cr<{2Du5_1ZHPb|yH%@X!9S-|j^( zyz(P9k-H)g|CCM4{x;R`a!s^VuI?H(2UcVoS3V&cUc@Gzl;aYT77ikitcp#5by)3K zAQm=tM0wEM8q4IwZPT6$r@6^w0s|wdB-?UfOStOj1bRAE)I{hg?uZK*m7C&nBZpXE z2^B^~|5h#NGF6zmigVZCKv(p6OXIhMbI4Gxe*pdwxg`-8eaVAf;eeDGGqDar6$nF> zy}W*iPH9ZeA)FGrGVWSZ6oS@hOpuDTgf`59iH<9m{|=Z2szq#aMQ7xnEG8)bVb1fi zF?o370?!hALYtC}C+$l(@Sdkco{vr;{4XUOsn-&B`Ci8bmLE32dd=fR0um&IT;-)( zgL;@l)J-PPmwHUz;t%%9dW%2MSvEt02^=A7_PTAkwHmu=E;5R;r*uX|-ip1-3Zo%a z$6Ptr+>4aJHR}L@ov+e(%wn&&A1bNy>Yz2YeHA2sBP4;`pZp{iB)!EEx;i{ZH~DLo z-QgCO)Csj!TQ(F%q|!*JbiWI#v;%$!n&=XAWAM-f{!p2eG_3=H1q3FND~4qPS;i1E#7Wt31uE!ReC489 z8maF;U!0;5nb0`4+;XINir&kDzwzIPy`B_9ej7?SzxU+nGK}As0czdG$$61AD|NEQ zDQuvWU#^@e&+k;ASX+O@ESaWyCVXJmw{?nbZ%c==FKbtp)k9~LZs+Gh?I^%U}q|NBf=ATz!4>?IK66sIELk|?_^c))f;`0 z3zQ9+y5W`I{|(S|)jk#2!IaF1ab<&P%-huzkI7sJ-(G1AK z_2w^Wq(ve%Ta9+B@YYXkxk=3|{fC+o<_{QM>yPWijRar~h5JSk&b$K>=s!f1^AP7^ zG`B5s)gSsptvRX>c|a{AL1VNe-y4xmrD@RCQYYuNa02rc_AnZt;ss05nUNc(1c#Ai zCc2*XFa+ibzw&BBs8oo@`RjT^P{VSZU=u}*2RUqT46LgfvrxsVun)KSOLgmQR@W)R z;SV^%aX?bgaGjZQ+@c(B=QNt{Lxvh&OoZH%Xgo3RdTWrFU!r4o$)MLr4?Adp9Z;ZT zN}-GAX0WBW0dkXAPScU}s~K?Y-rhTBfK|Q7|PU##k9We@dbn zz;H$58T5-ojNQdX8p~s-1ag()@t6kM2G`igt246-%^>NLq(oLMY@20QTKISb0pZ-U zOwbBn4^VA;1`W3_;Q9ubYKt9(kf#vxXp&oj*YzS_?@On^CBVB-uCND6Rv_BOQyzD< zzmH=5W_&4k#$7e?C{pBx`9Z|8(9<_lsW+x6xx-P^TT2NSDGh|D!Hii76f*?#N^4z2ic zPiJUGW-L_~YCp{L`%tB%0#LasEL4713FDlOqPe-@$#rM?UKhebsg}_DUPx|xd^&?I z^Zowr+xN;$58RW)6b=Bt>BcDM8ToyqMunzTW;(A=ZuZfG8oakV*xxjfKLDVGax>7P zko{AG+}YpT-2~$)ZTe_}EXbn@iCH$8O19)`MwJ746p;utwDOQNCHR&`K;BO74OZGX zb0@KqbWUc8m>*H&stb(P&{N%0l?L2g$H@^thk-8`9TKx- zoO_`c$2hR|9LZnBJn}29AKH4gu7i#jTrN-LD7=U7=;h0osF*h_Mi{idg$vV$io&g2 zQyLxZ!9evr$X`WpCTKyq*tJ2J(|;1BH0h(hX$ypY|TwDJEDr} zgN`CEob-7v8s~Fxo$EEJAeZHt9Hk)|yrH=nbMdy@?Ib2vTUki6K9pyh^+{_l&wn{w zKDZ7$8f9?`~MDff)l zPJ?l^u&vz98m?lLLA)G(|3Zg7L~Jy6E&0+f=7f)x-r7QkMphAlTHRjrgG)bM=8=8e@_)HUMBvr&#A z&qhTs3O&0EwxOM-m;$9;AgH9-t!>)Ibwq4}&VK<)8$VN5riW9}Na4GHgibI+IX`qH zLu7?AEJ&h|txhZ(iZa^&U-qm2$ZKft>#opNZYa*8XbZ)x(ik{^Bg##57f!?$3Z{(ZcO7PL&s!#`kI=>>*_PAy;MdEo zJSI6Z0wqFk{&u`2KcfGse6$4)icld712Ed7q9mlZEF@}B4&ZMjUo03Bd44D=nt)ap z1c$$-RFHRZ(9Dd0;o9SWLH@`HFUNW>H79Eg(JReZYkRWb7zB5i1=n13_Hj@p5iB8e zX?_%n6N`CPB5=w{lKVn7voI|Mm{#)mKhT1X8fV^tx5^OB=29juJk)hf_ijE*LM+Io zNUv|e^cm6O8u`g3X4*)pe?CoxAiYbvI-%FNMc#p)Xj{Pt(2$G=jez0 zZv|obvV^S({8Ad@YDvX7YDT%PN3cpi;V|ccXVmJ~VN+Y7eM9DrSVQDaBunI+ycNoM zL>(0~8Ghf8`4Dy8aUT^0d@1-WY_|-sQPzT)bYiR3I6Br*JKdm~t90Fj zg0nQ&p zx@{GEJtgfMZ;&;)R>5J*quaTFYzs2KD{cgvmuL&JxIS-$SrjP)?d0hNhE-{~baPz3 zTX&Bua8CgNr}Sk-=_u`l{%|^Ee_4E}F538SeXOn!Y~k6uM6~DAb%`op`T5!z>=uXY z#T|RI+hzko`>s z`w6YCAg)Sb<qbl%HPF;ecOF+uV|60+7%WwcROjrc(2i7*`KDAPRiZprPf|NN%atWfh^(kFS=RcJ zhFj}So)suYfgm1$#qeouEXDHK*`{-9GU5{}Tjc{-S zhNC*ebl${!g8?Sw}lBXr|$em-DF-qF+Z* zmjc0=UC`BC?s5%+$9o? z&7ABGHEULGX(oJ-YmY*<9!M_4^AqWT?dt^+rftM#DoSx5UKja^bSHGw)Y6Q}&F!_X zhr;b-j22)*IRX>#cw(7Le!SPCsfX;Gg_b9Y$(dw#cAXbEs%DjE0Tx=dq}?7;Do9GP zG6!a{aZrJY0I9|q(-~a1z)&~^VHJEof6$i9KV(A1!l5(5x6mEY(aE5U4jrTB5`|c0 zO!q>QP9ahdbW8OIV#LTO937De&y64biF$`Z`4VycR#Kr2#KiHH37@hkL_ew*NaSzYg5nu0-2&8QruT(KpgQ*7wVuQ^0{~G1Kr*v~0u1)T3_ zBKfY|$sro_!Mff>c!&ny>LfR{YiGIhv9Z!>^Sm9*r8B)9m=<%r9iW!8y&bTY^SunL zd@?@Cgl{p?^TM?=-Tm5JZMC`H7V6U3-WE`c`Q8>(%NgGm zT+2CM0oQTgw4U{QpKH>aXn7EK=hE7O3O$c;AJS|%UjdzJrIMqFDsXDA^D5ZNR??}d zRtxyTp=A|vbrFZ@t<~%~?|-WcQ?$!NYceGD$ojS+)tA2`$%9q6)L3 zGc!&9NFxF+orgso2v8MU>Co+hjgbCE=C#0X$h;PyRlX7C;XwiNpczGZKGsnR!Wu1t zorUA-^s;w(d77UmM{}b<($0c2gl+oO+{iAN}i9d2bvj7%d;Izbzo}!&RyL4W#Oz2Ar`PCu`)4(T)p114s`4IJ53esOTJtoc@TzF-Pk&pfZ(NDB4uGX+R#8L z%uDG{Iz}l8DbL}y2>DX4I=ABrxwowyb$`{U%D*Y>NHxE?+%3{DSGNpkX5p$otDu_g5GMj*i+4j-u>^KQ(gOwh8tS*N^MSnJ?WOb*!KQTeIzq3Lfkr01qts%(7mT9h85bAVXm%vk7|3W3 zf8^~}LRAjl%-6&3gov{w3v~abNx;OR)1s=hbzhIoDI^u*c+S&4;-~YqfG>OJ@R~aH zgbDQ{Cd1oKSVCD~@Q|R437x6>D+5q?`7aSASe=<=Dnbn2p_HMPm-; zAkL6U^|mQt6g=GF%W`7iuUi%<;9qkiz>>rD@b5}SczWJ-(8r?7jF?UVK{2NTQ+5_s z3YW9Af2+0AgDCV!mQkA5K>0)Q3oh}vG5TIqxLR-7KwREEy!WTzY)!Jp zF~)PwO*1nGLU{Tlt>fZ1K}KO)B>n!HS6j|P{hI`~WAV6*2vMh^b3$-^X%GVr*M)o> zCR!{Z`l%7?k{OmE%ay*Babm8$aHXFV#UX17lpXUBqw#yb4FA?ydjLm5G7LAz_viQf zDN)}Dl-1}EX!LHHW_EiI>E9lU_ZeaNOmuIGQ;uh6g zDKt_v9o}Th*GM_IJGq3t96g0@-NMI7AbzHaw6*P-yq7TP;|wf?}FMzAcwFJD#L z5~mQFuk_oLE44iP7`f@{69sDqH* z#DBLq9ys5@xRi2iQxy$-er*pg-w!{}k01BPla8*AjskGKygr=0UEVGZj_w*sE5=(d zkWv1CQwHZc20NAvJLM~~ELNi$2~Mg)3unh&rrSk(#dn7{5mORp)ud;p3o*lQ5-&yT zU<&`mRzTqg%)nKOJfNW41v&FEfGaqwJN*F-r}Y6jC*f2D-q z9?qzA5b7Y<9**A~;^R45b>Uw4)66_JRB7g^(4t~!k}v5{Ea_0PXi+WcP}69!9FDhE zn{QS)wr_#;b9{W%vCA*rHdj33qQ_UteGKb$@zye_UUESp#YPH#<8wll?n8 zJ9~BB*8MY+Kf80jj!)~}4K;TjXg@`om992e?>sN?7rb(zn!owF(dO?HQ(R-2U+fZp z|2;ZXwv&X1yYU;UIZc*qk$iuNDadv=3Ep7Bb2TLMvYqxuKhhxs1-^d!$p^b&?rXp_KoBp=fsOED&qNK)z<9;QxE zTkc+U$cW*_p{9kHn#L;SLR@dnwQvghx6!@`U<{n5+3*Z2iniF8vI1SVYk|xUs z@jm`a2f%jg7^qZjfZ!e&Vx*iLQb!2EL+YG{-Qi2Ad5I!ku9%ono*gRPDz(_PJOxB( zZ@=rxsVY4u$U0j{T}{h4hFHRTi@&KK3qLkYeL9oVWW&ay`V){~nI|mFY zM?iE3%r5b*RwqGZYmXhTc5*o=2ehz6!5!$dT9K9=){jxLbheV!l+k4BT4dBu+E($I zcD&g|ncyj3hW?H$#x5i}fHp}L4`HQW=$R-MVok1WeI2D1W<*_8X)~op z(V+E%(Je4PWOEeK)3K<^B{G{0O#zz|w82qjJ+13SN?`wfy=UO|h6sH}^l$1ZUlP{% zZP$b-75(H9A)L6~e{B?&b=TJ_XP_muD%U~IJ}w@7dqyS3^q)kGCH4w0fi!t>$-i`# zl^YMO9{$GFscL7^D@}HY@s)Y-HP65i@)*|U$d#s|ofKftG~97}3(}VS@p!po8&eBM z!J08LR|a4c4ol|1-$Ad9v{p0{GURXbe;MEAwugfBp@IK_`52X+))+dj#`%1WM!IfkVd`I87hS_##z&W_cHt7_f=CdergRqm7% z#Ea~$FK^zn4z4H$m4`pn#obK??eaGwS+dLqX?i>wWr&icr!Bf>F>Gq~aw*Q9F|Qcq z=${czGxDb#=LP_g2&U5+Xx7p#-Fd*3n4gpq*%DyoLK86#qb6|W7@hP$ErO@5j4q~# zFPj}lpSj0hN@S~&nGi(D5s?m$`l^uoMF!8@R!p%`>@4$YP{R=gX*H1N+C@{{n5#6< zMoCaM0^}KI`QUb|jOFh2y2=l%W3=MgIuOTyXVZm-4g&vIRgsb0qWu%=ZH?MAIa!X+ z_@nvjnj1saaATO%uHLg4*m7;HEXxLPu~XvTBbdAofQYI4y~e0Ff7R}WFuG*WZDt71 zQMFwLpPV$P2WWaxY|fH9E!Cw3Vz`6E9D~wpA1u6tp?8qnaVktY4{ES&PHk!Z{xuuQ z%g7}Hwv8Ato3fFM70IXv$>Lz&l%0{jBl4e&V)3;#>RctH*D$Zm*xc#YY+Jq4cGJK3 z&BiRW^Qpa7uCABEgBs({2J5ze&(C4@csygDqGA&`cU+&KsfWfM#BZb-$^aDMm1N*7 z(OM^Kc+KNazzGCxh~bgz`~J>yH-tUyJjun<1cDfd&BPpMM$lyuOgFrHWwDsW9K!mq zKA>lu8^enC>GNk7&r2ATml0p_>xPOz(>G0r2$y#g&&%b2N_)C}Ws5!U+V`ngNYdFC zuir-Q_Qt1FE;f%=^A4?vQIQAi&Wa-=Ab%D@uACdv{{%jbe_c6?0lPapftJ8~;_2b& z&g&WKOn8E$y%b@=yY6L1u_Rsbpp*72^`D*B9K(3X`KZy<$>1pr-q1hBS}V9LGnw;M zGgVMQuFc--2za=6utp7fK0*c420_=heX|{%QzM5Tz|Y(v=~^{AafL4NL+@FL^E2~# z7;wph#WIL}I<{XE$xdXoM_3F+*sx?E z#UEioWss3AX^J97A>}P$iVSFTdcS=V2V)l4y3Uu8#D%q(`Byrv&nlx$xXf>GhrbPa zutCsfBo>N`R7)$ta;vUvnm1R((RBjNz%`A+6pP0pk<`i*Y0tP@;%xDPymn#C7lX}n z`bM0VytPEPyL)snB0}$+XEFsX1M>G8-#kz%qdHrCtXJWNcW-7p?Q;ILsR&`r1hLz< z#C|ls`Xr-zoaVQ0H}LT!A&Tq_gi0Gz5Op~hH4@U?9x^W8HEAwQ573xD zD}TYd0In`7@jgieS3MDx7OZr<`xa&~@m-n34l{gWq?oG0c^dhKLw-D_*;qT_BVYec z6oK$1<#<6CQT?;X&!SLc(ee@i!~610gQKRly$Xp5>W_M|{(Sy@K?F<~K%w7UP@!P2 zH_iv}&i(;4`m>r$Km2b@C8yaH{kTLf7L+Y9rzQqr<&IHzENclqtz z2)?CyV}WN6cPsL%yERUW?O?F;GR$I49=Woi=-xfDzm}KaaYbn+j5tbe$o!zT%rE(& zj1q)`nnV(-H`vZIDJ;;?_QAT+-Dc5r*`d8}P8-L{W-mcIcbF70v=vTs<(-S7zwEs4 zyD_qX=FWZmnWNbBzO0nrl6Wb-t}+<81A~&^(jr`0kU_tb`%86XJJFv;WAo z_~)kuSPk`A4u!%zE8V*}-77`B;IHwpPrCBK$*v+U*e(71O3jcCt@|6R%Qw66gP!vQ ziz!s=5*5}&i7=kxqvEuMAk_nZ9}vyxoHS6o6+d_9RkRQlol3|{$-!xXfw3{U$_ZQj zO6;2fg50|9+UI|N>)!Yi^8#u@+NgwZzM`23HvlNLNM})p*V<;rF0AJg-%v4;rMTiV zW%2Ib`#~{M6>x!W9O7D<=5sTYgRode=g+Is!LhE(90tXNgpxr$wVP&}K1(9xzL*$F}QfsuG z%NG(LfxV^?Z9p`a%Dk*Y)VO47#(Q8GfSv90^#%FU-qzjT(bnni%JM~rUQ;Wb0?-%I zyn+pw{WrggfP{nN_Vc1W!vUzIMVdclcJc{OY+YZibqtQ8BZUNAW#?$PEpzcM@G`hOB8 zUPa>?8!UQyp=+tzXP;w_@+pr36k~fX1-;M2%Q9!J#aA^%;jzvio4c(ne1Hk!5hF7dM;NO_cgP#x4$9~(jN|0vB(;BSFS|Q4$hVUgy(<(bAm|qTx_`N5 zP0W@l@qh2~0!kvZJlG3UjXOKy7tTpyEl{XK{0(joojP||30?RXR34F&z( zX!UZFp(hrumk#i26TaeyP#&X@ze;l3I64Kh#t5hYYu<<%hXgSI^6l(u2IC$CoS_MH zgbcp&t(9XMyh4HoUd!Aeb2?P!O$cGg&4tuSN<_q?lPJO1F>eIE)5{N8-R7cYfein3zrPacQ}1!c8mKly{d#=9q%!5>T0gmy-CCSDMsXx@-F#l6&>&j7 z61W+p6kY+Cq?o9_Tgq-WuZcIB(gGFpdpM^^hE@@`@6Hi8s!g_jl@3i;kU8@%G~xz;||nztF;V{IcUt)dL=73} z3u#LWRi#*f1_R& zuC21?UquwfG5Kzm94B28R3*ETiVc-)Fu8W$rL-)3Wx%%=%yehjb$1^W=aHqMiLmz_y%Vx28!)qV={Nal z6_HQ)qrNqC{4ZtKCtrU^N8#qVklw%rRq^d~SGQP3mdAfFSuL%8ibH1x7em;hmoUqA z{gXa~>Sm(#@Ky#WuL7miUzDyvlP|s@!p+aQukQBF&pWsO2*)99BAJ=0%KNeF%!jM! z?Yi?$1122rt%B}MskMmOG`yiySl0k-`zIH!@FdCNH?IeY>NvpWF~LXnHWVefPtq39 zGWi0#G9_Pi|Xls_yRD0maqLRx91Qxs^g}V%G zbSYve^W!0r1pMr?zY6Um+;^8X((z53Vpe#trk5!Qkx5+qwM z5w;v(xtZ@>+FQZ1E&HCD_e&8%sPVCks;k+h5saF~QjJ`PT2tp``I93Q8l(yQZ$YGX z0j58-R0x#;KAP7PODtL)rc3gQ3ZftP2>g=q?Kt z#uUo?^-EKGk58*UaY@Ou5b29EM7hT71yH3HweGy&w`VCm0RsYuu-r+obWcC8$L+ta zH#d8~?!SPj(HZ2l$XU1txeLM7vrvEQHRAr%srf1RwE-=5Y_8otv7=c`qPs_h3s#m` zS;rB4tvQ(qz{S^~E^Psvm`J{nN4uId4$QEPn0I8WyJ}=FKLY&F2{WiU3LcV#4f*>B zu=hja5$2G<@9Ll2b*=H1CUFi0Q-P@?VndLjfnnvg-wjgZ-TXJPL}}=Q3Qxa~VUWjI z6@~(n0^oigjK|*#JP*E(%H0x9?ty3llzV-Yq{anm6#mxkmjw2@g zHhbcBegkQ?#V6|#aAZ|8L2y@|{WmiaLjp`EsOx*d1Z&&t>#OJZOh+Ks3cm3kyX|ag zI&@IJ5HA^U42%V?ODvxMrp4a@G^rz4bK0Oann0Z<;KnN8wE_r;I|hC39%O2Xna|lR z(;d>MQ2l26@M;q1RtVf*A^Tdv59mEy{T;RX7@Az-#=PtKwU)8F6VG~APrnKw5r%8? zeAC!|mZBosDh{n?pa27jaY*VLNFoi(u|Go_J3S;ONEh^7moTuRlw|E%#wrBWX?E3plH1e5-`(>O=ff^WM&Jh&)xtgn!dmaZ@mW z+D>u`6K0r^U=BtdW^nhz;X}f5sQeCy6e&PH58!{V?%ncU{c`TVvU9hi*+0kZpF z9q?Q>;8x9`=&^7NNd`utNWU;0`fGwIIBH@87$`S}>t zo}|2snQ~5tbutO!tx+BSoCopZ)X~E6^Gt-UP&%IBK1{r?N3v`$4^-w-$6IAT%vwHBp5PjRCXn_3Iu^P<^Q{>@C;LI2nPUViL(xhQaw zVX7`9+|uFju*J(Wis5}Qd+`Yn1E@&?a4&vt>&k`#+v04!2lOt_i5v$b7$iq(epr)74@pj8g4@O z2lp4X)p%v-cLZc zxZ=z`WGeQ97@ZS>ZaQOR(-&;WpCSR!1Ag)e4-Vl!&gUkG&v_Ec%7iLqt+~REwr>=6=HfZyh?)f5et%{;&{CUm zapVW`$UPA+rxN*X;)j;3o`CX>;#zFE>Fj%TK8tPffu0E47{;q-mdG#^wbd%>kt?E^ z!}1eCyu6r(Fn%OS@Mtz%Jt0!t^*5fRRN?Z+)Pk3krz^;$*b!)_{bBnM`pf*tgBw`EzrSCz(^?8;Vvl*nizIg!3r z*Rv-k#Zk&$C|&duI?Ir!bTtF6pp>70Sri``^N=d5{>n04OBy0}CS_|%Y5_ty@5jee zk_3isrI`C)M2^cX#IX>_0+x_6TWZWrWo94JG>ilNDvd+Gi3xY)N6OPXB^X+vZN>A! z9n7nBjSZaCDeEeTkp^ltQP->^c3K-G_~vqrVM1d9oJsdu_s(%b0`{WVi5*3`17-RO zu>GSK@xH%Ie!K5IwK8o13NUd!c|Af7+OtPd> zq;iboreD_nG(QIYlp#s4F(a;Loi(}e6U5qZSY9jJihW~-9#}IpdzqP{2p~-+B z8do7MaAOZC*;q6ygvC(9A^0tyTfXE6@+%agnYnn9ILa^E9n2&RX+JcoMz%#py!&Mz z77m%y|ItZh`~oaxKm8A6!)X<)(Ayk4Rpm2jLAFmDHAxs7eSi;Ix$`wu9uyiwo!DH& z-if1T!bDw_X@7Y9o*E_fJE~~;;)bC(yELOtxEEKJ8bE zmQaZd_(IyZGj{ve3HGZ(s5+>59;rEe^ZS)&TMxL_`56-$$&Lxuo@KIhc-N*;r})pW z7lXe7c;#Se*?>n$8n7`r%BuOJqBRxMEFs2(sRQdk)%$a1-=GZt#hSh& zVVkC=E=b*ArXOca{^}@=CI;-nRtsr6v`@_zH|NSQG+R? z#z#&g0UzfvQbaotU$6M-=()5h(E+PsmGgn`%}-lY4W6v)4Oi$H?*uMCX}!FaFY}hj z-S3a!*M5#17GcUHL6jay(wveBr`sdjyah!6gMY>GsUaa`$G)$EbDczNnaL`Vw4kuvl4JsM?_xwbNu>@Y<|81Zy( zSpwOTzb*n_gO`KnqWpR+LTNM!lEaQ@iHVs&)VAp4(-Bhe>j+ID3I&Rn&GC_;9&7nl z(L%TOZbGjzd@bUtM?hck#-vuK@{gGp$lDG={nl^sx3?9Y>pBx*L(hjWu31SjY{~x$o>dPik{)s=J?I2jeZILSke?JnjFK4 zz@-T5?^^uwHL6~9J7tSIVW>WvX+QbwU9~$t+3~aieh&X)zk{Vqz3d~JFd?mDHRrFG zG#36Wbm8ZPnxFMMRi{;fE~ImkaW}z}e)a%*xqh7<-0aVOy>9iIv0F}J7u!0>{oRb8 zufN}q7P)?=bSF1ZZI6&(?FFF8J1_|(R`7%E2X{`XnGi^lBYLRr{Tn;(mA8*N4nFvB z?DlZ2S!d^DW9R1KSY5Bi^g!B^U;sEWN_kyOV(kcJl|}(NwC(2I)%Ez2N{uzYxo34!Y({ZEXksV7*kCf;iTh$%3&^p!iWC_Yg6JxkwCdQI%~AaBVNQ*g<@hx9>(^Gcv-B@> z$IQ64EldYwM|M~&1cXx`1L<5_{29pqL9zT=OZos%5(t@7=6YJz6g7cv-~KaJ6yp9)U0n-Gn6T{f__j67edF_^&* zaUuSGKxYztllIN~?C0n9%zl5F_s)C&{rc}Y%j_qap}2mPp80`3E=59iIl1e0_TVZ} z;*-B0AXv9Eq#Y`DUE$CKC>SZ_c#!6%sbBlCxj7hdVJu~JE87Dl#rS}C~nz?=k31D&a=tG@!C+WdRFgduUg5S+)J9QJQpbUW=DDX7g}C; zTw4LC?QC-Zg;wt1l7QpE9E^mNH@=#aCtM_ou5d^4I3$ilNzW;iSX4Zcl8vrCReuRr zGXPE%0$;9fDa#kMH_MKev7;NLj#sW2aj5Q6gYX0DtiL@h3;}Vis780{kOJ*Uyje6v zwzJ}lf~c^(55ur2$CA*zxV$M;-C$wMd*8}ULz~kCP2FL!&_Th-(*@1|ke{oF;u74y z>L^8Rbfh^*OT+akixMd`padbJrh_X@Qyo}Fl^VAnbv=3T(ugWeoRonj*_>6Hy* z$8sbXp@unOMyA8Gy?{9XO;BVO-3d`vbZScYmBv`wpENNl^q+NYcDJFRaHRry`pasw$P96{=EkEK4*cSl8rQo0K=@ zi&Kq{s`@lm6kE5f+sc#bCN&TD%x0y;smFOdZX3X4zHXY5w)4==aT_p2EdCJRKXM0H z`lJaw;&e!M=-#(Nd*hyy+>L%=lrFi{jORJphTPjPlUh{OCRuhSP2F&r?2Y{tQ)6lX zp{377OKDFWOTdPv?9`BiycBcP*}pp+s7gw>bW{qabO- zbX`6#;IFMto&Or1>Ez+5IfX<@yQW`2g@BOSEkj3L#7>xis#8%2f9AS_W9`3%8(XzO zt>SS%ZOISKNDWR0t9!IzE1EybU~fzaHN^i$Yl(?a48=`E>@lw`34YIyFqtR~9I!5M zk#KA)J7^CY&f1~}F=2ZJ5OR{G1?hn&)`3=-myjyrD%Qtj~YTv42bCbrtG^!HVv;59;Ikc7v=HQ zXWu~S-Ur$~&$jm0Hj5z$%>mHs@r?ZI-izxQ+p9HfJAyq^m%Ci@hg#dV4Z%y@>2rE$ zx8?BZ51Yd5C%Fon!s+U!56^ z464F49;iHjG=HF4zv93)dltvWT$6M-WZnIhEj1v;bX=IA@j3t4dD*eN{&YKj=x4}f z$Uv|r-wC$9#lXFi)5bW_;RsI5XNR_&AB_Ylo{2-UN1yBfYi{;%pZRxA0ve9aueI6F zfT-S_O_G~|q1*s5R;F!E<0pbi+L2Lu7Lqj}cjK+h%5Pe^GnSZ{g26*yOP4gwu$zeS zSZums-k&%cn<`u|b?&MBXR@_+O(mnQm0+u!nXqXN0#3|Ttdc}vnrAQ^5Qfn$scZ3f z%xyIZrG5m@0ll_##Rz3kj&;>`MN@~#uM>cK{pILx?|E12(&b_A=xEFGmz-Y+Wm9Bcj5e^xeQ$$FYCtRKpqh13ruQP*_ZrFOef_7g4rWNL;h?gjJ+W04^0VF+TOk%K!ZXL_ zpn9Z!0+6ofHjW*ZK?xna1fTH~2K|r1OoNXjxRZu5gK($wK6{)X$woWR!yK)wy}(l=0P&=!1gcANwKZ@OiSiL+WICYV4SFbX_F#l?UEyyz{QglD`Ypunz%$_1$um!e z;ommd>0_oOLMapZw^s4gd}ydQ3s^0tW^l$otBXEWIi6!6B?dAypvDsG2^COQVw-_o z_`qDVsicN@uM-Hswr5mraCFwqHG1-%#z?P{N#&MKUYBG-aJ#sj%H}AwVRL=twz_^h zzrT~i+960f;%F%kt5Y)ho^BhyUx1avYe;Bn+W3%ILn1~Hk5Ho@ks{MGD$efhkg#lF zS3Lz?!#%nAkAyIB1QF@cBn%w1tJL?Y>ZmZ#p^B1z^m(WMGV@VQrqGK@e zO0}b!PCU3v?S->@C$y8|IJg4TFp(Xb>X1yOqtN8N5*6g!qUUt>TY!aa#V_l43`#4Y z@sx41fv0VKd1-CJZLnvVLV*4x?eVr>Vqn=Eg}rXLQm`|OjU0i_)3qUZ%{)`a{Ro^Mr8fHfW2L3GBwktl8T#Q zZpbszeidp`Usg7E!dEfeGCQ3*zx2^N9huvLoMCo@S1R9c^5G{rV|q7@uuBx09!V+r z3gXv{_q%Z|Z?}BUi2kAaoDt}9*#&Uh54~D2^*%uiSZh32&6jl^d!%%Ksxn5lNXHY0 zGvMAFOgU+eUT|pQ&|A1Cj}cZbZ3+AH(ZsdaPLs&{u}pV-21KKd_}l>mu~awu?)QUJ zsvBi1JYwKpGjih^i#CzwBUg`Bq_%dcPCAj=NMuDrup}epMOJHaG#Ww<-Ms@I&>H+k#a3xtu()VlVGIUAjl7$)q?pcHBG*r_c? zQm4Wpu(M0l*@{JI$p&|~j9WIo?Lbjl2Ca}P`NDdnrXSJE|&}Ffg&~DXbv=y_OVq8>RI6j9KC!VJ z-%}Ec;T2%Gu@#zx{bOcZ;uB3GZZ-XXa*0W$v;WN{9w8MOT?iVvuOl2lsR|IB>I}9m zZ4w{lDI3c(D5C?I7trYPLaXI{MFvI#)OHT7ijQ7u!(fp~l zv9a;-?NH*y#lnITc>PrVl+M$r)8+nnC-uHqu5!s^W!1g1<$n9O!D+aqpAilDp2upl zrJBqB`yYn}Z(^}X=$AR;&ij7Cy$B3gMmSgGEt>KPhM7zol5a)>*4F5s$!VrulYh@g zio3gGCuAufJHf^5lM?-uMDcV)e**+~HxZJ2(Q{2s^6^EZN^O1knfTdY$Uc&Oj?(k3 zBrD+NxpKgiV&XBniQCehLxBF}$Hcn9!j?ydGUerW4ul#QRxr(IvC^5PQ~c{10Tm!j zS)AOeYP>&yp2*Gmeab@oV(h?AFH4Bkf+XKOyqOG4qy!YZN| zRSeE7oQ@$z@cb7|Uwq)Gyt!J(hE+CgXr*6|Ye-vtF5hy#pq{kS3_@Fq8W}Rj8i|v1 zKX?_HaHva9T3SrB27jy8t8!?QLDL>R{&xe8bRh9Z&bq9<&ugcDlv)AQF4kLM4kfNw zKtJLj6{7jig&2NA|6QgFq|zhP2wJ@oautQ*zk^fguro1src#TjFMcJy3f-x=C{b;t z-`iF`8nFNj7n&>OL%ye+8KXJq*cMezp_lI?_zJP!p+=z*ebHD*S2F~UFEVxjgZ=o4wryeF=*hhYd>)@2m)gL7q zP!o0b5tx%K-F;XH*5H7z*#)%h0|!q&IvyDN|D(F3Ob@y_dzd-}W) z+WbyoP>YH_N>2&y9)A>dqCX*K`;C|Q&MJ5LL82?&03GCC)wYD8#Q~=ncM12E__)!? zQ+H($rHhV|%9)<>HtpyBdVMuZ>`HXE4^%q3g6Zi(t<@kCX!~^ zZ8WnSN>4U%vmnK~bD#^YJJ3!A)x$8jy7jTvEIDg#I;_$U}c1 z^Yax&;AaTTdI4amFQ*nx0MWOA-CNej*T81&Ztp$d_5Lfz z->ar+*rWq0N3QfGYLyd%L6|7fhIPC12G0#aTJ3J}DsU=`ri)K5+_BTtj{B;;f~6kQ zzV>0#6yYl`??f-dK95 zN-gVCuT#pkE7@8dwUAcdSur>;CNB!le(;AR)@J2HiSdpdXIhr&BJ8I2SmS7T_zzW! z1(WM%oS=7-$x^DQl^hL5V&INI)5Wx*viierLrnNIz(%qU_P0<(Jleae?UzlbdG}Z* zP($Vz`0wAeEQSOL*$-l@x%r5yA!2@ia(Hm;NZE)_I4;6I3oZc@1B#fGA^a(PdQ!a* zVFDa{-8(a;HQtN>)j0_;dWVc>wjbEjvVy{EOrBE5VSjy{^WO2!|8Ru%Vd*0+DB|a) z*rLvbxWe01yKKcr#1Anxz&V0=tzG8&*w5YwD_Zvfm|x&+ZaB9CfNL20vqf*wp9-47 z>1G1p7dny#5MPgx$5}05IMbb=U8Y>m2gKG~z;2xBnZFa{=|3E6nrX4MCsw;dXmS3) z!)z@avb`@ileJQ90gK-=JM+D4kSjKZH$J;R?;g*W**^jAlTYD)-?Kg3veW=RTL3&3 z?Cg0SZ>{<)G!+#U>l>*dk@RyIPf4$Y3b)J zyvL=-pkXA!c-II>v=E`32pcw!_^HswoJJ^f~F9F7S|24C(rNP;oqCM zea->|ffMLmeBL7ay@0&+=Lw5u3ynl!{yjRuc(rl5p%NJ1Gh}?W5Mq;FRQqBUY+lb? zI3``mAES0uh{A>S%6^@%%|`km1)r7qJ_7{ly_U72%&8n}1yaT<#ZKiA0t&g(ufE6evp@EPiBU>Xn*X3>>?ns5s?YR0v7Ddt1-K0=x_mSY+sxlAq$wH@CPU~8c_c5 zOh1c><3JzQ4=wo*QY7_AB+xa2)2FWiS6RmNA?$Fd_pMjG5H9S#ruBZ}gxHZu`#S+^ z+8*!Kkx9Y_fy+-#*whQ30s2!krO5U$%_6mrC}|W>92uprIkPba1g{Ip3epqievKw3 za6?6QLvhTd+Glp4AB)+}k$xMCG8k5XVoM#W+LmcW9a71QLd;=$JCXh>7JoC| zAXjYh1lYKOqokZ^t%=)!lF=`xOSO!T+Ngjj{-1rVKHtfyXjE+j+-#FL7^-CJA8B4G zmwD3<_OK)04biY?riFTs-!Q5i1fU+sr!c8=H!Jy`8Cb^}SLf`JO-arUIG7jfWHHl_i7F?@l|IFrwEa-PM9%K9F zL{#A{mndEHHA>Jtt{8(RbcJri#~#a!hWr>c`DD16%(?2lEy!ObvZmP3ReYN?#zW&f zmF|~44-)*7#zh8;sQzBr$YYMQ3!^t>Cv{RK2fVLlmz9(Z+e8cp!^Dzxm*!*pXcN=L zE+Cq(2z|tf9NPdnFRPHqna97nh_jUjCzS0jSksR&_rIHkkel+?_or0xiBoK+Cu0eT z&}R11fNuJSL-@X0g*=LC>Ru&pYNg5^3(C6u;QlFBi*$LH@8=I28Mj3-L|V#aV~Osw zw;}eF$w4y~CSV6zqq1OEBpqo2N*3k9&q1|(!sG=XUg)W)xg6wr+ftBog3?QdVx9%# z;s{YNd7h8&MSm>Wk}r@`=l0LTTp}xF_S%H^T5P9zzS^Vl2H3N68;Q1>sj@|JaWt#gSpf#Off&LIdAe&vjGiV zLeULkLp{wR2Onz4^PzubSPUVyPyvk_fI}}8O%$D%h?qDof`JW9cRx)4^?wzWNl@=0 zvSW==)iq`MuTT#eO){A}?}8Sz*#szNdKZ@Q-7)E2N`|2@eIjx{>Mv5snX(@&4~)E6 zVTU_NDu`NlU$6$l8py8*5r$e$fu&dlvy`xFWWGj@#hzw9!)O#zV<@&ubX5$tpz2lH zLsHL=A5}a9VR0o!402@+)8~&lePdXV%CcCnY3_WIuqj820UeYp?_AuwWW}b1US?8Q zue&cZ&r{X#;y!VRe1q;cAZ^cpK|aL#!mUC>c5CX)8YMZxH@}#>!cG>F@eLjGma>B% z(N#A<<7`1dT9_>jfl|EG@tUDVd9ja06Yt=TfZmnA7JJmh#f}6 zd}5#Qf*rBJY5WY{vk%t4zlVp9GvTw$7XI}A9(>X#oB2Imcye^)IZQAF6vs&|U>aI0 zT}EQ=!~7!I6&0O0Wa=`~tfxpKtU?(-c76M(T;?bK4R8tjy4Xmn6MzJLU?sr8=v15;q!yH zggahzH+$nw7lvypZ`W~RBd;jWZ8RVi+e3?xv{3&ymS+pqYs$Qu$E}HGTAj!XE9r*$ za7lpHvjRtWPOr-B3Y_yUTd!vgvc2N9)qK9AV+8qghGa&CQ!-N*x`=IY9}e-}x6&(0 zRa{@FzycMW@z@CQC4L5Qf*NN|7AxGH1oWSMpk zOS-H(*E2T1b60YCRU`E6pba@Ix>1I-uOC z1U&s_8LG7TZB`hzIE38+4ZBoATav;|N?fDIt3b4um@GW z=(qlE3PvJqa2-yUO>Q|_edl)zz&|WFfp~BGd8z@rq-UqzX`#f0ZKY^VLKX{ZtLIQb z#t{v{I_YiY6cS1oaaQfopz55H(3#_Cv-|VC%w8^KxBW}RA3vb4P4mhYE^p3)^dxZE zI>UWEj|FeIJrrrKmtjLXO1>6I~aGq`nv2IC-?WGUO>?92hFe0pgm_3cKFXYO&Y5%DkFu?d{ z+I>n4B^0qC#K&^Ez*6n5@pI-l6vj`iXo*krs;0Z&DtJFW(9&`c+cDmKg)h=8a`%G6 z48uy$rTgb>s1wojA$QlwxF{_)V>L=KiU247FU^K@Oit}dn_JfGRUo@@0TU-tcY}Fv znvoQlrBF~!AsR7h_5hOGJ57<;d07(n|07d4M!Wz{1TBeGDA899%@M#L8X>|Mpr!g_ z!#y%pyuLX7)2I*p>sx>B{EnDh208_HPmenJlP0hWPZBvljnKFzr>g;lVu?M)9a&{5 ztLc)&A!2)=yg{jHe6#YXy4-)Ht+LiB%P06}V^29R$pH?m47WWm zuD(1uBK=&6-j>@`usPE(1Ib8uZ-r9qkUkKYkYp$*6Cnd~B*diC`I#0Wxp9`FA*1b6 zCDA#?zI%hgpAsHJn_qS{b27x|ywN4W?{KZMZe{Leg*^PXdnZJISd=JY{4-2bw~=RI zwkM7E_J+1^pu&4oA>iih5Ck4|{4Yg&P|0P->Dde?FEzAIV1O?g(e~lIdN_slgR+p& z)79}`h-zmNa^17HkrC_=F*nj)5eY}-)1yw_CVL&imf{Nz6mX7ZSS<{++93KVeboxp z#)YwS{Qd=ysLOf5hDSg)V#<(;mIyaM8Ms$&KD=p(cMy_#bIei9hrODe9N6j8{B8Ioc7j#c6nui7E`;cve(>P=R~M4o{v4f^k;7s zLYvQkj_y4>>i|Ulp_dS!Y6ZSgdhL7#d;5oqv9@o#EbG;R)o2idKx?B_=Fq&e?ouVL zeV}8}Ff8I`gkVZ(PnRc$*QYmaZ&ivE=gIjP#$&f#qp`P)W%Pk2Q^n`Zv1_1X_gf@C z`?&5I%P~w6F$&JpWC*a(Zjjifwr-m*%=61?3ZTpHA9wOQ%j9bKL!E1;U%^vT{n)OveaZl$}S{m|yo9<3^gyw@JZ0Wk#i+j%?l98W23 zqvgr}jQ9(WA^Z59#2MXcv?E@E#>`_LYBr$iFHY>33`2GgK^Q*wKAj{9W@VW3yh$(P zsO;%pJ3XD6e+6H5t-it!#xb@N(}mxq!J8d!3507)uuJB`2R4RRO~p(qr-Y)8{-xy~ z)enAB(L;)@aIajHZ5h%MX2VS*|Pr@8a@|0KB4Ip2MCudf9#SXS97a%&`n ziD}6e?Y7{ViO`13iVpy-bn|a(+MM1|Dw&lACCLmSC*G?%Fq2ulEQc%s753X3-vkWh zWfBJ$*tpk%`HhyG!=JNK!b@AeqjR267^TDy163z#DVb`a0tRv%0URP&IsHP1 z@ZEP zaEhuQyc@%+qzs;uw^s{CD^>(yqgns`r!cKe^g{{iM3N6I@`JcKSmMbL(SpSCyK?8_^lFFFNtPnKf6Yy_sknc&5Vi3QsKY|GXL-ear$8<%ZFHj~&zK12= ziMMN^RW!c&C*}@N%buYc1zsF;CjLP?yrMiUM}1mh{dc|8I1siPi?k-SbO=K+FAttk zDom9}*!H3gr`kR9O={cn)>z%?O!nM*r(OKFLZH5lhYLI0OvLO2{AlC}g(fS*3PR4S zJ&eAtN5s_9o-rzX<~4Z1_d_T?Xz|RnIx_*&!?Sf!ryqH-=zp{xjyrd-7JFn!tyF3d zeB>eJP9|kc-tXU_KEk50x``6Z{3Z+akFYKXC74aoY%)FJZOzGkI2uuE+x?C!dK}R8 zWd^Hlgo!>Ein*4-R^z+>A!iP@#@LG4kchd))Ov`P!V4Cd(wn3NRrW)WMp$Js2{_L( z7zs86ivfeei(&iD&-52!3u6AhM`a;Jnr?u3mpz5$$X@!Q-YgokmI%sF#N9orLZLyr z@!uzdemhn5ooB0#HUWGxX7g$*8~~Xhg?3%q1F&>{>xEZllHA)AeKFOP%o3zja0xnq|FMiKu6l+zsUnv@aW0PmZkZau91e$+c;9E`6ts?Bb*e^7r z?pOl8JG7c?XdB!Jg#W4vO;V1ZaYL?YZ>)ZgJxz54x|O3d5Gm$N5vX3D8f3+MHhB6# z7ZJFhS}YjTr;@_o4&(68oLmr}k1|;>rH|K4Fc&AI*pKffP=thefH~7)7H%10PTbYJ z`+f2D(O#!e`hDmj6h|A25)F8U(omnZHk<|PI=0&`p2n^gle$*(^g$DGMr^Vh?>^v> zHih0?WS>I}?eclNs@bH&7MK;+*5#zW|nNJcWB;~@o_+APWD-1;WS z5`c4V*uN;qn&ZDO>9*%F#9V^>H(+ufYSaHG;=kjojpAwQYB8=`uKBpznnlu;RbrkX z#wm+cw02GuV+wIcs1apCI%1(*gU^HSP55=o5J#wf z%Vay5P=nUg-1qPZLd*jO>^+~5&rjx6QEN?M={T`OQR212m*az;2`Bvxgi|xC79KuI z$PPy7|6&MRJQ1`>h`&pu^stRsuE&L40jysfxcNpcLFiscSrlnUga83$!ZI&Z6by1- zKM4c;x{5Ugvc=G?#6gUAkf*3IW1Oq*;ruHyM%b2w(y?4YwRJG;>85Z#xrJ~y1ufOw+p;BER? zui!J{lypc$d(TbazVRK7>?cM%tkuV31(_Yo4L)_;LS6ETGOwUfT>s4m^>WrbMHqq7^g+pe!GEKDnyIlUuY#RcwD zRxY~RGgmUw%bDysWs9U$p&EYCl}kfAd+1zPsz-61i51n`Iui>9)XBUFM+Lz z-;M8+fIqSOUo#Vt579U!=f9r+eI#&t2e0}L)*b`jo3!=&?aZ0wGG(xNbc!NZ$va;R z$!NtFPLNKxG~H{ijkE#okZmmvs^-zi%U<7P&oe>O=hhYArk(ITb|Z^DD#7_Ku3w2M z>|Ez$l09Omhuy(8GCScV@m_=|evN2%A177}g{RWBgauB=fOV~#EInvbA z-Q5=qrM)Cn+8Y|F8JCX${ETx*8)NjmyDdTKCejjGednA8tzS=}y$@o5co7T18J1O< z-jGa6DY!PRWX*|T9DLu|DN=BwNGd4$cver75V8jh4TWdrJ0Z!`7ElfNg#2;(fw&(gW3|4D zzQ%D+O0VGDOgJPo!8aUYCM1HP6T+I2|7TsOZFF3Anu43l0n=VfLLT0Y#;WX+&@Z$nP{r(fi?yuN$xfDK&m0=@irLyS144`1(Z+lxv`~gLa52=bIqD3F1+QOKK-Qu%*N4kbO~O&VYs4( z2EAhevgklmu5fK~}#$SRn2Jo%XwG{1~D{h1aE~zsT%laMtH6)SW|;cpT^B z_K*v$;CHiQGEF8k@v7Ol9B<9HhVvQ*(h~ZcPRAu;(kmEt21lhM!v~jFu}>|!9bN2U zR}=e_s7ItOPh1|{7d~?nA&!~0pU+m+|EZr_*%V*6CR#&{V@*~}AWISN7bD?%m1hOB z*_R4$n>!(9*;F5I)DL(Up>Y%c{6#`VTH~NT^?Z1x^^>5SSj(A3-1bwuz`N%izK`c0 z<_HJocLi7^bgV8brlQK7AT_CXaVn(4RwJ%LFSuf$0J7eltQs|D8jvMjjA{c#`V&^X zzb|Zx91Rtcv{h-V0yw{QyQ;ORN=*nJUZCnxF}U_y7rR_@fF(QF!0j|MsI|PDVlns{ z=|;QYv-dhxA~Zr_Bt_S+Vli0ld2Yp14bfW@;}h931<$I<&Yl^r>J>?rt|$V@m*4M{&|gJxFT2Y&EZX$=Hv59VeL1O6t1*6J0x9 zgLD*2W0JJ^@w1ru7$ z5Pc3^gizbj{uDz9;(zuP9KYhyTfsdv!0M z3g^Q!dO**h@Y<5{`CO2Uiz&sPlL~z>!2SyT4@W%rI2Id1c>!Aj!HxPhNqo{ERTl?r z5w(z}k85TILcsffuJDULrHD?S7!p-uBW+q_@mqbai%Xpwgh8vv{z=fFrti+93=EgR z!XS(qT1h43)=!aiO_R1Oq==m#a)~}HvaNVPc_hlAtlt3d(+GXdF5p2j3zaOO!-Oe- zsD_af_kBey^{Sb9#^vPYSlbf#6(@JE6TmlwN7oI88e%2tkFY?9VdsU(L_}fUo=Sxs z)!0NDP1GFZzwP1Y)GPog#82#;8;U?${E2PQs<4;)DH{2UJq&^AiZ9M?H;<{sI?iIl z8hs~ElK~*zgvmPJD?%#3o?cWZVvI^}QS~NKc~%kVC+kpczfk6{)lMXQ5bQqz@HbV! zmA0VJ`X&&iPJg3cb%iOG?CKQA_{86Z-BNa_nwjH3WN!fs7&Ba$0w1Sn#9mFAEo=3x zR`I)>VtbZ&TB37&@k*w0sAJ2@boz>A8!Ud4qNXiCdRHe-c3=|qlkM@#9TR}hrz_yI zw-_W5pTxuOb;aUniRCEfL3D40>D!#>##xgSA<7#CIn07WdB*IxL4qiR_hDKzd`i!^ zx)jgM5XJ)Etl&l$$+rux!7*GxxO)6H7;|1;k#gNv)3QmhPO3@8sTFPE4PuUhw#5$u zUqs&)TcZ^6963kH7gtBNKh%ypmng}SB|xXHwwQW(f2s9M`gx9h&?R@kY2FLNb+}%Eyc+wA50EJa1aNJsy9DUz@U){ z(K%j1Mq;gmp(f9zsH~2ERtB>s_iGCb;mp&=#M7%{f_zBQ#6DnWnq$HCU2$P`qHWS9 z$#209BqAsg;g`wqPZ(;9=4ey4rN3QpQ5{pz!iP8C`G+^smyWGKVsm}DM3^yI%r=>PnN?T zv3b3zp4^7^xZY&Q*>PjsQgckHX_`D->{}_a=<_}OrT7$WaSb#YJXOd;(&;xXoiKV2 z(r3{6m(TXLkZK`idlacWK_yBIBTZrMWm2F46P!MMVT#yTtjQ|>9)1I#WwK6s#!3o( ziiMiU)w3qyEDN6{!deD7HU|+7=6exuuln~fkCIW4sh@Y7(C(@rGkrzDZZWKZS*h}= zTb#WT!zWJTpyx|bPxEnh7!CE(P=FQrM)c?FD!S3Q#I<+1i=X*r?qNk$Rl(B)A9~qX z4-HPH)bdh7k-B^~Bd><*0-Q_FSS?TZ5pO7J;{-pvKlU;aoSBn;8~>-Sur%TCe!()F z*>_)JrM-F;l5u5f-fR(Jh7N!6v`F1z(OFAV4+18M^Iky&nQ=V1TS+wIe?7Y}+iZU! zi3wJJ264a$24hG;dT7{UCp+iY_peM<9eWqp(+B#&5?ZG5+NU0bW?d+?1?!Jiu>7bi zrzLuTRXbbLFj!fD{TrHk5cCSb2KQ+Hn%DZhoY`^(Ff$?#Eo(cUga-peu`W#OQ6`T` z8!W6!S^U`#ne)wrPJBY3AS~}eDNBpi%E;x&@C-ANwi;g|!(y_B=?MpNe8+S%vcf5d zU8hQWT*Jg*29NKiX-t&YWPipM+=dTr!Rp9B zY1D{-FZwT}`rWfiP|pg-s`;8&K%tVNqCd8exo99nRtSuq>YH-Z@kWF_P|M8FuhO7> zAhYIym|+e2KJhSI+Lrow!@u#n?Ub;+cG|M4A<=y-l;Y@F@~nMJ8AbQbYg1ZPGgwUM z%u#Q^RgVet4o0uXrF2$YQKHmm23>ocu^Ci9SLc{jxRT8SV72N7s{WWcL+du3(?DWnuZitOv?`sd zY3R&6$ZV832}~^5vU8SaiSBW{#yJqNRhl$lE`Sm&M*yPc5pY6hWWgINqrIg*%3Qtm zf}6?a{)Gw$D)BL4(&B_Czie8Tf!7qRdL3DpD`rng=}{Lt)YMS{4HQN)qa+&Rtn%s? zR1p8FD!Rbt$by;YFxZ6i%jY2SZ5owEIO zc#D}u38;B-E9b`TQty{bv{@<6QPY@<`a@~9=q08p(9&cnSsl8`Z~m1Q>B3^qwX$$* zF|-WyOFAeues*p-4nc=OAqgv^X7j!F!%!zIHI;e@zDR^|n#yu>T^6qa%ejLD{ROQs-(y?J@P=_ z9MRgUA4Om?1+oJBvU7k?F?NTwx>2&#}ct=oe4?6FmJ1wKqTRT{tM`kG%nhTUk(om{bpc zA+bofrn^x-7U0!auS?!2z1C#T&Hu>%>gZzv-QsWlwvlW$c%ZUi27!7pn*8f;r| z27uSe*SOWC(XymmZj!ewSJjw%(k#JJ)Qmq^`AqXoP>=P(6Mf#T5=XVO_!I^azmAYK z5A_3I=!-KPfTE1JUBPpx<_o`J7#m?PV*YpIXXlM5$hw@V4>@Tny3lO#>LAsk`0rqX ze5c)DIo^P+Ui4Hsu>odLvLz9$)Z}F?(XQMz?B@)4V}FTnboAD z^OF%~D2&1Jipl#3#rf@XD37}dcM@P6{|!L36Px>+eUpptpgZ=3D^?^b17}7`$|;+8 zB(HkDR{wGP(EfJL>Sq64^{Ezk&zgOi1*Xacw{wilW)~_)LWELh*50?p(5tA6Hx&y4V5Ne3aEn(8I&n0i!f$ zazx*+S{&OniKr(#vB_+$J#<;+lVTFUNM9J}?bQfsGSl)mSFc&IfPt*mgE7vCY5ezQHkP& z=9n~Y)O;LS=rTo9z%+?2ects-;Bq;7^(#0ho}v#x63Ll3Q0V}@z25OeTdutvZQ{rN z6k^lqJ?Wa;0@*3zXUFVd9T>|zMz%G2ZUfcf03KxZV*j)*8aXaVwBWo9+Rb{Lu9EnW zMDHYJ531H7;Xw!lBaNm{gL&kv7(=pAgjipkdz2_!)ANiV#p-uu+#C_lazeu>aKMxh`#}cUT9L#xQe8fFbF;V5b#y;*SI~)`z zLu0eFe8N@3XrN@B!3*Is=KAKY_oIe9-S6L-CKD-LUJW0X;;X$G+k==mu`Rtwtsm*{ z)85~~BZmX5d|YrM%oKhqhbf;X%-lksF)T7gdoFEQAM_#phOx`RpWY2b>aY zhury09dpS^VZ4T#Nn@YwIKd=9q#%XF_5BWlR;>Jq$fk>xwy&=iSu_ppiCC*1aCT@( zGOeHnkQjc^g7ZrSyh@7+m`^M>vJjF0s?HWFC^S`E3Q2pS6c#vobelWXj(y5~bh+Ap zOqY&PhE#me@N#Ul&NyGjnIE$Eme|KPntBe;geRDfw92i=@eLceR^QMj+c;QDDA%Z8fOLN*`EB3*OoQ^pWP_I7#@Va)_f7&DOhti0U2Op!4ty;<@{Gt0_6 zy~`xDFAekk9)`SfbR-yZ;?Td`u!h^~`B+dEJ(zu+Gs~rn1fR1vBhw(~fSxyS){A5mwdGXXNpsI7e0u*P(VUSz_sS*Xv zmG@+)vYwblzobkIlcxEEMrvgJD<}Qz9|MWLjjj+*yFms*6dc7eYK6GJe~bGV#*O?A zekhr3fGTKZ8R_{O5NE0iT04>wD~roQD@{;>dK!%fL_*ue8}JrlL1Yb( zo<^O>m&d6Nkh;}@)VLajEOBwVBKk_h({{B_SSr{#T$2%FPYnDl9kLdThf=bpM~KOXR~g04#tsEeKUO*fV-l?DY9nPkbBFQ|o*dv?; z20ly$H96W5wZkRVgd29T`}Z;VsXY!O=#j#wrHp?iQtUJ*c#IRAMu%p&SiHRW633-* z%>N2fF~m;zV0=VzQGTkaS3aZ?7+_G*It1(U^|rqd-&5gw5eDP>zV1n_#CagYr3XhG76YyRlHAdldQ!c89eM z5_&&5$O*7G1@c^Kk|!*)azT%^>>4knmC9sV;ALg;hb$BhU{x7Wo%O%x~}nZ#ved^FN?+pJ_*zp@I*~VD@2)o`RNfeZqs$ZXkqi4 zkUyC#?hfH}UyA5M&>>+WnmHAA%}KttLRu|XQXXRjS#_&$NIA%JeptI^a<8=+snSjQ@N zmNoaOFS>IsHeExCfOM>>O~(qYybDCbYC|Z;hj=|N&hk5Yg7p;(Q^|St)bS`y6O6qW zs%$Yfo?cxpdVB{DfCNDvR1uR~y*nMVOeSHAVwTokORKzaWQt=^bnrv2@5lv|Cxl`8 z>YMBi+KH7TN?2|2kuul*v-lILui0?qJ>qCpKOIi(cPvz%oU|Bf4cxHaAVcwlAS?o| z!IVY^oNb<4I0ZwVW88Et1)T$bXxlguXy3Q?3_SjMQUs=eA%h;W?(`jQ3U#7{+Nbyo zBKe*!V9k4MaG#QGE@EuFR(D(!RDWVR>+T=z*>hw5n015rg-U&svbc$7EwP;?bY$r9 zn=+(93vslmr9_c_YLm48`SO~JeoILbIEdDGz*^Z2u|oB2WCK#eEIfL}w&ED;0*4_O?bpCxu#X4VK%NAGq)M0X|Y z&o7_@ZZd7nr2=H!>wm8nD*;}@q>_=Q5#>%Hde#j0zkBo5CAKGOvi%3zMy)gu?_$b%?#Sl(j9hNE+s{~aXVA9 z6(oh?$1wF_USc8WC=r6&*I6S?4u6Ir-kJ0VbkiGQPTNvS9`5;KV|XqM2EwXuGymeo z8wP6&IQRi<)))3|V1@+&j%>W{44X;dc$Dof&QB~>D7tIl=q6}%{%3>CJ_-eQ9L2|<}i8IZY96 zP!F5gAi5o7l+6d7Hk9rSfq!+xOwi@}?UXQ}8Wd?}0`>Qp^TeMq4ndc{ z3?ztZd&eED3MdcR;{%o9USNfiPy!=e5So7-S^*%V7o0|K|13RglGDg0H|83Iin(8? zFNE6Askem4I{Zu-hGnp{yf6)jTP&oY4+;(<>||_{p%$6kDbH& zQOri?(DGduEyaXgE?!~S5>RO zC`W6V`vh3NGB5u;e)=KTo0qb6HcXzRy>7hJZk6W#fysL;hWeVfqkyq4y6AGFJzvk{ z5Q{>Ac8YarmC}3zT@((cLSjHr%-SmU=Pe3h?J=-GjxMJeE^IJCvYU;bW~{ zRVjQu;U0TPkk*O+T7KZ|#U?@}`*3Igl2Q{d#Kd$GW=0I(U0Kc${3+vITma4X&x#ZgpwbcKckfZZ>ahn2vZ8@q7O^ z--WTpIG|?ag@2xK%Zzzd<(;dla;heP*0B82ABo54&mX8$wf+#EQe|fn2s1C}tZXYN z7v(bT70AvKG7P4Y+AaMj*Z58*FC~W zzRvVzTL)8>rAQn$$p5vXzs~lbf)-S)ooiDDD@Z1_?g^|+b6%~EQXve@9KtpC{|z(y zhjv`Pc_21A!2kw||1g}{zf}q9olRHEZwuCcKp(PnYe%;vqm#(#4CJ5YM`evSx*0lS zv9K8~=!7tv;w}*(e-S?ky`Z|%zPT5Pi7?&TP&I=2$EvR3RGW<Ksi_aopzxCu2z2YVWl3JQzky$Rh(1` zqH+1V((>FiYtn*w+of2~U+@KN>5$2(hj?9tX6gYi8spzicKn86!L^(PA}7CHIuNU$Ct$8qlu9ZIS~ckRJ-8I7XH@ft;ryvg1A%3Y`&$iC`T#i*zry6-sq^(u{B_J3=B;ckuLf|0V{n@ zK}>~-<>_&X6T18~&DlZ%;cea^Llky4>;^6mW@mophyRT2!P^+c$*T2L(9GdVLof8u z+DvRZe9inAd0V5Y!M5c{Xp$VnUF8Z~M$#F8XA#O&LuJ{*t^f3eLZQ5B} zaad$>PV#3VzX{X*XyJcx9rM4g(|`h&?6LDu?fb5p-H(j-8KVPdJ z5NmVLBl|Kr(X9<=WO>Fc+hJiYg6`5Mx$wAeaB>z*2lupu-*ACCB2H+E9bd(o4 z5vYGQ7XK>miD>#rlbOgqQ`cLwM>!<_0i6($m|P;lju9`{2#~kY=Jq(oaB9wwQXCb%tqn@SuXl zt=o;u_bO76RbjN&Sk#+ z)N~U2Ic**uZ_gvmCt3A}6a28-1~;l=^|!&muxt z6Dtf-rsP;npwSzH4?#}m#S{0-pmF^qh-E*I7T3St%H35`#}c0tEt&mqfxgBZc{6?2 z*(hw%z;8M#M~<+nU31yWm-6jf+QB1@-%M*-<6w=ZK^SJ-AT#I;^HV_LUcmx%t=pjI zQV}vR9kS(}Rq9`4-oaMPV2^z~^9ve?nKt#~@q45k*G+_#vP3E!-3E#3>iHF<5j8o3 z^cJMu?6stYEPV_)j z>-M_PX?Pwc)9XYKM+EWSP_+c!V9mRg5ygSuLo+lBKqWaDR{d<+PP>kw@A3bv7SB)n z1!<4!Kd8Q5N-1fg71GZbcSqYCmIz;m8`iZuj>x_)qZ)nrrNY}T;*GrwXXoy;s~$8y zeA#&%9F~=asKbdt7txW-K(!EQBnY#p6jXB{dJbYNI-^Tg*#FHRhbZ}s=C6wnB&f#! zigh5;nS~Xe+Zi>W=!6tXqj=ro5dt-{hm$ak{2HS*b20pgR}x@rHBTb^x z(l!5a>?Kc9a%H2$& zp!%u2zho#ABOVlmfH%A|Wu2RA?O&huHCt%cc1 zGb$D~yOMQXUe`bt5Jqic>Pw?Mf73M2hRiNFA&Z26pMLCB@@IO@{N2Isac*>&>0avS zzR}UsrFKi`SBd%4zU$TYcbTCzMAln4YtJV)2PyiAwK9S11=SIfYY!RAH=YoI*nq1g zg)j`wI<#O0Jq89)C)KyJJ|;q9<%`s2pa*~K8pXG9h_;Kfqea{Bq2a(3OEu|v=D#4Z z{??S|e!<)Pz3Fy)!hYFUTOWLFSa!R9lst~9)#!Fvbh|&4f0R^RT(GTa`3P#Ygj)Z*(Af#q!O`|Bd^W6%W7z7Thzz>O zH!Rp}UMhOIUv8H*eMKdg9|S^a7smv>eJ!YZf3>u1abJA9SY+o3<>Ooj5`n11pEZP$ zt@6m<22W;S!dT-FHTsuv`+!ER8ymNHn)C3qJkGCv8FJ6WL28nBsMMlUsyBW+R&0>3xW&WZp02a)u(-rNO1hdaXJ`NQ!MyUxV%~S^Zurx9CF76rs_yz}+dXpe zRfTm?^5OG)sv32V2qWKRWNZaJ6~b{4-GVizc)rX)TKO3^Iw$(o_O$HpmyAti(nwGK z$Je3BfA$-mAGhD(Bg0Be1Zy%0wJ#csO4;EPz)XSi5-S%Lz=u@G{S*qonjkoUaI{qP zaD)UYMebr`C`x=+Pz6v%)Y+*TvGGb1P#c}vor=A*@wFh)k2x18^7eJG=wkEL?f%mK z(fFSShu8}gOYonSeZ9z%wr*yG+LK-SU#xsOzW&T%rCnA4dU7T=>y$MNY8&WNY^7%U z!sOb^mNVIHo~t~wZG#-ZZMSy;#$bj8d&;X9&GOep)zZs(iMKc4)2#wbLV4n%fhtO2*i(zTp`Zw)TwoIS)rk(y_m^5ki6?gmi#Cz zD;K7Eh6@yjbMh#i3YV#RlpLo<>KX=9w0fo~Mo)vlUZ@9*{bpFUQ|X>+;z97xiP)%A z@z6<#JNVzxqEYaS;MA>IqGN=M<~Y=KqSSS0z{Y7ifS-kYfr-7fG<{R@Q8{OtKHyS0 zt$-w9YE<{fS+{h`5SQ6-n7$GRvTRyKMTK-^R&eH^F?5hS7;Z|MoA(Xr=7$!~*zA`| zwJ+wWk1p1wk6yRigYfq#x;_3l7};(xJLV@&XBl1QSzg}|if2F@pg0`=B}kEWvBGrU zJqQ_PjU6s$8N{5;18nut0u=Q}^UlPI-O>F0y3inJotg{n$c{Wx2!MS1dN{jq(f#yT z__j&8Ja~C^zT;>wOu!G#s|@6He(laEHMkizhUVG&h9g9UFo2n;;)wpe*Kln!@y;D? zS#^JJ9x5KvMPxcHxy{P>$#LbHriFo^4`i0Qu^MPBKHMA@`Nh0?>E^VBg87GUlNL%J zPhZSSOA2Q(d;pNir5ak?9d7}oa>*Y=NF1<_{PY(uMyS$4fG9+#PpA)ff&sO`V}4#) zoiJhE6^51lo^E%&3{MRg4L({Y0aLEyGc|~`ps(ltQP5HZ6CAZ4O?R3FZ3z;XrM3UL za_*;5w5KI2^OeIpMR{j1O5EzHIHN*7HZ}EIu$_mf+7lD)@DBm|J{2XJ6WbS=4g4udV!b&RoVmc}?_qQUB{&r)_eph7KNjs0BsD{chqtx}z zcWr)=ohiI~OB$CMGjxTdX1nP>E6Qk21E`H9nZ=BS&UYdT;}(gLacV!FQdBubw_v7* zJoC7KUQ;zpf0vBgVqE-yWSDc#Rz?uQQQj$y@vFpus>btG%NnO;sg{X**jR0q6j}^3 zeg21v_qOB%TBe^+iffPQW!xVu&Q#PQp(R9F%_5XU+0Joq9QF^_aHM=1TOzlYP1 zw1Z=4y%82$eSy+Pcr5RG0%O17cY$2{skUmrYjj?eGD$ezgcDHy#gStKkk|p0q!yY)N@ALVD{VR^~<9H0~Thq;rCEWC5XWr>O5}2&AZ(<4kXq*8t~%spT3X!HBmWKUCN*t9AE2I+^<;`~GL-70lq$&R>Ytjd(gz)QL6b zpXuJoT<~rPToxUTI=*Hz{oY4g%X87z+nMb~$#<&w?{1!tTazUr4@CEAv&7mli1)Il zAf;yT{f6+>^s_JEvX64(4qz+ht7h}lk=Zr*>C5#|=j-NkOEuf{kwCp;vn(+nJm*#- zVg3VPk~CE>c-COss!5U|%sIiGc*m8M1DPHBouyPat&pGrk!JY%9X_vETV@&*$gBQj zGE8)nH#)!gW6K)^%`q~eET@)P%c1*CA9{g%dA&hVL72$_`SKm%{G-5sV%K9>WBw$Q5W5VmcEW z0sHTB4%h$egwFYRomQCo^>Xtc>|}%QDe~(fTev_-DPfwjR(oW{w7ii9tv%p`X}Kkd^upX|cs zCTMAK^D(QC;7P8Od}AqUw#BskAXA@Bsum1H#ucPEgp-OIm8Xu@Z#vF3X4^8x$oobd zd?Sv*5ymN&#|sR*+A0DqnPyg0Y+_H+#Ptep^rAB$h;+=CDs44*y`a4;uhcPwBXQ}1 zlPDnkd>)3XmN3SM?h0~^v*J0TO$Fmhx*hCSe>?1ERPCNoHz9EcDoe<;-bxhkR3cT99<6ChaoFt8D~P(r;Y!g&IzY7HAs$tXh6z)VHvJa&C{0(omvlAJ`BAhbRDacf>2K$ZC)qdsSKdB(ZdoI(Hd8~S54$U3 zV}v6sjla&$qT9A>%YM?@2WL^@qjQs>7ws{QqWNq{c$!W|SA$%jCdyU9aU?>MP&LbP zw8XQ}N~e*UJF&KhLUjBHGD}WK!PNOmBF?sRzSC));slGT4WNa|N%bP@ z==b=f=4`gi2PJd=w{#cA(H{B3X}ru^p<66f?OLEZux4Iu12!pS-{nL)-(hC+dXWBh zoW|!UX%Ioy=3`qCl&p3pkf)~7EA(OR0h9vZc(Q6#nFeW;xUF@wZ{v{R$z^lQ+s$F z$rh_wgtk}WWbl{PP6h=p0|=zM+MPfl=XSmWdT6I176MkAweWeCYpo?-i`7^AaI{D7 zonZIAci{Vd){y`79j>+Qzk|UbFaJGzark)uV=GS!`>!R-i3CRh=7GBYCn}c+O(Lea zV#MN=ymXJtF9vW4&bQVglWIgISgWoA-*ug0eqf=wnf$BI{LLgYpnl@dg_<| zTK)g|i^BQe^ZsM}pRGJC*8kU^Vv~0;(h72#BDq>&wAYwk;VK-|h1JAZUG3bt=(Ch| zqY-*>1V8EQay}l@Psr;PWjY1qsY)HFB1Qt35@zGeiG^~Y9WJfx&ajZy4M1-w#Y3;& zH^OrVqJA4i^fP3;$0LHgZ$0Gw?%jS+I7Qre9)?Tw#~g=rOi6%n9K&G!&Ih7ehw_Q+ zLqaZ6$6CnIY3<~h)}Zp#L+&|cgn*Pldm2@*-dxJEUN*&XRI}@!xrvo zX5aw4gLAM1UPHSY=O&eM>HU;^p5?+7ltO_Ot$&J-Q06?f}QVRhcN z1~5;BPHU>)HpI4C|EwiKWu+p~X!%vrVzr*Es4*L!MVKZ=i0nq2BC~SZFH4nFW!~#Z z2Znm`g&QB7t$@j97OT@09J8|22G}M=kK0vK)+q~lVF6$Eb!}EEZFO@jb>G{5`m7=U zDaYis&wmDohsF4h{m1y<+j&~Zf9dg$ns~izu2?q>fjCR0IZ(OO{_SO61a29*q{2cX zc~J41hrHP`{kiIZC)ZRDy>?z4q`v2GV|CX_osmdrOr~$^2*`L5!FwwdGMdl@iHuk| z7li8&Jn3#@U4ya{hOmhE=%q?J0#|txU5TmF0plCL_i?V%En3L$LX{yNUEg3HWCw6G zq{-3;2)?D!n5`EVeaDPn^$b`oOo;8ppJ7tP5r*+YrWiU#H$)|T7i2*=4)Zt_SY?k- zZ^z&Dd6r9~g)OXLp3Y0A?x|I}@0K9QY5F9;;pnZdSg)nF(9;+F@^qi=uHZ>gIC;u! z>#BZa@397EzkE+CZvK@;Z9p>&nBkWTp80j>nk*YFI}l-Nja5fQsJbTL$LKvSE!$aI zuF10eO3)k2<{%516$7f)GdRpyTa-QigyYya z3R|965|$H0qkw!`Rcki|y4rcFmfcJf_1umMwk0+y!IF|6!eFBww03>mwg z$H-gA<;ut=yDMUR>bXrDqvNzad#6&Hi2mXSIKfspb9eqv>I)ZyJ=tqH4of?zC~IAr z`HY4yo{?x|$1wfVil1lQXUI8e>y*{DXHrJQCpP6)c^B`Ru(#(HOXTlpm(Q}|= zpx6A|^j{`C9m`(GZU{$va$SEJ##4+2-LCrc&3r~U9ic%N?akx3Sn9LBvHiOf=+&le zC@)5o7FNEh3S>e)U8p0WmtpuLO+XL!e*N`>g$?BWcMpA*+k;3rdfv40OvVE#Ph%o0 z;jo;T&d1}Blrmp=kj%osC8FNUX9e(9G+FL8=m@8=O|sRFa5s8G$0+7xLD^hbySU~^ zBcXygEt<%us$`v1dT%1(hD8K3+TOM{rwxX#d3-kTtYiOA7{?Q`_Wh5+;Kj2?`~N*X z?eBlvcN8xb2_W$KEnQVLjf`Z0?2=&`o?}GHZE;qCrkBBUU1h3{{s1t7(Z_1scRrTf z+|FBb^rgT;H9LyG+vCaVN-IsIu!0tA8J~6X2bh7KBHf%>$?U9l8FlI5SMOKPzGrFg z_H}iYcu@{nn)Sc`qM!Hwd;YBd-Q)Vdji=T69|eR9gtbu+ z?){~{8ip`dIntC|6h`h%;gYRD84H^F#T2{9t>6Xw{US45WGi11hvtiYsk|(EfL&$J zU!vjg|10Cz`u@Mq`UU=f_Wbbi{_j?v7W}U+^U+8}fu$@00S;Q_Ogx^E#;pzI-{^-MgL-$tVc{ImR5&OEWq(&f}zSVQLK;^%(jnSR@z66Yo#3 z)cUf7y7c$u%NGxQMnf#fi)X0U3&@BCq+4P@`F>8r0Fh{c7MLsNNOj1JmG@=s{N-?= zCeKXBY}reXgX(7_*OXof$dFR%)x)-GfC=vu3-b3uRj>pWB4p(I zu(yiJ*0P`&RVDV2GA8<{TNTtIvny7%QgJ$)WE=39vzgg4)eP3o+{&Yj^uSd8)rzwU z4QCc5IU0SiW6hxNWvB0&1N9+{XH|x+CV4yj2zl0W#{aaKp3StU!l}L0POli|(2JGp zFeeL2Zj@t2lu)h@McEiV`9?g^Hbg`tWb&1%qxi}&h*==eIYB3A&z!Vmz_=7*eK;}I z%1mXcv2-ElFFTD>RiH(*!fvY0WepDH2+B@o5#zd~wpIcQ!J&VMA{O;@=_VvseQcLI zy}u^QflG&QX5W?)o&NGZAA0z*|Lhwl3&I8YLB2dbLHbW!%B3>9XnvA!cZBGd9t(Ay z6FrL@atEGC#%H%lvH&2cX+ZQ{O1K@$*Og}v^CpiilYGSrIrcLI30S( z3h3F@F~#!HJ3p%v+P~8%Kwgjk`fKp*?q@N@htFRedn(-6hnE*`&ffgv7+qweM*&I5 zC?NsblOnklIP}YxWBEpw`SPW^kF>B6rLnJ7Kp%CG9WD_ka+o9v)XgWnU^FOVTW7q8 zA?*f4peE6RauzAKX>iW0jzFz(L%1+OsOMnYNpKYObT2*GOV29Vv1}KQo5%kn&+XTL z%LV1&_YoEXM*+uWn?cR`|GYoQ=YRd~#k0rrzpXqi)_?7xL(r+2Y{c`a9@NE6xv&;e zd^GW4%E%~fu*x`O0>7nBR>q0f)fJjKX1Lzvx}0*x$Odadta73~GK>#w3UzAu5Iqg8 z>eqOGm3gN6sbgx^lesj5MOK$_iB=!IQu^Mp8gJzu*%Y~!_L*~iFVSVP78WMoeePO? z)1G$CYt)b`n`Hy0uV8w6ceutGg z(o4v6HV+dT7xLwpc1v6vZC}CMeXNxy(g#mFFXx|VNHJfc@jOy;l(uO@t`j<$CdSyt zc<#o}%EhggRm{!t70cB11JTaFopaI6+El!izhh(U-8oN$RZTW-Xj2of zIX4?hySX>3Tlu6npA6GZgITbRgw-=*2DSqCjaWE`51=i?)-@Q0|p4b1529NsR zR-W7Fe?Q5|e)avvwLe2d4@UPh1Fh8j;P&HsTA%6TZgjp9l(y3NOnu!Po+r~=D}A}P z%2hthM^l;*$THjnAgg%3rFk-~RvmNg$c({mQ>($rQ^)E;q(h-nI)o_X>img6Sl{i03m1?n5uv#_NG}4n(ykYIwfxsON0BIA- zZ7!u22(C;+b{!+_3@Z){-J*m0ri~B=(?uk2)C`gLx3`C{5(wC8IfE%;rY)}|z0{1P9Ii}f?_HJXyY1qt$%{XQdE$01} zCY-8P)->RlW*@lu#$Ha_@R%di-J$X34%y8a!a96r??`PJtD@e94RexzTg+@GW`C8l znt@+4qxLFUHe>et`!&OD%KpQm3A>&BXV8CkSUCSbI(p3iv6bgm_8&RfufG2ePA31k zc_Wafq=#b!vPWBO2~uFMX$!Kux+8;75xwmULc0D&3_@;O6$YWzW*oPP3NucvQ7Jd5 zYNOIaGYGxH2_9mRuOH~w+cGF!5_7|&Xi8GwYFm0aPo}RkTCv(m>7v361+QOMlUW;k zi48@KE_w&nFJ*(UL3og6F}>9N8<@pZ1)Is%={0Sq-WVq-D>vjEEO&2{%k5|n$|fgB z(!eB_ElYLAI9;*<(W~-3L6Tk?Qg(wDI1R|7?X9V>u{qlt2w%gEsErNazKu;lH~V$l z-t-_^%=>L^ZxyYqYkSkpK5*MxV+&1{1bvqlnhonYm1C$g(^w333p34~nnYKrBwvZ~ zY}F9fS^nP3ppuqKZGfrPnwGbf-1=6fKw#dA z%r;lkhvgHo!n#!6Td5nB@9ui{=sQ&`R*Knb^FgtAwd!2L8ymV}RD{B{8EFhz6WNLi z?ODub&$UjJbdjHr707PuI<%5F8_mGox)H4&*+VjF+)kDXSlgJQbxthNRkBIyjSU&~Rt{-t1cSJ7_ygM}2$NMyC)}newCsf#y4@ zKQJj*^h^k=z5JY7u$0@Wy6$jRD1()Kv~769<}hoBEh*-9MzIwQt!WptC+q7ljTJ<~ zmQzq6R^Fj?X0z%+;}PA->X8-wIX&&JUqL`tF0$qdx6?ea4DlQ$*=2kZw(JfcHe5*B0ZFs%2dy8+sSn6Z2WfOtJD$oTU{o=)E01NmA_@Tuo!eCS=dCn~0iYL*YmKo50ouI+9ZsVCK6+70T|I+sC ziw#x*v#7h<*Io;mtE=`xuR`{c9q5!0x7<7O(5@CgRBFSr?rn}u)5SjeYTeDVA^qRp z@(Naf8vXy-i=%$t|LgGZ@&4ylp4+JZS{asQK+PYLqNcATcPI~ReafP7zYlq(^8#O* z?gd_XRhoY4%%BCNUcaw}Rtm*t;b^WVi zdTbh)04KP-i)z1z22y>tQK?)(erg+Y)TMgr5j2_Q`9S(gZy?2+)&WPl`oS**e*k#q}y6p}=5fq;B9NGpFa)0$G{ebgN~6g>;v~cgUFdBy zdyk`_vx6csQC`w}$}pk5QPKb3_0iiXTp|`hO$ic;2}dD~h~M#FUH*P4H@VIZIt5BZ z?@upLK)LAn6Pg^rzY6@0Kl~RzfPc-4>EuBEWBwG2=pbuoh)37+7<3=edFqRsxbxH> z;_J>+Kbggyr~jw3gWh9K*<7HrSFc6Kk2(8DMoGt~0l^2V9A_Up{z8mcKn}hln?zgw zzj*!f)laYeSAXb0 z{F8(;_w{dkCbOK}4SX?$408twi|I%lgsSN8ns9bxnJ!qrC!Eb=xuhs!LLr;b5gL-@ zhR6j+1ET8rozBhX zlkvLI$@q@blz6%mJ1L(2EjxoAhgeKIyGVkM(vFvC9!ohjEd_Aln7%jtE(V=z8U@El zYVdUO6OKDGl3*!4OOO@7B`Oo72lvPGFjT&faGq^y&6Y|uLf$_~7&7#M@i6cd5IZ~Q zEK1TufSnF3(n=7CIa|H+nvF z3PPZU!u)s5CV^%+!V|*%PUm}PI-L$+#sB4p`HvO6ZKZAjX^pK4k}8wDoO1%U1VIxbI-Q;k?!X@$e%tA(@gDgDzptuD z9hXRaNQxk6%)*e}sPvJ;P~~%0=Ocy^@B-5izG=lzbFwB8aDExFn1Es!(D9gn&@RX* zgLDVI9FJ*46Dr~SEP;}{I1NZ75kNSypU>zOY&@VPqQ=i>^!ih*vTdG4@jR)o(D~e3 z{nUNmRJG5Y&*(~XJWes?M=T(|VB_QpeMTx#H^NB1K=PBu=~Ah`vd zvoF_py`5j>hpT5_u8s-ja-p|YVsoy%r}oNGNagxyt{%~Gn)2A{0Z{?=2SJjqFX_a; zQd=4_iQp^Y9B~$XWJ5obKCa5(#Bt17%qduN)1t83m=K4ds3unwiI$D;2+7!;11%iy`oF+@%*j>OGk>rN)>+>SGP2tonw1crV zoW_nN1!otd22P_xVAANfb$ww&}zIAP0w%{k+(Hv2o@BdngnG)3(^2|l%-K%{h2tmJ`nQFi7?LZmMSkg^h zeMQ3O5O2;$B?E}(VW@5bh^x{8yketk!W9jo3o@Z1;Y*}154^!MBG6tyVopXlA;GcS zppe@ICs9E7dDao4kw^#*y1AjJ%u+e}!G86(!)}HfHz3DV*}w}>-G2Bcs7ZE3OQa@F zvH3xktq@9kA9a!;H)p1p?dADdD$l0uA_!m55iy=ujCtw1a|Rw(L(DzB3d&Y~F}<1f zNH!O-gpP8?b7d;=DLWX3Y;theAN=*8|J}jhF9$fLJ*&3p(Wp1*4PG2r>*+E|@Tb)F zJR$;>;mHX0=Qx?ZTgK$d!qYjYQ6kL62`$;jx}foUO3%1SP)@`p;`;-CR^ds{-D!x0 zkh|nn6`p9WU>&}`N<6`;{v#feaGP@979}*B6hauMxr!3FRyoVGX$l}!p?p-Ub#L7j zc!VhQ-g?_#p*}-_Rr9vi)>|@jt0r!(eWz~f)Pb?$*AzA>^GaMsmbYbkjZ4ahS4O^( zCEhLw=QIF@Up;y5>9j0=%8A-;jD1^vT%tebIF#rP5RPL2qiDZr))|1U2~w4XLB-)b zv$QvwvPxRf(&uU}UsF{33PrS*&+3(HP0{Nsl-ycAHsLFWR+p($!7k6%+WU3&+!%qb z5Tdt4%gNtP*NiP!)XVu8ZV`I3W%|?Bg+Lj8?bQEmZ^tw!^d0yTwRjWMS3uu2S*C{E z6n)yw`I@vaD6fUSG>Ruk(f5g?RU@>}mze;So6S`T`cm(F#t|n8r({9EXOoT*4mrWW z5{+PI2z*AS->FK^gZy3?+(2#@{Lj zGir^cY$g(^o|i9_R)G?e>Us9{x4j*!Y=v1BDCk|R!a;6ImV3Zb+lIug2P|0GbeP0p z8p#FWj$04Weucns6c zeyUWAIr(Ewq9k0J$|FbVxZ1svyQb7eG}Zo`(0Uug-5&66jL;j{b?=bmHrBLFdnGsA z)UN*hoQ44+(SmXo&D6y;PQh~)1ax&Iij~OF(tSaZY}sAcHk*8L7`eN0sfiB`gt&5d z7OC^HkxA6u-PW{FBe9^2H&%AVxSn|Wj-O^MdYLcx5W6ox&jpGWj;YTt`^n}nv zsJPEKnYJ3nwG%i%{nbYJDjmiTWGM&nBNeuXt3YGQMPdy>LJSV`SYLh(^iUbsWwkyC z!ZL{AA&OYkGo|fe6ou+*vK+XB15vtcczV@(pWV5=g-h!FyQ@6ly?xuyEnIc)Uvjwb z{vXg|FDx}z;q5c92EiTflIR?{tWi)FKUyaQ#FgI7_Vsa<)a%o$+O9r-7NHRqU~5}u z-c9?~H5uiMG|5g|r|iaTo7Pt9qf4pGythNT{I<7a!W_UjyQpUV-1s6_W%Fl>R0rij zX9?nZZ^1}Ru@qpHZxtk@1edEcfJK}6+e}oOt7fQrkWbzG91iRWikU@X-Jgstb_+la5T}r2LiE3M>biq zxkA8P5Tv2dT9Y}*QfRuP^{8h4%97<>wVppKUE)TjusDBKey{tTLMv4NR%Wqg{&LZ; z(=&l~^XEi6Uuy=7GpAzL-tVMYku$f$bq!{2goVISz_BxXZD;P(w6dYKvp9b#ocBD3 zDID9H$GV+Q=W1Sa=d<&K-{JPBfW7bSPeHNW?GF_C512>R|0*id=6@Jy;OT4qQgDzt-qBukp zFepY-GQ+~Uk06`Mf=_VmB6ZwBC|SnjST;zbNe7`Io33NyS7nM@!R3^9I~6Eo;7Q&= zhU0u3scTlXli-Qn%)lSK z@CUts3@MI!gMN>{IO=rbc^Ik-XmyPOvd2q=RrBX*#WdnIm_?tw3~%sKJVA7v!69+> z1YFINC;KXPwIB%;KO4Vc$vG#2M9C8ck9If80K%N(Wd|Xe5EYGZHKZn(UbsZU03E4D z5F2OU8(06mEaRu+74LUC0U6FMbPrWv@B(VoSj-(bSgv4o3+XkHUnF&tPYK6}Mq|cj z%I#N0b`ka@sgU84wqHh|CgW&CbYnwu+}TOV(x$i_Cv=NTFFO+d6|=vCtm#MgOmudT z8oGu^2T<8T)~RF0SvnkiEuKsabyXf&XO_N=1c}E^N1v?1p=lj${$t973cQ#Bn4tG8 zoX?1R{JM|M#)kI1p0|(eZr(u58PC&!JOU~toB{<)iO`XX5+f*+3!4M6*Yi3ZJQ@)p zeqsTs1NSAAL^Bp-iN`A8b~?HsIQYP6Lf*mz2>P$u&MxV{NQ)l+@(*^rK!^c`n96kd0bh1eVdb`W3yfZl=g3ai_cW?f&RjBFvqP|Yaq zXq?6c$#|66gPkpP>(^xY6OLmV!Ls4BrQfH4oK%}y3b-aqbtqQmnJPchs0s)LzdFKM ziEI^9DFt;0*}(1~luDJdkV&xd16ii1l(>WeOTgxGrTU=nGeTrlBuFGE1hoTR1PRj) zO+=&g$ZQ`8HX|sZGa_UIH#h>OoXFY`c2lf~@^qljX`DudN8RO*=Hlm}z>WFJAzEogF)TF`zZa zQ7-e>aT9V(xthV=ynXlj(oa6Ab!x;S!9uc5p!j}&yP@LygIffPZx}7UHa*bVVdG(L z&c<>@8Z$nUJ0}zAsew)q5BIR;ng#c*2~kTK4hc_$Gi^F59xTRE(>5`8T{e-6(_0;DxRdf7`4lsrAgh^^-M#Dikc^TL zDx28d;Kd+fJR<4Uhm0CfkD-r(MH0Ld?@wQ@dA>!5Up%?MVL@IzL%m)=Ml2xRP8lKw zw?f3gM#Nwt{losg zj>>UpuA!kDED$IpQdt_)h>9r*vWtilnoQ?I2s0=NSx1B15eZJ_qQ}O)WJ-EE%4>HY zB~vQU41Z+Yj{Kpr4hcDIEzYD?tkD!l6CzX~KNV3?e$V0+YK{YSAz^ZfU_552n|~Hu zZ%lA9=d!6FP=WW

*!Icfa;HvIsy*ZmDGGPviK&PPc(TCix-u+@K}2J__Zp@wbyNE=H_DmF zqp~eAN!44W-rO){Dku%vQ7Jw^qSm7!yDEPWSs_X6^!U(6VB%sZ?UF}Z8^c%GcIR~! z794lO3CpR4R|Z*qIyiHYg&}*cuR7@ryC3j8S5=;ffh^e5A^dOM7XWtr=<$<{a*Wsy zhd*Di%k#k@#Q9{n1uBKq^Rx?Z( zhJf{|%>9b_YTs!?kAW(g0e-&BQC&~mU9jtbMY&R7iL0wDy7Lro--mt+-YntE*ut1X z@9k2_Eva^mDrBN}vB!a(>;X-7)DnMdft4INY8|9OA~H7b`-sLmu<);yC?%B>;96J9 zutx0^S5~BZ?!L124KnD{3QJ2$7q?l@o qQYfRLj!DpDy5)%h@^}lY4r4olfe(Zx%+wPerG3wc|J4fhG&GrKNfIdsuAOEmj|9Ei}J*!ITl z5Ofs1RI7#wg19Uu<1H^bFYSg{*H0iZQGfTa>`p&6G; zn&^-eWwe>=azTzA*aOA&PH%qzb;qQl5=SCJ&P}bmNs`;;Os(2Hi`^`^`cY*B$Wx7K zQ(#W2BZ+n$)|-WLs9AniC-d+r7Qa%nMsPID3$Qq9=W0SocM0Yiw}YwuR3PeOtP0wo zyq26RTy!FZR&fmKnrkFw+{->oXzjxLQWE$<&uYTD2dB4_(ChMCGYNkzxT|EXSKtWV z?c@ff8bP7O?8v3E2T@n}`VBFrh?BarVOlDlCLHhUUaAz5gZ=Ku?_v?z-4tmTAH)^h zY^DaBwPXX=^8io>?FvRmH-Bz?mEFbR{@e4T%b)La4d+i-@o$gJtG;eyQ3_*L7=Orf ztB1glFc*vGkdz1xGk1SbyK~y72}0!pQjUG3K8IN>b2e2U=;94WhZsiWHqXLyUNlRX z(yC=1b|aN<9UESM4geE>8lZn(OPLN&ELN{DnD4%eDvIsw_UT$JqVKY~f&>puqRa{u^?n(Do;n9AArvl;*P z(dcpVtlyWLiv*3Bn=Tb%9fPlmsB&l*%{~YF|(;%yYN+v{GG@($GVk&1#4TzJyf`@?q#SSqIy1nec~9Ce&k8zJ~D(>NPj!G+5#_5MkM& zrg%NqjD3Io9j)K@jNw%U(24jUlFB)sVe;uS*KQEDvxDEf>DPb!&f+{*A zablQY;9yvCH2yI@i^gpl<1C4ew$YtVY`Yt&kSBk->0p1fGlqwBbM7$yfFcRA?wKn} zp@XZe!m~08p|`48aGX?DbM6Faxf(@CchK73bv1QU;OQvagE0+hO82@)H(T|-?)iD* zS>}J~o=q}Ip$A{vHI9lV)9pJhlz=o6tLwpD>Ygk|9=Le*4x^=|Fck>*q6bcqCB}f#)Do+2;5-*lI)B!Qx=)M*(ZhR$|M3+r zR89J5QM$wn0BygW;6QntXKFgoW|jGG>l%OFSWX;lcHLXCebtQVJ+E)S-3v8*rwe!U zXE(MRcY+7?eVt(9CEoBx^GE>0EO~-jvloXX`MUL(H(SRd2bK`fF)c&Da*tPH zRWomI1kWSdcA`RH$k5f)xT5WdCy@32>&eNhL-yuy0+;DWRkGk9vq%R?U(xaD#oD`S zL?aW-B&2GhI6{JuJ%Yct1QdUpOF~x@)daMHUJqdOoI|fA>wCT-K0v^bQzhI)V3+i{ z3pqq|cxq*s7DPuc!Ur)lN=4wP64WXCK}ak1bpsq6IDh1^xB;uCC`~=f1kiSKqaqC! zO3^|Xx62`LPTboknx+^BcAR zf^#Z`AZNNDgm^rTkVknqB$eV317!>waWMZcdw;&(#*yU-!}YhWA_`WwNEZPTlqk7a zmV0QDve~9cek5g2wa1StfD8cHOk|`p6Qp2Ua|Pcez4!Fq!u!NoV#x&n7pd%OVWwRo zkQwWV6KDM$Yx8%@Mj(IARPH`eqq1A-ZXS;(W!)H6Eh0yTY13MWOqC^+XgBhm-A+Bb zQ3Fvx<*l;S@h6ALBadqwpMKM5zc|)oG#wWNP3|pT8dYmTs%u1zsq%g}5_|i{B-L`m zt{gJBFresfG10oL^)*QJ@X9tfm^U{@Aha=-?jGv2m`zo|Zi|1wCGWeQC_Erh>A0Nq z7!N4)4%sP~l@jNN7l)Su?-=JUW2-2%gZOh)@nM)GdW;cGDi!_hFKiWXc3G=;02-S> z7p}6B^=sH_bW926Jv5Z!tWt+Q*pxs&v%06m${byp82tcLesG8-h!l^F;?f!xz}2^g zGG%*n{7yw&MS*{!7o79Z8xs&n>8?76_J%B8a|5vF3}!CV#3M$WL|G})#JG7HFRHq| zjbO8hMFuMh$C|M-0K1c1b1*RJOFq?zBt?y`{WY!EOhk>w7&V((|@Fm4h5kC_@8pMN%6>; zceu+hD*%Yr%Zr!4oiNw?+{JL4n~L4gIvefEijmd>nY$d$eJi-9athU|4i3-G5BK*j z4-YE2eGh+|eeU1|_WfljRq6($ILLQDVD7S{n` z8qAG&a-7eM%Bw;2M+gCx&GJka&R+Jg%fe@-9=5>Qv52FL*+x|;DonaXj~n0S4nVzP zk1W}|<|;?h4E122A6sUF2*!u{f7p zmo0c4QGb%=5Rbzqu{Xr?;&~0ec(yefZf!?9+t0S1?L6E5>tCPshr|BPXy>o$S${Zq zF;LHkJKGIwU#k=2WrtrO1{!w;fi84>%#D;(%Uh$VzhTQ1{4`G0mr*QxsSddJj z1Sfw%GZYxsk;5JI+*YTr2(2m)K)JjuP!D#4f%1B*I?X>5`^QIzCzls1RcEax7f$6n zFS5K=g}!#h`jI%6k8`w55Kg_dwdHErPr^thAG&muWu&-!O&Sy598Mbz z#IM>MZ3SkUp&N7QfeN^usG(_`&I<8#TdaS{!lF^b3<@Cud>pYM;NH$kofp9wgzDTC z-hRpq!7d&JDfuxzzTMPflc*aP8glXvX84G*s zU>BXI#5DQM;bnXNjhD4U`WYzUK7xNV056K;^EYOZj$ggOXfZ_1)!rgeeX4*CKzMM4 zIedU`e?dj#K3ehU$SdXJlB6lQ19*?u1)<>*g_zazm=A>Fk+@8`5QH|vfoELuq@1WQ zA)5_aR;;j67U&Js-OwBo7g=sW*%@$klX60XP&jk@mpP_AOOa@uQ9g$aaD{)B08T)$ zzhYxPg)DlLu#yC1%KFF`Mo*k21R`>9cx=O%xk>KWLKmt;1!*owq%NkDz06{kmND`{ zzdoiXBba+Xu4d~VrJ|VVyzKRQL5ZgNzS0dg8;=zPj@xn@NNj**#M+Q0Hn;J-1=ipU zk}l)~pYOf4m0q!ks)uX96A!6lZNR{P5JAR#jcEjAyuji85VL|vbgdIv#);}C z`Al@b5{D3#OHpB2mg{%%6r3l==WjMpodCMRe74kRcQJ%gThAK#6T=26_Oqpb;a+Vp zqa}lRb=$#=RvgT$`d}z{uKN{x_oSe&Oe35Cx~TVsEo|_3Y8{tl;=MzDWM_>|mCT`l zHuj6iT+=w!xwyPMc151;E!+Ky0H+i$1E@P2jExKI#>z7~t<~@h3EkWk=qx;jY{zpo zgn$n5#>Kaqi^PBC6~MJuo?>%dwFZ}y|9sco7Byn1( zycCIPFVA#_4;3;B+bTf3rvD+#^H~aLQSd+XRTPg#_+R|u7a2w37c-DS=nDJAd+Qgk z(TN$y1Sd9`!XNmyp#L%RskAj#B>?*Kf6{kg1k5J)6X37Z3DGuadvivbdtlLGBEWNlb0 zmh$3(>yg+Y)M8a2u8+;=E@nwt^hAG4?71~9Fs2(cIqQ2Z^O*vFwgmxbx+n`2d-l)X z^u$w;qA;V7jWy5)E@d?l-%$xpxo{Yz7Xd5^uKCVJfEOPk{$t@eN@=u`O<=fPo26LJ z4eez$^b+zUON$i>_E(vL(7z729JR9CfeOX~V_-Z698s6_YGEMAo9nwkk89pd4`srM zqU_$IQ5SJk+x57AaMin2S`ajiEGK<8evP7Ug))LiP)<-UeHheKhIQ$Q$SlL7%05a5QNNEa-ep@~RMGmSF? zmp#|2^+5!gtU!DnU|+6DFV(OPySm~K8#wRjGHu%{NpR;7F`PhGtwS`-U> z2lbOxi4mEW+SlA&$r+g}l+)gO$XYd5}D%$<1hjM+1Z@$k#Us2*KC>ROGGqJKOXQp;%& z4k{)rMz30bca2*#rw=24>AI(biVbL11K(&t!erzUrEWA<8W6KgrvT7V6fE87!YQw; zE<;^fTe-uuwLvxb3#hX1Do1aaKP&YXJiG#mDk16iO3gdy>oPKMk;$QAtlMNx`f{l# z6eXF9VklFGM388GMOh}{3|-RThHv4oxFkTG3`iz_2pg5TovSn9SQH#wGDX=;?6`nj z^LV=8%2T6}2XaBnFq{v$o+X*t(5XaP$P_8ZV%C%@HB4rx(4-ek-PU_z%ak2@&@wvR z-9==(5ZgQd2BXuZOni7>^J)|^5TG?NlDo1}zqGd20irO?x`S)cu|8Z2qJU;QCYgR` z1A&)+8EuoWZxEx=WixjATb-T}vFXNz{7p)9bkNRdGq5m}EGcYQL^iV*1%e@$YB)(L zSOl??OB7c8RihPI_m^5H36y<6(TZ~^8a9d|;(6WDU{L{RmJ(0>yi?xV{j)bVhG;mi zdbZ7x>0*~%qPqSH25E7;_g{Y(ug(t-SAq_IsG$cy^3#nLSsqUzuB<>Oe4a4@;VYa8-bTh) zD+G2;2i=wXWJyHMdt4>aurjXk6c_cjsPH8=i18Dk<)c-qVUQfrWS;ayG7*zFOocRm zA(m#E$(%huP|6*08rV&;)?YXG>FHXc$8S&81IH_DUoBJKOr~iP%!}lBoU1YXfJbep zQ*+M)-}2BJ6hTS^@gflYXm;E~52S2Iw0l5`xGl`#uL+wfkt|q6Z%U?T3jk)+v&RW8 zF2Vsyb!rLEk(?u7W*t=M>CMUU(aCpzVl8;>;P80eMr-duktA({3SS++x%hVP_}I%y zgRc(0_F~_f$kJg8xC_?Ym%6CO7UrDXgrQZ2!y|?<_!=2;sGI=y1EtS<1$ivuBHx9U z-b+Fv#O}=sU?yTZpwKFoR?jQaS@Eqpc@sFC_Bj;EOFH*}>2D@F@vQ4cw8Tts z3g^)y0+y;7;=~0dD8aViAh4Ul%kszQTQs@>>=UIa5-frjC&jknVyIG?$GTo$>NK?) z!jWGTF6Jc}+ zIdt5dNHJACaS>1BMCQp{T)a6sIz73#JUu^jjpF z#yJK7=5n3VB0d)`pF_frUc_+>*76bRX)7L_e9>*fSt>x@X~gTU1&W^9>XLBPyb7sleLc zh%OHfkFE8V_>_iqjF0a)hXNK(# zWrC`Ek3#d0Hczb$99N)jgCIB+JE$8iP@rS~2?`^*Ds=7#S8IQIsm>ulH6pmXvR-7C z9iKkdu^AJsDW{Hah#SQb5(w8u0hk=!I|ZCK6&Ux?IL%13&ulohS-@h$e2soZ>{AwP*pSOtBVdB(} z*lxtjz0fs8a2xO<#YoR`Ty-(f@66hhea(z<+w9AK(>hUZR2un$_sx6vxjUIhHOT-` z{rvMQ7`$ld?H5Y@vF>sU&%$YQI;MM|)S{0XBM2cgTNunTN-sqqh%NL73JPlKNFS&K z+oC#cIwziGQK@|o3RPPpH_O`gx+vzwkS!m!I#FqnnVhR3sJBQJsjQu&nZ+u`ZsC(l z<(U0{!Ze(|U~8M*LKXqXVdxCRz|FZT+IFfN=`N0e#EsAFd7cr79gh|ti?k}BUY0$^ z;SFpezgHQs5Rt}3kVt7j7%K- zENdm67(jS$c7Lg%u00~80)ehu4k?nyMhz2K<^*|wLo1u>*^?ykVz#}?6say_5&@|| z_!M&qR*;y)-r3pV$$>aMIsUtNeR^=XOlEpizq2@g)R{WX_RXeQVZ#O?nUm{ABYvBI zkyGtv`VRe=3LAR%8iI_GsfdeW=9QQvQGp`WJZ(@la<}A;CtO59s_QNYIHfIFGBc@I5kY`6(y)iqjW(~I%d|W9NpDJg$n^p zj1ahZP0T6eLKM*~Cp48xYK!9|)-@}CODRTB%LffF)SQE^U`rj&2z17E7fLN4_8r(a zdTIfQ&n;5yY7s@boXRpDVwNb%|B|g>{tkd^RpXSyd^a1cL=)jfinqbX)^HsYji4zghDv0_atq3ThG>QeIm}du6GN@@o1# zwcCDy+m6cU%<3h|n$8T@@x(=cO1r(0sJqz7!{Z@uCcckWsDUcgQ3IqFbcR{q1TVe->y;gqvMZB^fWaL`T*8KGnNa<`Nc(;y4q^GCIBR zY|O6w7*w4Fq~ljU{5=&U+9lS3(L{*7f~!e3b$_oDJ?@DM(E2Xq$kn5N!S@O^r6&@F&!)20>N#P=O(~ z#~V6$jI~ZIWVWJntHBHLCG^ORo8kprE32=n87Axu0@H#v8Bb1JN7R zGtwp30e8qwii?(0Mlsg53_UvmJA*u1@Y|Lep(mM;e7;@*QL-BND1H}Ii!%}mD+s^_ z!0YP`8`l%;!;W)*`i@K(3lsG$_xE~>bU$B1=+;)%Kc5nZ!_&rqRU zl>B`g%?$;1fcV4<0-htZW>DON4VjEnyU|!;ktMOOzq#1^{?Kgo*Wa8U{&0SDc}Nln zzA~f_-i3M)+u8!+u|2NLQExFa#hy1fLkC+Z{1NOjj+&aO_ocy^V?RQ6`2Mp2x9 zYZ1+QRfD&GbP%xE0u^_mOCL(jjR|ODYJnWgQ%-IVXzs@6Z(KH2jB|Zca2T<3w>h(1 z198R$BuK?*0w{KEs3O(;5Y<#aoAiy+i`#rC$XeQ2t;Hk5UHH$5EJ&>|x}6vH3L^W4 z-;GD>pg5FPtS4ZRnm!m80^E399RTBQR8-A<#^SPnN2A)8?nKy%sT4@tw2WM{$JXhh zygwE7#9m?x82a^T7$H*HM23Rv`38%MF`d|SsZu<>EWY@-KNvS?FPN|uuatvtl;x;D z*B&PpgV=*Pf*c1|mK4cUF~i!4gx20)RIaO4w=6cYrF%0(YKWK58hi1kpOv~R^@xvO z*XyN!zqQr;%rs81%7_+hhUK1&Ihnqu^g(CX?;`Sx!nKe`q+!s7=b z;8>LwOm%=oB}!chO~A_lO5&Jv;9B7zNVt?O(;%IiT{5{WCaTy7JVW3j2Na}dFj1?V z3rH}hcmhHs_tdDDuuF=o-HSEnd9$hB{vc_8Okf-c#{)Kp0q9b#wgLx0T{r{5dXrve zL~a$s-KgGi?0`)NqxFglXP_oqI zYH2U^Tjn|;>~Qw))~-N9zh>xN*R&H&=Vqg&*Q-A|qB3XquE$21i4{UkWAq(JxFW!R z)z76z(T~kx&)2R1M;(3MWw+DlQXNDM()I^?#lktrz@u5A3I{;tg!9&x3HA|ihCHW( zkyL}da)!AMsv@Av7~aqkvl4|Uj`Y`j*Z86oLU@PPZHKg6p$uE-*<&Myo@ZI<$&Yaq zC92kk4`)*+?OuQ*fQZoC5`KRC!7>`*h2kl^ghC5E< zUz8UGZ3jUo(pnNa=Z6PJ7ae=QRvEmE~#_zf+M7Ioe=&J^-yKZ1>a=lbnnb zx0|y`n`PTVfuq53Zm|gXHp=347eWR zB#9e~y&l;pM@Sc_G}R1+0?ZA6VmfRWp>;Y=y@ssVYCB=v9O%L(4KNmo6;}uvge$a# z2e?ih6gbV6aT=L=H&lvtxMh-X{M|XhJrLg=QH1Wrw{I>FPJcKNC#Rr}hNzs3dqtO1 zY}B=&h(BbQ+S<%KEUdHXCSi-u&YE4AX$xL?5Zgvq)V`t@BQ35!F*gt-Aad~*Y z^0_;KfFN5qp5rx8lp8322G|#p9@$T3hU5NCkk=tnwYVra0ElBg#HZ=Gp-h8d2s$dV z(mTXhv$W_n;C%VYe$Rz0XDLRrlr_h_&!XwAE<1j_OX`pklD+OG5bMofg?@F=*75S? z%ffkou$hc@q77ICRUTnbpPUNE$OVEJ)?Ea(LGv<2g} ztk$JP8fnFDIr6vZQOWP|O6FV1U~Co+Sw1M z#4M6u=3TtDsW=XQ#|)S+mf6EXm>l%Rkf7dpL)O7-VV>ynjQZZ07kf628P1>?6NG%B z*n=q9pwN4#$>x~HBZy`;%gLq|t*;6&_Ufco5Z+mK3qbM!zKnH8y*kZdAQ&i-atLW$ zKvLEUV|wnOLN!($3-}z?7zVMMV4FFbvX<3w$^*kUwd z!34Fw!O1|FCIZDW5}6gg1<|z!W!}Bw6+!)RI|_fNlQ_K+zPkg&7JpNZTGl4Er*ES| zUKl^b`_J3wS~OQ|)uwi7P1K$3j$PPUidP8&1_w!hNV47`ojXtQ^!UeyXK`)ZXK$rk z1|+q`OCA?CEm*8+#9BSmJWt9m!#-^L6nT_^E5?rOOXxDvxytR7CNf zoM!{pWUqs`@VpeRFiT6XPFn46(b+37;TjGW8iv}2Wll)n>&sEyvTaA!4fzp(N=q!R zl*v_pb=kd(j543W1pU-?TnVW`e1^6OgPjp!M03ZOOpi#m$k^%>DlHX|s|sGgj8>F` zr@(~Jo92sfo=2@x0002f9LYAFfL91Nhw2#WFkroD!er-~xr?_NBG7QedL*`BKEnz6 z!8W+AxzDl59bDbP8bU;_#xjo}k=*B;#G)X7AqvlyUkhg%M1ib%S&j22wZ8FMSjg~M zc*Cp8aQ})u^}oOz?hPQnM%5X-(+1_DF;kJnROA>`4X8HMip`ucmfP3H z2LC(Ji_qpdC4ps1EXM4DWKlYak{CjS z7YHzhp1oBZ%e2Li6xv>#`w<=8fOs~TjmK=ev;gvsx)DsQ4h6tW!49#@bkJ{qjeXt^ zj1XwQ8bYa{bZo71T-hK}rMY!VuPUccCNecgY$A$8qtW3S)Wx+_d(iPIMo)tY3v+?i z16Q8Wt4OM3ei36SLv+u94Q$O9VgQj6{iU{hA27fbj-?T;yW%_4!$1x76#RhZ2Bs>*pOP*q7AT$mYbg}M4>f*l#SN#W-4?d4MN0|c*PHt@tpzJ zuUR!bnH!F$^1Xp-so7Dxoovx#^Hmu@6LH)Ja5SbY9Cv7-inJ*7hC2q{z`A68>5gVe z(uKF>*cwM{8bPYtZncKDeS?(e*df2M@8csus_^(L=$iSQaJ8KzX$ggYcntZf=`X&G zLc|1j1F>HpwFJxb5xdUQx#JPm9gpm?HOGt)CkWCQEucZ*r1mYpdSn%UY7fNFp#reW z3bJRe#En9-f*5(o%+wC@$-?u2YEZ%K;qD|MCmrnb5-pZaQr)#n?#E&;+C(ZtN=8(T zCU5l3mAM>^;vossPL`~H@C=NlGwgy3aTio29ug-9Dv6a5P2y7Mo3wC8b>Zf~;knV6 zd)^a)N1z}_cW#3Syi5?@8?vzzjXV6m;T-FQl#;0sSf!PS091m&x~(v2{mZGc{7{c9 z4zy$_bliDw0j%wf>$qbKId3>s_4njZT>rSC)Dh)b%nB7OK^R(pNzM=m`JO7PY*3n| zgAs(#amYQz*avn+ka?T87s7r)(ts>kNDk?Vz3?zo4h_&j->ly@wp=mA0l_5)9H?M~0&ya?JP~CnbUzEfb(=)U1trDLGo8<-aG+TThr3kY zq$)2aaprVlvfl-Nw^8#UgoA`BbzMW2T{se6#=h@!P~|KOq%0pNkX++HEqXWuP#ur5 zy*se1=5E+Ba7$CWtdr*8bpN}<^UagJOY6vJea)K-C$~o`OZ1%Jn3`lA8AZ9#H^IUM zqMJEtg~f*9S>a<;KksDkvReXJt%Z=moCovcNar^)kBrEF1J<5-1`(rlC|FR{!z-f$ z+5CCJl2Umsq_YYx6V8H#aW3cX6%m3s7sK@8p}oL;ibrk$SQfwou2REljU=DV21z`$ zYZG(T0dP`UYl?5BM#?W3+lWhZ83AIbGxD#?ypoywxg*as)w6V{x^dS2TV_F!n|>Y4 z3hxPa44K(~#VG=6I(CO;48a?LbKKxWTGIkW12E~Mahw_)%nEo0{k48+MAXVsT)bl9 z!d50qMlK)-8j!;)WrYrgZA3yqvaHm5bg^AxDiLIWH^=j!P-YM-owi3{aL=E<_-oq(b_UO%ezBs{{Ik!*#qsG6 zP-Jp{`Lmz-nQurk7m2>%a+s2xr4SSER7>UVTry49)7{-BTuM*l})2-uln9L$H z$OA-!6@95OtVS=BtJqK0L}Ch{p(!xMH)U*CHc(PdTnK3xxc_`GLw4P)u~5d=LuLXT z?8^^&Q4qkVp(9KLiEZ^!WK=RGa?G9U8F&eQ)G8=STatFFBqXy}u9&~q0FFoqzJX1` zCA!f@;FMEkB$8cBhJ$2AKbrT(%N7XtX9HuChPigOeob5Myp5!j+Uv@~^qBLAh0>fq z0_SK4#r;yT)1bgH&!xxlLmpNp#(hE!wh|%56r~P($W`p7r1pNEDYWF!i#CcUNc?nv zHzWmEsdrh9P`b6nD5(0`))qYY^DMrWiGxcmwU^B7XB&C3G-OO86IBsxBKaJwC&HSd zAhsOTQd&KYMkhU@G)I~mQVgIK>v7loUjdbEy5LIjOMumj+M)m*+j_R*KPlKi-bfo_ zQQE>$#cZl7;oU72aDl6GBvU}HhGp4*vbf>c>osqe@xSA}%fplXzl+zWCr6j3=SyCw zb3o3-AsB85S(^Ohl97M~4{rl42|F1e=>q0dLnrVM=70F2)et(E{l#gB=?eAS4ot~`7 zH(a_#s!Joip3MfE7qfw@t_jKu88X-pF%8In(?gKp{9l{|a|H)Hd2o2_j<^xU%{)3Itvg-~bu+&M#+BUKFhU7a z4Mi|nf!k?e<~Y+5h7$JKo3D?LF1{6S-|`&(*Vfy&yCV(IkxdW56yc#rC1RvQXrg*0 zU@B;4^FWAodGXb>#c~RN204#UKmkr!fOU}rFD2^YaimgA12=b{MI{+dB)G=&O7zHg z=6}U^;Wlk}3j`OYw+LTGgRkDcr6745uye@GV0gl!W^kFBn;Anh->6jO@z4cWucbQe zmh;RZYObT44hYMLRl7l8up6S+v+X^G_AW5Wzk(Aj68P~rF3Q?}PX)fG>c33jmv3b; zsr~c|zN8^`w`h3x9XxPI;P1#OchFO-tg+PJ5@x!q}86t+XGGInGYUQ(^>S$wkh1j*9&vIh1V5QM~3``U(Y?RnYbwDT~hTCHq+iVI4X4<1l zVgG*RY2Q)bz=Z`?zM;#$GIlm3c&73sF3{{TrX$A(w>^P>JBa+F4<7ndP7zT8uxlnj(SUtF^cjBQ&!{-qv? z?+m3KZW!f%i%$t%iIGZTbl^f8EsN0UtfUo*Z%)s?Jvnr>(hXX>0awhH zL=4hMzR}qP!+`;J5Zrc7M2~%7v-4N`qW|pabFpTGnPPWy^XBHJ7prpA)A@Ka9}Ug_ znJ0SXyRrfBtkM_66at6NbT&(*&9E^Np?LDwt%4JO6hc%qC*hliZ`8nlJGoM-sa4>cBT~cr zoR%ilH!_m*;y=U>DoO0O;4c0g@uWT%QA}c$)XgFhRZGa*Y@u)%_-ELQW zd-{Vox)eX0o_}}oALg6dQ9=r(hHd5%S=&$39JgCl^xY_2OFAK zDidw@ee&_a+H>?;XQC5@yJi9Xq$NLpKoH?+F-4)>e;sRo68tkDiP(fq^bfQp$QikV z@0oF2PG$qZ>>~WG%`tSgojaqrcYZDo_urf!UH+X*#@CJzZB&*>*qdy|$|B`pTmA#) zE_Bl-bHZZ>t&`k>?k=L+g4Y?8=NB_P+1IfDc!eFf@aWx&)GK7mvj* zbULRVjxVO#x{8E+)p}L!;Uj0|#AwySTA9=fhlKTnqIJw7Jn;h94DlL0A0j524XT$` zAg}U9&dLla4lZ;$em<%Kbqd0Nd#VHQjdco|3Oh!6wCS8HmLU*(2jB0V>>nO5*ztnec#236C4TusK&AN^TRxwO?Svv^kN*1Uw4A#Fq2vD4pfegJL4 z&!5>!%rV~$0MX3LhQ-$~($8jpBa}J;+f|Hws=i1z`#$q?F+n4QCI?D!f?&P8VH--M_|J4j#+d-%;@$vK-qN+R&;lE%{%b?u6X`Td^Zr{ z%WfC8)Qc0t;!1ee-st>)3ZNahjc0xvPq*9J;K!f(kN2P3$Fp1yOh)*bnwYqsoSmk@br+NS&d7s8pFNO0r82j)H$I zfWo@l;iy^P05PB4cG(iHXjFXOg_(JcBAI)z+b-#j)Li%s7*W-Ww=mEZ%|KQ^r0>7`O zKluyyHxZ|47$C(z+A?Xb`yW&Fu3RvTELYb*)rYe!K#YZd^OU=~DIyou`1y^was54Z_%^fs0JT6W}E~EIJ=x?!c1+q6> zOV6Jn_HI0XPE}-I^toDF-3j74=6@-q0b&psvBAMDvV@_^z+i&XUR>Dq-~!bMRLiSF zK?MrgSe%yTpZ@L7D{O1bK{X?kkJ1RX1>!!~ncxWlF2di516GOUAl(LG0DXOK{|Z}q zByxy&6d&X4G<=OvQ9tM1Fyk4h1Y_a?*>oD01nl&G3$H*`0P1gl(fmMF0Qz>Gsr*=v zkM$Vp_Y@F!g~fu&TsDW|22cTE?o<9O%a!B%D7NyeypfWU~BXb;ufj?k6b3!&Dvu>iU zt)CA(fu+WI7wdQ;yap<^QCqODz>+ahJS=;|b*Ch8$Vno9nY$70;t&nZxj2-$oF#AcAmig@~hsA)L7l$icWqD;HLO1IMuB zsS3D0p*XXyhM|#e*b6IE__B|`{rxQ}9_ZJ-XsG3ib@&rrQ@7<2RvndSxEbY2p`RN@ z3@p33y0)!pzu znKdnPL!W-+D%nZHdLM+~MGT(d4k3ED0ucn^OR;C1uy_$@O+k3g5CMwlA9P~^RW-a8 zXdElri#6$P>8Vcn>(~Mtgt2mj58+~pDM-w#>+ZQ%ItB8sAliVf+oI% z1^h~UiAUUg_m%jv(4#Vc{DVU}D*`yt1mKZORTQIp+R-hPxgsPd*MkR)3Yb}OAOzdK zef(5}=mNK~{f=qE#V}a;$FhH95^I#R4`t{qDndhv&iXi?Zi}fJOOygw#0|#i^d$G- zW)*?ScalJqPOwJa;<1Q0s;br@-=I|phKB+_hRKf zeXVi;atN$?Qvxw<1AX#>eJZDqxfbJ-a|kHw@XO;0m(dn;5MYVGDS0s$qWDCB3Lfr7G`9wqN?KjwI;0)NS#lSqiN#7E)>fN z*M@g{*`m_RjO^04gJh=SyFrReQQU9f&XR4K`#WbsNcHZjX!{ z4rkLDgdBOvN~jOc5qXK|5eq`b@0rhao z6oZ)!IL9fka|tmSVGXhYAi3K_9ZbRZPNYBEMYZ

2wch=+108p-(XR0`6ljEFQV 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%0DJ23iFQ9OBxz6F$j3FA9`s9&k8QOM@4-@|wnYYA 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, ``, etc with your values): + +```bash +helm install jaeger jaegertracing/jaeger \ + --set provisionDataStore.cassandra=false \ + --set storage.cassandra.host= \ + --set storage.cassandra.port= \ + --set storage.cassandra.user= \ + --set storage.cassandra.password= +``` + +## Installing the Chart using an Existing Cassandra Cluster with TLS + +If you already have an existing running Cassandra cluster with TLS, you can configure the chart as follows to use it as your backing store: + +Content of the `values.yaml` file: + +```YAML +storage: + type: cassandra + cassandra: + host: + port: + user: + password: + tls: + enabled: true + secretName: cassandra-tls-secret + +provisionDataStore: + cassandra: false +``` + +Content of the `jaeger-tls-cassandra-secret.yaml` file: + +```YAML +apiVersion: v1 +kind: Secret +metadata: + name: cassandra-tls-secret +data: + commonName: + ca-cert.pem: | + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + client-cert.pem: | + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + client-key.pem: | + -----BEGIN RSA PRIVATE KEY----- + -----END RSA PRIVATE KEY----- + cqlshrc: | + [ssl] + certfile = ~/.cassandra/ca-cert.pem + userkey = ~/.cassandra/client-key.pem + usercert = ~/.cassandra/client-cert.pem + +``` + +```bash +kubectl apply -f jaeger-tls-cassandra-secret.yaml +helm install jaeger jaegertracing/jaeger --values values.yaml +``` + +## Installing the Chart using a New ElasticSearch Cluster + +To install the chart with the release name `jaeger` using a new ElasticSearch cluster instead of Cassandra (default), run the following command: + +```bash +helm install jaeger jaegertracing/jaeger \ + --set provisionDataStore.cassandra=false \ + --set provisionDataStore.elasticsearch=true \ + --set storage.type=elasticsearch +``` + +## Installing the Chart using an Existing Elasticsearch Cluster + +A release can be configured as follows to use an existing ElasticSearch cluster as it as the storage backend: + +```bash +helm install jaeger jaegertracing/jaeger \ + --set provisionDataStore.cassandra=false \ + --set storage.type=elasticsearch \ + --set storage.elasticsearch.host= \ + --set storage.elasticsearch.port= \ + --set storage.elasticsearch.user= \ + --set storage.elasticsearch.password= +``` + +## Installing the Chart using an Existing ElasticSearch Cluster with TLS + +If you already have an existing running ElasticSearch cluster with TLS, you can configure the chart as follows to use it as your backing store: + +Content of the `jaeger-values.yaml` file: + +```YAML +storage: + type: elasticsearch + elasticsearch: + host: + port: + scheme: https + user: + password: +provisionDataStore: + cassandra: false + elasticsearch: false +query: + cmdlineParams: + es.tls.ca: "/tls/es.pem" + extraConfigmapMounts: + - name: jaeger-tls + mountPath: /tls + subPath: "" + configMap: jaeger-tls + readOnly: true +collector: + cmdlineParams: + es.tls.ca: "/tls/es.pem" + extraConfigmapMounts: + - name: jaeger-tls + mountPath: /tls + subPath: "" + configMap: jaeger-tls + readOnly: true +spark: + enabled: true + cmdlineParams: + java.opts: "-Djavax.net.ssl.trustStore=/tls/trust.store -Djavax.net.ssl.trustStorePassword=changeit" + extraConfigmapMounts: + - name: jaeger-tls + mountPath: /tls + subPath: "" + configMap: jaeger-tls + readOnly: true + +``` + +Generate configmap jaeger-tls: + +```bash +keytool -import -trustcacerts -keystore trust.store -storepass changeit -alias es-root -file es.pem +kubectl create configmap jaeger-tls --from-file=trust.store --from-file=es.pem +``` + +```bash +helm install jaeger jaegertracing/jaeger --values jaeger-values.yaml +``` + +## Installing the Chart with Ingester enabled + +The architecture illustrated below can be achieved by enabling the ingester component. When enabled, Cassandra or Elasticsearch (depending on the configured values) now becomes the ingester's storage backend, whereas Kafka becomes the storage backend of the collector service. + +![Jaeger with Ingester](https://www.jaegertracing.io/img/architecture-v2.png) + +## Installing the Chart with Ingester enabled using a New Kafka Cluster + +To provision a new Kafka cluster along with jaeger-ingester: + +```bash +helm install jaeger jaegertracing/jaeger \ + --set provisionDataStore.kafka=true \ + --set ingester.enabled=true +``` + +## Installing the Chart with Ingester using an existing Kafka Cluster + +You can use an exisiting Kafka cluster with jaeger too + +```bash +helm install jaeger jaegertracing/jaeger \ + --set ingester.enabled=true \ + --set storage.kafka.brokers={,} \ + --set storage.kafka.topic= +``` + +## Configuration + +The following table lists the configurable parameters of the Jaeger chart and their default values. + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `.cmdlineParams` | Additional command line parameters | `nil` | +| `.extraEnv` | Additional environment variables | [] | +| `.nodeSelector` | Node selector | {} | +| `.tolerations` | Node tolerations | [] | +| `.affinity` | Affinity | {} | +| `.podAnnotations` | Pod annotations | `nil` | +| `.podSecurityContext` | Pod security context | {} | +| `.securityContext` | Container security context | {} | +| `.serviceAccount.create` | Create service account | `true` | +| `.serviceAccount.name` | The name of the ServiceAccount to use. If not set and create is true, a name is generated using the fullname template | `nil` | +| `.serviceMonitor.enabled` | Create serviceMonitor | `false` | +| `.serviceMonitor.additionalLabels` | Add additional labels to serviceMonitor | {} | +| `agent.annotations` | Annotations for Agent | `nil` | +| `agent.dnsPolicy` | Configure DNS policy for agents | `ClusterFirst` | +| `agent.service.annotations` | Annotations for Agent SVC | `nil` | +| `agent.service.binaryPort` | jaeger.thrift over binary thrift | `6832` | +| `agent.service.compactPort` | jaeger.thrift over compact thrift| `6831` | +| `agent.image` | Image for Jaeger Agent | `jaegertracing/jaeger-agent` | +| `agent.imagePullSecrets` | Secret to pull the Image for Jaeger Agent | `[]` | +| `agent.pullPolicy` | Agent image pullPolicy | `IfNotPresent` | +| `agent.service.loadBalancerSourceRanges` | list of IP CIDRs allowed access to load balancer (if supported) | `[]` | +| `agent.service.annotations` | Annotations for Agent SVC | `nil` | +| `agent.service.binaryPort` | jaeger.thrift over binary thrift | `6832` | +| `agent.service.compactPort` | jaeger.thrift over compact thrift | `6831` | +| `agent.service.zipkinThriftPort` | zipkin.thrift over compact thrift | `5775` | +| `agent.extraConfigmapMounts` | Additional agent configMap mounts | `[]` | +| `agent.extraSecretMounts` | Additional agent secret mounts | `[]` | +| `agent.useHostNetwork` | Enable hostNetwork for agents | `false` | +| `agent.priorityClassName` | Priority class name for the agent pods | `nil` | +| `collector.autoscaling.enabled` | Enable horizontal pod autoscaling | `false` | +| `collector.autoscaling.minReplicas` | Minimum replicas | 2 | +| `collector.autoscaling.maxReplicas` | Maximum replicas | 10 | +| `collector.autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization | 80 | +| `collector.autoscaling.targetMemoryUtilizationPercentage` | Target memory utilization | `nil` | +| `collector.image` | Image for jaeger collector | `jaegertracing/jaeger-collector` | +| `collector.imagePullSecrets` | Secret to pull the Image for Jaeger Collector | `[]` | +| `collector.pullPolicy` | Collector image pullPolicy | `IfNotPresent` | +| `collector.service.annotations` | Annotations for Collector SVC | `nil` | +| `collector.service.grpc.port` | Jaeger Agent port for model.proto | `14250` | +| `collector.service.http.port` | Client port for HTTP thrift | `14268` | +| `collector.service.loadBalancerSourceRanges` | list of IP CIDRs allowed access to load balancer (if supported) | `[]` | +| `collector.service.type` | Service type | `ClusterIP` | +| `collector.service.zipkin.port` | Zipkin port for JSON/thrift HTTP | `nil` | +| `collector.extraConfigmapMounts` | Additional collector configMap mounts | `[]` | +| `collector.extraSecretMounts` | Additional collector secret mounts | `[]` | +| `collector.samplingConfig` | [Sampling strategies json file](https://www.jaegertracing.io/docs/latest/sampling/#collector-sampling-configuration) | `nil` | +| `collector.priorityClassName` | Priority class name for the collector pods | `nil` | +| `ingester.enabled` | Enable ingester component, collectors will write to Kafka | `false` | +| `ingester.autoscaling.enabled` | Enable horizontal pod autoscaling | `false` | +| `ingester.autoscaling.minReplicas` | Minimum replicas | 2 | +| `ingester.autoscaling.maxReplicas` | Maximum replicas | 10 | +| `ingester.autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization | 80 | +| `ingester.autoscaling.targetMemoryUtilizationPercentage` | Target memory utilization | `nil` | +| `ingester.service.annotations` | Annotations for Ingester SVC | `nil` | +| `ingester.image` | Image for jaeger Ingester | `jaegertracing/jaeger-ingester` | +| `ingester.imagePullSecrets` | Secret to pull the Image for Jaeger Ingester | `[]` | +| `ingester.pullPolicy` | Ingester image pullPolicy | `IfNotPresent` | +| `ingester.service.annotations` | Annotations for Ingester SVC | `nil` | +| `ingester.service.loadBalancerSourceRanges` | list of IP CIDRs allowed access to load balancer (if supported) | `[]` | +| `ingester.service.type` | Service type | `ClusterIP` | +| `ingester.extraConfigmapMounts` | Additional Ingester configMap mounts | `[]` | +| `ingester.extraSecretMounts` | Additional Ingester secret mounts | `[]` | +| `fullnameOverride` | Override full name | `nil` | +| `hotrod.enabled` | Enables the Hotrod demo app | `false` | +| `hotrod.service.loadBalancerSourceRanges` | list of IP CIDRs allowed access to load balancer (if supported) | `[]` | +| `hotrod.image.pullSecrets` | Secret to pull the Image for the Hotrod demo app | `[]` | +| `nameOverride` | Override name| `nil` | +| `provisionDataStore.cassandra` | Provision Cassandra Data Store| `true` | +| `provisionDataStore.elasticsearch` | Provision Elasticsearch Data Store | `false` | +| `provisionDataStore.kafka` | Provision Kafka Data Store | `false` | +| `query.agentSidecar.enabled` | Enable agent sidecare for query deployment | `true` | +| `query.config` | [UI Config json file](https://www.jaegertracing.io/docs/latest/frontend-ui/) | `nil` | +| `query.service.annotations` | Annotations for Query SVC | `nil` | +| `query.image` | Image for Jaeger Query UI | `jaegertracing/jaeger-query` | +| `query.imagePullSecrets` | Secret to pull the Image for Jaeger Query UI | `[]` | +| `query.ingress.enabled` | Allow external traffic access | `false` | +| `query.ingress.annotations` | Configure annotations for Ingress | `{}` | +| `query.ingress.hosts` | Configure host for Ingress | `nil` | +| `query.ingress.tls` | Configure tls for Ingress | `nil` | +| `query.pullPolicy` | Query UI image pullPolicy | `IfNotPresent` | +| `query.service.loadBalancerSourceRanges` | list of IP CIDRs allowed access to load balancer (if supported) | `[]` | +| `query.service.nodePort` | Specific node port to use when type is NodePort | `nil` | +| `query.service.port` | External accessible port | `80` | +| `query.service.type` | Service type | `ClusterIP` | +| `query.basePath` | Base path of Query UI, used for ingress as well (if it is enabled) | `/` | +| `query.extraConfigmapMounts` | Additional query configMap mounts | `[]` | +| `query.priorityClassName` | Priority class name for the Query UI pods | `nil` | +| `schema.annotations` | Annotations for the schema job| `nil` | +| `schema.extraConfigmapMounts` | Additional cassandra schema job configMap mounts | `[]` | +| `schema.image` | Image to setup cassandra schema | `jaegertracing/jaeger-cassandra-schema` | +| `schema.imagePullSecrets` | Secret to pull the Image for the Cassandra schema setup job | `[]` | +| `schema.pullPolicy` | Schema image pullPolicy | `IfNotPresent` | +| `schema.activeDeadlineSeconds` | Deadline in seconds for cassandra schema creation job to complete | `120` | +| `schema.keyspace` | Set explicit keyspace name | `nil` | +| `spark.enabled` | Enables the dependencies job| `false` | +| `spark.image` | Image for the dependencies job| `jaegertracing/spark-dependencies` | +| `spark.imagePullSecrets` | Secret to pull the Image for the Spark dependencies job | `[]` | +| `spark.pullPolicy` | Image pull policy of the deps image | `Always` | +| `spark.schedule` | Schedule of the cron job | `"49 23 * * *"` | +| `spark.successfulJobsHistoryLimit` | Cron job successfulJobsHistoryLimit | `5` | +| `spark.failedJobsHistoryLimit` | Cron job failedJobsHistoryLimit | `5` | +| `spark.tag` | Tag of the dependencies job image | `latest` | +| `spark.extraConfigmapMounts` | Additional spark configMap mounts | `[]` | +| `spark.extraSecretMounts` | Additional spark secret mounts | `[]` | +| `esIndexCleaner.enabled` | Enables the ElasticSearch indices cleanup job| `false` | +| `esIndexCleaner.image` | Image for the ElasticSearch indices cleanup job| `jaegertracing/jaeger-es-index-cleaner` | +| `esIndexCleaner.imagePullSecrets` | Secret to pull the Image for the ElasticSearch indices cleanup job | `[]` | +| `esIndexCleaner.pullPolicy` | Image pull policy of the ES cleanup image | `Always` | +| `esIndexCleaner.numberOfDays` | ElasticSearch indices older than this number (Number of days) would be deleted by the CronJob | `7` +| `esIndexCleaner.schedule` | Schedule of the cron job | `"55 23 * * *"` | +| `esIndexCleaner.successfulJobsHistoryLimit` | successfulJobsHistoryLimit for ElasticSearch indices cleanup CronJob | `5` | +| `esIndexCleaner.failedJobsHistoryLimit` | failedJobsHistoryLimit for ElasticSearch indices cleanup CronJob | `5` | +| `esIndexCleaner.tag` | Tag of the dependencies job image | `latest` | +| `esIndexCleaner.extraConfigmapMounts` | Additional esIndexCleaner configMap mounts | `[]` | +| `esIndexCleaner.extraSecretMounts` | Additional esIndexCleaner secret mounts | `[]` | +| `storage.cassandra.env` | Extra cassandra related env vars to be configured on components that talk to cassandra | `cassandra` | +| `storage.cassandra.cmdlineParams` | Extra cassandra related command line options to be configured on components that talk to cassandra | `cassandra` | +| `storage.cassandra.existingSecret` | Name of existing password secret object (for password authentication | `nil` +| `storage.cassandra.host` | Provisioned cassandra host | `cassandra` | +| `storage.cassandra.keyspace` | Schema name for cassandra | `jaeger_v1_test` | +| `storage.cassandra.password` | Provisioned cassandra password (ignored if storage.cassandra.existingSecret set) | `password` | +| `storage.cassandra.port` | Provisioned cassandra port | `9042` | +| `storage.cassandra.tls.enabled` | Provisioned cassandra TLS connection enabled | `false` | +| `storage.cassandra.tls.secretName` | Provisioned cassandra TLS connection existing secret name (possible keys in secret: `ca-cert.pem`, `client-key.pem`, `client-cert.pem`, `cqlshrc`, `commonName`) | `` | +| `storage.cassandra.usePassword` | Use password | `true` | +| `storage.cassandra.user` | Provisioned cassandra username | `user` | +| `storage.elasticsearch.env` | Extra ES related env vars to be configured on components that talk to ES | `nil` | +| `storage.elasticsearch.cmdlineParams` | Extra ES related command line options to be configured on components that talk to ES | `nil` | +| `storage.elasticsearch.existingSecret` | Name of existing password secret object (for password authentication | `nil` | +| `storage.elasticsearch.existingSecretKey` | Key of the declared password secret | `password` | +| `storage.elasticsearch.host` | Provisioned elasticsearch host| `elasticsearch` | +| `storage.elasticsearch.password` | Provisioned elasticsearch password (ignored if storage.elasticsearch.existingSecret set | `changeme` | +| `storage.elasticsearch.port` | Provisioned elasticsearch port| `9200` | +| `storage.elasticsearch.scheme` | Provisioned elasticsearch scheme | `http` | +| `storage.elasticsearch.usePassword` | Use password | `true` | +| `storage.elasticsearch.user` | Provisioned elasticsearch user| `elastic` | +| `storage.elasticsearch.indexPrefix` | Index Prefix for elasticsearch | `nil` | +| `storage.elasticsearch.nodesWanOnly` | Only access specified es host | `false` | +| `storage.kafka.authentication` | Authentication type used to authenticate with kafka cluster. e.g. none, kerberos, tls | `none` | +| `storage.kafka.brokers` | Broker List for Kafka with port | `kafka:9092` | +| `storage.kafka.topic` | Topic name for Kafka | `jaeger_v1_test` | +| `storage.type` | Storage type (ES or Cassandra)| `cassandra` | +| `tag` | Image tag/version | `1.18.0` | + +For more information about some of the tunable parameters that Cassandra provides, please visit the helm chart for [cassandra](https://github.com/kubernetes/charts/tree/master/incubator/cassandra) and the official [website](http://cassandra.apache.org/) at apache.org. + +For more information about some of the tunable parameters that Jaeger provides, please visit the official [Jaeger repo](https://github.com/uber/jaeger) at GitHub.com. + +### Pending enhancements + +- [ ] Sidecar deployment support diff --git a/rds/base/charts/jaeger/charts/cassandra-0.15.2.tgz b/rds/base/charts/jaeger/charts/cassandra-0.15.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b097979309231ae6c3dcb592d014dc5236ed3b59 GIT binary patch literal 11697 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 H0NelopnM + ## 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: "-" + accessMode: ReadWriteOnce + size: 10Gi + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## Minimum memory for development is 4GB and 2 CPU cores +## Minimum memory for production is 8GB and 4 CPU cores +## ref: http://docs.datastax.com/en/archived/cassandra/2.0/cassandra/architecture/architecturePlanningHardware_c.html +resources: {} + # requests: + # memory: 4Gi + # cpu: 2 + # limits: + # memory: 4Gi + # cpu: 2 + +## Change cassandra configuration parameters below: +## ref: http://docs.datastax.com/en/cassandra/3.0/cassandra/configuration/configCassandra_yaml.html +## Recommended max heap size is 1/2 of system memory +## Recommended heap new size is 1/4 of max heap size +## ref: http://docs.datastax.com/en/cassandra/3.0/cassandra/operations/opsTuneJVM.html +config: + cluster_domain: cluster.local + cluster_name: cassandra + cluster_size: 3 + seed_size: 2 + num_tokens: 256 + # If you want Cassandra to use this datacenter and rack name, + # you need to set endpoint_snitch to GossipingPropertyFileSnitch. + # Otherwise, these values are ignored and datacenter1 and rack1 + # are used. + dc_name: DC1 + rack_name: RAC1 + endpoint_snitch: SimpleSnitch + max_heap_size: 2048M + heap_new_size: 512M + start_rpc: false + ports: + cql: 9042 + thrift: 9160 + # If a JVM Agent is in place + # agent: 61621 + +## Cassandra config files overrides +configOverrides: {} + +## Cassandra docker command overrides +commandOverrides: [] + +## Cassandra docker args overrides +argsOverrides: [] + +## Custom env variables. +## ref: https://hub.docker.com/_/cassandra/ +env: {} + +## Liveness and Readiness probe values. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +livenessProbe: + initialDelaySeconds: 90 + periodSeconds: 30 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 +readinessProbe: + initialDelaySeconds: 90 + periodSeconds: 30 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + address: "${POD_IP}" + +## Configure node selector. Edit code below for adding selector to pods +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +# selector: + # nodeSelector: + # cloud.google.com/gke-nodepool: pool-db + +## Additional pod annotations +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} + +## Additional pod labels +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +## Additional pod-level settings +podSettings: + # Change this to give pods more time to properly leave the cluster when not using persistent storage. + terminationGracePeriodSeconds: 30 + +## Pod distruption budget +podDisruptionBudget: {} + # maxUnavailable: 1 + # minAvailable: 2 + +podManagementPolicy: OrderedReady +updateStrategy: + type: OnDelete + +## Pod Security Context +securityContext: + enabled: false + fsGroup: 999 + runAsUser: 999 + +## Affinity for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +affinity: {} + +## Node tolerations for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + +rbac: + # Specifies whether RBAC resources should be created + create: true + +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 fullname template + # name: + +# Use host network for Cassandra pods +# You must pass seed list into config.seeds property if set to true +hostNetwork: false + +## Backup cronjob configuration +## Ref: https://github.com/maorfr/cain +backup: + enabled: false + + # Schedule to run jobs. Must be in cron time format + # Ref: https://crontab.guru/ + schedule: + - keyspace: keyspace1 + cron: "0 7 * * *" + - keyspace: keyspace2 + cron: "30 7 * * *" + + annotations: + # Example for authorization to AWS S3 using kube2iam + # Can also be done using environment variables + iam.amazonaws.com/role: cain + + image: + repository: maorfr/cain + tag: 0.6.0 + + # Additional arguments for cain + # Ref: https://github.com/maorfr/cain#usage + extraArgs: [] + + # Add additional environment variables + env: + # Example environment variable required for AWS credentials chain + - name: AWS_REGION + value: us-east-1 + + resources: + requests: + memory: 1Gi + cpu: 1 + limits: + memory: 1Gi + cpu: 1 + + # Name of the secret containing the credentials of the service account used by GOOGLE_APPLICATION_CREDENTIALS, as a credentials.json file + # google: + # serviceAccountSecret: + + # Destination to store the backup artifacts + # Supported cloud storage services: AWS S3, Minio S3, Azure Blob Storage, Google Cloud Storage + # Additional support can added. Visit this repository for details + # Ref: https://github.com/maorfr/skbn + destination: s3://bucket/cassandra + +## Cassandra exported configuration +## ref: https://github.com/criteo/cassandra_exporter +exporter: + enabled: false + serviceMonitor: + enabled: false + additionalLabels: {} + # prometheus: default + image: + repo: criteord/cassandra_exporter + tag: 2.0.2 + port: 5556 + jvmOpts: "" + resources: {} + # limits: + # cpu: 1 + # memory: 1Gi + # requests: + # cpu: 1 + # memory: 1Gi + +extraVolumes: [] +extraVolumeMounts: [] +# extraVolumes and extraVolumeMounts allows you to mount other volumes +# Example Use Case: mount ssl certificates +# extraVolumes: +# - name: cas-certs +# secret: +# defaultMode: 420 +# secretName: cas-certs +# extraVolumeMounts: +# - name: cas-certs +# mountPath: /certs +# readOnly: true + +extraContainers: [] +## Additional containers to be added +# extraContainers: +# - name: cassandra-sidecar +# image: cassandra-sidecar:latest +# volumeMounts: +# - name: some-mount +# mountPath: /some/path diff --git a/rds/base/charts/jaeger/charts/elasticsearch-7.8.1.tgz b/rds/base/charts/jaeger/charts/elasticsearch-7.8.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..96f3fd538b4263f472929eba8a67ae2ad9c2e312 GIT binary patch literal 25705 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 + + + +- [Requirements](#requirements) +- [Installing](#installing) +- [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 OSS version of Elasticsearch?](#how-to-install-oss-version-of-elasticsearch) + - [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 + +* [Helm][] >=2.8.0 and <3.0.0 +* Kubernetes >=1.8 +* 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 7.8.1 version. + +* Add the Elastic Helm charts repo: +`helm repo add elastic https://helm.elastic.co` + +* Install 7.8.1 release: +`helm install --name elasticsearch --version 7.8.1 elastic/elasticsearch` + + +## 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` | +| `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 should configure the [jvm heap size][] | `-Xmx1g -Xms1g` | +| `esMajorVersion` | 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 | `""` | +| `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.8.1` | +| `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 | `""` | +| `masterTerminationFix` | A workaround needed for Elasticsearch < 7.2 to prevent master status being lost during restarts [#63][] | `false` | +| `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` | +| `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"` | 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.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` | +| `sidecarResources` | Allows you to set the [resources][] for the sidecar containers in the StatefulSet | {} | +| `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` | +| `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 OSS version of Elasticsearch? + +Deploying OSS version of Elasticsearch can be done by setting `image` value to +[Elasticsearch OSS Docker image][] + +An example of Elasticsearch cluster using OSS version can be found in +[examples/oss][]. + +### 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 a tool like [curator][]. In the future +there are plans to have Elasticsearch manage automated snapshots with +[Snapshot Lifecycle Management][]. + +### 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. + + +[#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.8/cluster.name.html +[clustering and node discovery]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/README.md#clustering-and-node-discovery +[config example]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/config/values.yaml +[curator]: https://www.elastic.co/guide/en/elasticsearch/client/curator/7.8/snapshot.html +[custom docker image]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/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.8/discovery-settings.html#minimum_master_nodes +[docker for mac]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/docker-for-mac +[elasticsearch cluster health status params]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/cluster-health.html#request-params +[elasticsearch docker image]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/docker.html +[elasticsearch oss docker image]: https://www.docker.elastic.co/r/elasticsearch/elasticsearch-oss +[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.8/elasticsearch/examples/ +[examples/multi]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/multi +[examples/oss]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/oss +[examples/security]: https://github.com/elastic/helm-charts/tree/7.8/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.8/elasticsearch/README.md#how-to-install-plugins +[how to use the keystore]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/README.md#how-to-use-the-keystore +[http.port]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/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.8/jvm-options.html +[jvm heap size]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/heap-size.html +[kind]: https://github.com/elastic/helm-charts/tree/7.8/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]: 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.8/elasticsearch/examples/migration/README.md +[minikube]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/minikube +[microk8s]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/microk8s +[multi]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/multi/ +[network.host elasticsearch setting]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/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.8/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.8/modules-node.html +[nodeSelector]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector +[openshift]: https://github.com/elastic/helm-charts/tree/7.8/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.8/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://github.com/elastic/elasticsearch/issues/38461 +[snapshot plugin]: https://www.elastic.co/guide/en/elasticsearch/plugins/7.8/repository.html +[snapshot repository]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/modules-snapshots.html +[supported configurations]: https://github.com/elastic/helm-charts/tree/7.8/README.md#supported-configurations +[sysctl vm.max_map_count]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/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.8/modules-transport.html#_transport_settings +[updateStrategy]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ +[values.yaml]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/values.yaml +[volumeClaimTemplate for statefulsets]: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-storage diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/config/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/Makefile new file mode 100644 index 0000000..a3f9617 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/Makefile @@ -0,0 +1,19 @@ +default: test +include ../../../helpers/examples.mk + +RELEASE := helm-es-config + +install: + helm upgrade --wait --timeout=600 --install $(RELEASE) --values ./values.yaml ../../ + +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 --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/config/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/README.md new file mode 100644 index 0000000..4fb0a28 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/README.md @@ -0,0 +1,27 @@ +# Config + +This example deploy a single node Elasticsearch 7.8.1 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.8/elasticsearch/examples/config/test/goss.yaml +[values]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/config/values.yaml diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/config/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/test/goss.yaml new file mode 100644 index 0000000..8487013 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/test/goss.yaml @@ -0,0 +1,26 @@ +http: + http://localhost:9200/_cluster/health: + status: 200 + timeout: 2000 + body: + - 'green' + - '"number_of_nodes":1' + - '"number_of_data_nodes":1' + + http://localhost:9200: + status: 200 + timeout: 2000 + body: + - '"cluster_name" : "config"' + - '"name" : "config-master-0"' + - '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/rds/base/charts/jaeger/charts/elasticsearch/examples/config/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/values.yaml new file mode 100644 index 0000000..ebde4f4 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/values.yaml @@ -0,0 +1,31 @@ +--- + +clusterName: "config" +replicas: 1 + +extraEnvs: + - name: ELASTIC_PASSWORD + valueFrom: + secretKeyRef: + name: elastic-credentials + key: password + - name: ELASTIC_USERNAME + valueFrom: + secretKeyRef: + name: elastic-credentials + key: username + +# 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: | + 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/rds/base/charts/jaeger/charts/elasticsearch/examples/config/watcher_encryption_key b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/watcher_encryption_key new file mode 100644 index 0000000..b5f9078 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/config/watcher_encryption_key @@ -0,0 +1 @@ +supersecret diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/default/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/Makefile new file mode 100644 index 0000000..5f5215c --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/Makefile @@ -0,0 +1,16 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-default + +install: + helm upgrade --wait --timeout=600 --install $(RELEASE) ../../ + +restart: + helm upgrade --set terminationGracePeriod=121 --wait --timeout=600 --install $(RELEASE) ../../ + +test: install goss + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/default/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/README.md new file mode 100644 index 0000000..23a7d69 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/README.md @@ -0,0 +1,25 @@ +# Default + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/default/test/goss.yaml +[default values]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/values.yaml diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/default/rolling_upgrade.sh b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/rolling_upgrade.sh new file mode 100644 index 0000000..c5a2a88 --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/default/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/test/goss.yaml new file mode 100644 index 0000000..781ccaf --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/default/test/goss.yaml @@ -0,0 +1,39 @@ +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.8.1"' + - '"cluster_name" : "elasticsearch"' + - '"name" : "elasticsearch-master-0"' + - '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/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/Makefile new file mode 100644 index 0000000..398545e --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/Makefile @@ -0,0 +1,12 @@ +default: test + +RELEASE := helm-es-docker-for-mac + +install: + helm upgrade --wait --timeout=900 --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/README.md new file mode 100644 index 0000000..4892917 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/README.md @@ -0,0 +1,23 @@ +# Docker for Mac + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/docker-for-mac/values.yaml +[docker for mac]: https://docs.docker.com/docker-for-mac/kubernetes/ diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/docker-for-mac/values.yaml new file mode 100644 index 0000000..f7deba6 --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/Makefile new file mode 100644 index 0000000..af816a9 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/Makefile @@ -0,0 +1,16 @@ +default: test + +RELEASE := helm-es-kind + +install: + helm upgrade --wait --timeout=900 --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=900 --install --values values-local-path.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/README.md new file mode 100644 index 0000000..e48c2b1 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/README.md @@ -0,0 +1,36 @@ +# KIND + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/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.8/elasticsearch/examples/kubernetes-kind/Makefile#L5 diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values-local-path.yaml new file mode 100644 index 0000000..500ad4b --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/kubernetes-kind/values.yaml new file mode 100644 index 0000000..500ad4b --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/Makefile new file mode 100644 index 0000000..2c7d3d3 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/Makefile @@ -0,0 +1,12 @@ +default: test + +RELEASE := helm-es-microk8s + +install: + helm upgrade --wait --timeout=900 --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/README.md new file mode 100644 index 0000000..75adcd8 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/README.md @@ -0,0 +1,32 @@ +# MicroK8S + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/microk8s/values.yaml +[MicroK8S]: https://microk8s.io diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/microk8s/values.yaml new file mode 100644 index 0000000..2627ecb --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/Makefile new file mode 100644 index 0000000..3b1dac1 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/Makefile @@ -0,0 +1,10 @@ +PREFIX := helm-es-migration + +data: + helm upgrade --wait --timeout=600 --install --values ./data.yml $(PREFIX)-data ../../ + +master: + helm upgrade --wait --timeout=600 --install --values ./master.yml $(PREFIX)-master ../../ + +client: + helm upgrade --wait --timeout=600 --install --values ./client.yml $(PREFIX)-client ../../ diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/README.md new file mode 100644 index 0000000..ef53664 --- /dev/null +++ b/rds/base/charts/jaeger/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.yml][]: + + ``` + 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.yml][] 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.yml][] 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.yml][] 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.yml]: https://github.com/elastic/helm-charts/blob/7.8/elasticsearch/examples/migration/data.yml +[helm/charts]: https://github.com/helm/charts/tree/master/stable/elasticsearch +[master.yml]: https://github.com/elastic/helm-charts/blob/7.8/elasticsearch/examples/migration/master.yml +[restoring to a different cluster guide]: https://www.elastic.co/guide/en/elasticsearch/reference/6.6/modules-snapshots.html#_restoring_to_a_different_cluster +[rolling upgrades guide]: https://www.elastic.co/guide/en/elasticsearch/reference/6.6/rolling-upgrades.html diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/client.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/client.yml new file mode 100644 index 0000000..30ee700 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/client.yml @@ -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/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/data.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/data.yml new file mode 100644 index 0000000..eedcbb0 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/data.yml @@ -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/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/master.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/master.yml new file mode 100644 index 0000000..3e3a2f1 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/migration/master.yml @@ -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/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/Makefile new file mode 100644 index 0000000..97109ce --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/Makefile @@ -0,0 +1,12 @@ +default: test + +RELEASE := helm-es-minikube + +install: + helm upgrade --wait --timeout=900 --install --values values.yaml $(RELEASE) ../../ + +test: install + helm test $(RELEASE) + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/README.md new file mode 100644 index 0000000..e016987 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/README.md @@ -0,0 +1,38 @@ +# Minikube + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/minikube/values.yaml +[minikube]: https://minikube.sigs.k8s.io/docs/ diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/minikube/values.yaml new file mode 100644 index 0000000..ccceb3a --- /dev/null +++ b/rds/base/charts/jaeger/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/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/Makefile new file mode 100644 index 0000000..836ec2e --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/Makefile @@ -0,0 +1,16 @@ +default: test + +include ../../../helpers/examples.mk + +PREFIX := helm-es-multi +RELEASE := helm-es-multi-master + +install: + helm upgrade --wait --timeout=600 --install --values ./master.yml $(PREFIX)-master ../../ + helm upgrade --wait --timeout=600 --install --values ./data.yml $(PREFIX)-data ../../ + +test: install goss + +purge: + helm del --purge $(PREFIX)-master + helm del --purge $(PREFIX)-data diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/README.md new file mode 100644 index 0000000..f27cade --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/README.md @@ -0,0 +1,27 @@ +# Multi + +This example deploy an Elasticsearch 7.8.1 cluster composed of 2 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][] + +## Usage + +* Deploy the 2 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` + + +[data values]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/multi/data.yml +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/multi/test/goss.yaml +[master values]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/multi/master.yml diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/data.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/data.yml new file mode 100644 index 0000000..ecc6893 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/data.yml @@ -0,0 +1,9 @@ +--- + +clusterName: "multi" +nodeGroup: "data" + +roles: + master: "false" + ingest: "true" + data: "true" diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/master.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/master.yml new file mode 100644 index 0000000..2ca4cca --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/master.yml @@ -0,0 +1,9 @@ +--- + +clusterName: "multi" +nodeGroup: "master" + +roles: + master: "true" + ingest: "false" + data: "false" diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/multi/test/goss.yaml new file mode 100644 index 0000000..18cb250 --- /dev/null +++ b/rds/base/charts/jaeger/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":6' + - '"number_of_data_nodes":3' diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/Makefile new file mode 100644 index 0000000..6e49591 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/Makefile @@ -0,0 +1,15 @@ +default: test +include ../../../helpers/examples.mk + +RELEASE := elasticsearch + +template: + helm template --values ./values.yaml ../../ + +install: + helm upgrade --wait --timeout=600 --install $(RELEASE) --values ./values.yaml ../../ + +test: install goss + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/README.md new file mode 100644 index 0000000..73a3760 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/README.md @@ -0,0 +1,24 @@ +# OpenShift + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/openshift/values.yaml +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/openshift/test/goss.yaml +[openshift]: https://www.openshift.com/ diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/test/goss.yaml new file mode 100644 index 0000000..dd3dc71 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/test/goss.yaml @@ -0,0 +1,17 @@ +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.8.1"' + - '"cluster_name" : "elasticsearch"' + - '"name" : "elasticsearch-master-0"' + - 'You Know, for Search' diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/values.yaml new file mode 100644 index 0000000..8a21126 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/openshift/values.yaml @@ -0,0 +1,11 @@ +--- + +securityContext: + runAsUser: null + +podSecurityContext: + fsGroup: null + runAsUser: null + +sysctlInitContainer: + enabled: false diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/Makefile new file mode 100644 index 0000000..e274659 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/Makefile @@ -0,0 +1,12 @@ +default: test +include ../../../helpers/examples.mk + +RELEASE := helm-es-oss + +install: + helm upgrade --wait --timeout=600 --install $(RELEASE) --values ./values.yaml ../../ + +test: install goss + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/README.md new file mode 100644 index 0000000..fd2aad9 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/README.md @@ -0,0 +1,23 @@ +# OSS + +This example deploy a 3 nodes Elasticsearch 7.8.1 cluster using +[Elasticsearch OSS][] version. + +## 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/oss-master 9200 + curl localhost:9200/_cat/indices + ``` + +## Testing + +You can also run [goss integration tests][] using `make test` + + +[elasticsearch oss]: https://www.elastic.co/downloads/elasticsearch-oss +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/oss/test/goss.yaml diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/test/goss.yaml new file mode 100644 index 0000000..e0f10c4 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/test/goss.yaml @@ -0,0 +1,17 @@ +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.8.1"' + - '"cluster_name" : "oss"' + - '"name" : "oss-master-0"' + - 'You Know, for Search' diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/values.yaml new file mode 100644 index 0000000..adcb7df --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/oss/values.yaml @@ -0,0 +1,4 @@ +--- + +clusterName: "oss" +image: "docker.elastic.co/elasticsearch/elasticsearch-oss" diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/security/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/Makefile new file mode 100644 index 0000000..46f0ee7 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/Makefile @@ -0,0 +1,37 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-security +ELASTICSEARCH_IMAGE := docker.elastic.co/elasticsearch/elasticsearch:$(STACK_VERSION) + +install: + helm upgrade --wait --timeout=600 --install --values ./security.yml $(RELEASE) ../../ + +purge: + kubectl delete secrets elastic-credentials elastic-certificates elastic-certificate-pem || true + helm del --purge $(RELEASE) + +test: secrets install goss + +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/rds/base/charts/jaeger/charts/elasticsearch/examples/security/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/README.md new file mode 100644 index 0000000..0b94139 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/README.md @@ -0,0 +1,29 @@ +# Security + +This example deploy a 3 nodes Elasticsearch 7.8.1 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.8/elasticsearch/examples/security/test/goss.yaml +[official docs]: https://www.elastic.co/guide/en/elasticsearch/reference/7.8/configuring-tls.html#node-certificates +[values]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/security/security.yaml diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/security/security.yml b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/security.yml new file mode 100644 index 0000000..04d932c --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/security.yml @@ -0,0 +1,38 @@ +--- +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 + - name: ELASTIC_USERNAME + valueFrom: + secretKeyRef: + name: elastic-credentials + key: username + +secretMounts: + - name: elastic-certificates + secretName: elastic-certificates + path: /usr/share/elasticsearch/config/certs diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/security/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/test/goss.yaml new file mode 100644 index 0000000..c6d4b98 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/security/test/goss.yaml @@ -0,0 +1,45 @@ +http: + https://security-master:9200/_cluster/health: + status: 200 + timeout: 2000 + allow-insecure: true + username: '{{ .Env.ELASTIC_USERNAME }}' + 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: '{{ .Env.ELASTIC_USERNAME }}' + password: '{{ .Env.ELASTIC_PASSWORD }}' + body: + - '"cluster_name" : "security"' + - '"name" : "security-master-0"' + - 'You Know, for Search' + + https://localhost:9200/_xpack/license: + status: 200 + timeout: 2000 + allow-insecure: true + username: '{{ .Env.ELASTIC_USERNAME }}' + 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/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/Makefile b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/Makefile new file mode 100644 index 0000000..f890d50 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/Makefile @@ -0,0 +1,16 @@ +default: test + +include ../../../helpers/examples.mk + +RELEASE := helm-es-upgrade + +install: + ./scripts/upgrade.sh --release $(RELEASE) + +init: + helm init --client-only + +test: init install goss + +purge: + helm del --purge $(RELEASE) diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/README.md b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/README.md new file mode 100644 index 0000000..def17dd --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/README.md @@ -0,0 +1,27 @@ +# Upgrade + +This example will deploy a 3 node Elasticsearch cluster using an old chart version, +then upgrade it to version 7.8.1. + +The following upgrades are tested: +- Upgrade from [7.0.0-alpha1][] version on K8S <1.16 +- Upgrade from [7.4.0][] version on K8S >=1.16 (Elasticsearch chart < 7.4.0 are +not compatible with K8S >= 1.16) + + +## Usage + +Running `make install` command will do first install and 7.8.1 upgrade. + +Note: [jq][] is a requirement for this make target. + + +## Testing + +You can also run [goss integration tests][] using `make test`. + + +[7.0.0-alpha1]: https://github.com/elastic/helm-charts/releases/tag/7.0.0-alpha1 +[7.4.0]: https://github.com/elastic/helm-charts/releases/tag/7.4.0 +[goss integration tests]: https://github.com/elastic/helm-charts/tree/7.8/elasticsearch/examples/upgrade/test/goss.yaml +[jq]: https://stedolan.github.io/jq/ diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/scripts/upgrade.sh b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/scripts/upgrade.sh new file mode 100644 index 0000000..6d0aa9f --- /dev/null +++ b/rds/base/charts/jaeger/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=600 --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=600 --set terminationGracePeriod=121 --install "$RELEASE" ../../ --set clusterName=upgrade +kubectl rollout status sts/upgrade-master --timeout=600s diff --git a/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/test/goss.yaml b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/test/goss.yaml new file mode 100644 index 0000000..c060b8b --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/examples/upgrade/test/goss.yaml @@ -0,0 +1,17 @@ +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.8.1"' + - '"cluster_name" : "upgrade"' + - '"name" : "upgrade-master-0"' + - 'You Know, for Search' diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/NOTES.txt b/rds/base/charts/jaeger/charts/elasticsearch/templates/NOTES.txt new file mode 100644 index 0000000..3841ada --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/NOTES.txt @@ -0,0 +1,4 @@ +1. Watch all cluster members come up. + $ kubectl get pods --namespace={{ .Release.Namespace }} -l app={{ template "elasticsearch.uname" . }} -w +2. Test cluster health using Helm test. + $ helm test {{ .Release.Name }} --cleanup diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/_helpers.tpl b/rds/base/charts/jaeger/charts/elasticsearch/templates/_helpers.tpl new file mode 100644 index 0000000..87783da --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/_helpers.tpl @@ -0,0 +1,87 @@ +{{/* 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 -}} + +{{/* +Return the appropriate apiVersion for statefulset. +*/}} +{{- define "elasticsearch.statefulset.apiVersion" -}} +{{- if semverCompare "<1.9-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "apps/v1beta2" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "elasticsearch.ingress.apiVersion" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/configmap.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/configmap.yaml new file mode 100644 index 0000000..93285a0 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/configmap.yaml @@ -0,0 +1,17 @@ +{{- if .Values.esConfig }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "elasticsearch.uname" . }}-config + namespace: {{ .Release.Namespace }} + 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/rds/base/charts/jaeger/charts/elasticsearch/templates/ingress.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/ingress.yaml new file mode 100644 index 0000000..ddb84fc --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/ingress.yaml @@ -0,0 +1,39 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "elasticsearch.uname" . -}} +{{- $servicePort := .Values.httpPort -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: {{ template "elasticsearch.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Chart.Name }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/poddisruptionbudget.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..a4dfe0f --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/poddisruptionbudget.yaml @@ -0,0 +1,13 @@ +--- +{{- if .Values.maxUnavailable }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: "{{ template "elasticsearch.uname" . }}-pdb" + namespace: {{ .Release.Namespace }} +spec: + maxUnavailable: {{ .Values.maxUnavailable }} + selector: + matchLabels: + app: "{{ template "elasticsearch.uname" . }}" +{{- end }} diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/podsecuritypolicy.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000..f570c90 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/podsecuritypolicy.yaml @@ -0,0 +1,15 @@ +{{- if .Values.podSecurityPolicy.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ default $fullName .Values.podSecurityPolicy.name | quote }} + namespace: {{ .Release.Namespace }} + 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/rds/base/charts/jaeger/charts/elasticsearch/templates/role.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/role.yaml new file mode 100644 index 0000000..1d51d3f --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/role.yaml @@ -0,0 +1,26 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $fullName | quote }} + namespace: {{ .Release.Namespace }} + 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/rds/base/charts/jaeger/charts/elasticsearch/templates/rolebinding.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/rolebinding.yaml new file mode 100644 index 0000000..ad8304c --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/rolebinding.yaml @@ -0,0 +1,25 @@ +{{- if .Values.rbac.create -}} +{{- $fullName := include "elasticsearch.uname" . -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $fullName | quote }} + namespace: {{ .Release.Namespace }} + 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 }} +roleRef: + kind: Role + name: {{ $fullName | quote }} + apiGroup: rbac.authorization.k8s.io +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/elasticsearch/templates/service.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/service.yaml new file mode 100644 index 0000000..d022a63 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/service.yaml @@ -0,0 +1,73 @@ +--- +kind: Service +apiVersion: v1 +metadata: +{{- if eq .Values.nodeGroup "master" }} + name: {{ template "elasticsearch.masterService" . }} +{{- else }} + name: {{ template "elasticsearch.uname" . }} +{{- end }} + namespace: {{ .Release.Namespace }} + 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 }} +--- +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/rds/base/charts/jaeger/charts/elasticsearch/templates/serviceaccount.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/serviceaccount.yaml new file mode 100644 index 0000000..0d74077 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/serviceaccount.yaml @@ -0,0 +1,21 @@ +{{- 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 }} + namespace: {{ .Release.Namespace }} + 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/rds/base/charts/jaeger/charts/elasticsearch/templates/statefulset.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/statefulset.yaml new file mode 100644 index 0000000..2277e16 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/statefulset.yaml @@ -0,0 +1,430 @@ +--- +apiVersion: {{ template "elasticsearch.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ template "elasticsearch.uname" . }} + namespace: {{ .Release.Namespace }} + 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: + heritage: {{ .Release.Service | quote }} + 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: + 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: + {{- 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 }} + {{- 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 }} + {{- if semverCompare ">1.13" .Capabilities.KubeVersion.GitVersion }} + enableServiceLinks: {{ .Values.enableServiceLinks }} + {{- end }} + 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: + - sh + - -c + - | + #!/usr/bin/env bash + 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 }} + containers: + - name: "{{ template "elasticsearch.name" . }}" + securityContext: +{{ toYaml .Values.securityContext | indent 10 }} + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + readinessProbe: + exec: + command: + - sh + - -c + - | + #!/usr/bin/env bash -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 + + http () { + local path="${1}" + local args="${2}" + set -- -XGET -s + + if [ "$args" != "" ]; then + set -- "$@" $args + fi + + if [ -n "${ELASTIC_USERNAME}" ] && [ -n "${ELASTIC_PASSWORD}" ]; then + set -- "$@" -u "${ELASTIC_USERNAME}:${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: ES_JAVA_OPTS + value: "{{ .Values.esJavaOpts }}" + {{- 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.masterTerminationFix }} + {{- if eq .Values.roles.master "true" }} + # This sidecar will prevent slow master re-election + # https://github.com/elastic/helm-charts/issues/63 + - name: elasticsearch-master-graceful-termination-handler + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + command: + - "sh" + - -c + - | + #!/usr/bin/env bash + set -eo pipefail + + http () { + local path="${1}" + if [ -n "${ELASTIC_USERNAME}" ] && [ -n "${ELASTIC_PASSWORD}" ]; then + BASIC_AUTH="-u ${ELASTIC_USERNAME}:${ELASTIC_PASSWORD}" + else + BASIC_AUTH='' + fi + curl -XGET -s -k --fail ${BASIC_AUTH} {{ .Values.protocol }}://{{ template "elasticsearch.masterService" . }}:{{ .Values.httpPort }}${path} + } + + cleanup () { + while true ; do + local master="$(http "/_cat/master?h=node" || echo "")" + if [[ $master == "{{ template "elasticsearch.masterService" . }}"* && $master != "${NODE_NAME}" ]]; then + echo "This node is not master." + break + fi + echo "This node is still master, waiting gracefully for it to step down" + sleep 1 + done + + exit 0 + } + + trap cleanup SIGTERM + + sleep infinity & + wait $! + resources: +{{ toYaml .Values.sidecarResources | indent 10 }} + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if .Values.extraEnvs }} +{{ toYaml .Values.extraEnvs | indent 10 }} + {{- end }} + {{- if .Values.envFrom }} + envFrom: +{{ toYaml .Values.envFrom | indent 10 }} + {{- end }} + {{- 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/rds/base/charts/jaeger/charts/elasticsearch/templates/test/test-elasticsearch-health.yaml b/rds/base/charts/jaeger/charts/elasticsearch/templates/test/test-elasticsearch-health.yaml new file mode 100644 index 0000000..a278b14 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/templates/test/test-elasticsearch-health.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-{{ randAlpha 5 | lower }}-test" + annotations: + "helm.sh/hook": test-success +spec: + securityContext: +{{ toYaml .Values.podSecurityContext | indent 4 }} + containers: + - name: "{{ .Release.Name }}-{{ randAlpha 5 | lower }}-test" + 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 diff --git a/rds/base/charts/jaeger/charts/elasticsearch/values.yaml b/rds/base/charts/jaeger/charts/elasticsearch/values.yaml new file mode 100644 index 0000000..284ea67 --- /dev/null +++ b/rds/base/charts/jaeger/charts/elasticsearch/values.yaml @@ -0,0 +1,277 @@ +--- +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" + +replicas: 3 +minimumMasterNodes: 2 + +esMajorVersion: "" + +# 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 + +image: "docker.elastic.co/elasticsearch/elasticsearch" +imageTag: "7.8.1" +imagePullPolicy: "IfNotPresent" + +podAnnotations: {} + # iam.amazonaws.com/role: es-cluster + +# additionals labels +labels: {} + +esJavaOpts: "-Xmx1g -Xms1g" + +resources: + requests: + cpu: "1000m" + memory: "2Gi" + limits: + cpu: "1000m" + memory: "2Gi" + +initResources: {} + # limits: + # cpu: "25m" + # # memory: "128Mi" + # requests: + # cpu: "25m" + # memory: "128Mi" + +sidecarResources: {} + # 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: "" + +podSecurityPolicy: + create: false + name: "" + spec: + privileged: true + fsGroup: + rule: RunAsAny + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - secret + - configMap + - persistentVolumeClaim + +persistence: + enabled: true + labels: + # Add default labels for the volumeClaimTemplate fo 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: + labels: {} + labelsHeadless: {} + type: ClusterIP + nodePort: "" + annotations: {} + httpPortName: http + transportPortName: transport + loadBalancerIP: "" + loadBalancerSourceRanges: [] + +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.8/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 publically 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" + path: / + hosts: + - chart-example.local + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +nameOverride: "" +fullnameOverride: "" + +# https://github.com/elastic/helm-charts/issues/63 +masterTerminationFix: false + +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: [] + +# Deprecated +# please use the above podSecurityContext.fsGroup instead +fsGroup: "" diff --git a/rds/base/charts/jaeger/charts/kafka-0.20.6.tgz b/rds/base/charts/jaeger/charts/kafka-0.20.6.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d8fa005c01289570b99f9584452b34a9d889da44 GIT binary patch literal 32345 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 literal 0 HcmV?d00001 diff --git a/rds/base/charts/jaeger/charts/kafka/.helmignore b/rds/base/charts/jaeger/charts/kafka/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/.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/rds/base/charts/jaeger/charts/kafka/Chart.yaml b/rds/base/charts/jaeger/charts/kafka/Chart.yaml new file mode 100644 index 0000000..ca65043 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +appVersion: 5.0.1 +description: Apache Kafka is publish-subscribe messaging rethought as a distributed + commit log. +home: https://kafka.apache.org/ +icon: https://kafka.apache.org/images/logo.png +keywords: +- kafka +- zookeeper +- kafka statefulset +maintainers: +- email: faraaz@rationalizeit.us + name: faraazkhan +- email: marc.villacorta@gmail.com + name: h0tbird +- email: ben@spothero.com + name: benjigoldberg +name: kafka +sources: +- https://github.com/kubernetes/charts/tree/master/incubator/zookeeper +- https://github.com/Yolean/kubernetes-kafka +- https://github.com/confluentinc/cp-docker-images +- https://github.com/apache/kafka +version: 0.21.2 diff --git a/rds/base/charts/jaeger/charts/kafka/OWNERS b/rds/base/charts/jaeger/charts/kafka/OWNERS new file mode 100644 index 0000000..0ed92ba --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/OWNERS @@ -0,0 +1,4 @@ +approvers: +- benjigoldberg +reviewers: +- benjigoldberg diff --git a/rds/base/charts/jaeger/charts/kafka/README.md b/rds/base/charts/jaeger/charts/kafka/README.md new file mode 100644 index 0000000..d0011e0 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/README.md @@ -0,0 +1,434 @@ +# Apache Kafka Helm Chart + +This is an implementation of Kafka StatefulSet found here: + + * https://github.com/Yolean/kubernetes-kafka + +## Pre Requisites: + +* Kubernetes 1.3 with alpha APIs enabled and support for storage classes + +* PV support on underlying infrastructure + +* Requires at least `v2.0.0-beta.1` version of helm to support + dependency management with requirements.yaml + +## StatefulSet Details + +* https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ + +## StatefulSet Caveats + +* https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#limitations + +## Chart Details + +This chart will do the following: + +* Implement a dynamically scalable kafka cluster using Kubernetes StatefulSets + +* Implement a dynamically scalable zookeeper cluster as another Kubernetes StatefulSet required for the Kafka cluster above + +* Expose Kafka protocol endpoints via NodePort services (optional) + +### Installing the Chart + +To install the chart with the release name `my-kafka` in the default +namespace: + +``` +$ helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator +$ helm install --name my-kafka incubator/kafka +``` + +If using a dedicated namespace(recommended) then make sure the namespace +exists with: + +``` +$ helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator +$ kubectl create ns kafka +$ helm install --name my-kafka --namespace kafka incubator/kafka +``` + +This chart includes a ZooKeeper chart as a dependency to the Kafka +cluster in its `requirement.yaml` by default. The chart can be customized using the +following configurable parameters: + +| Parameter | Description | Default | +|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `image` | Kafka Container image name | `confluentinc/cp-kafka` | +| `imageTag` | Kafka Container image tag | `5.0.1` | +| `imagePullPolicy` | Kafka Container pull policy | `IfNotPresent` | +| `replicas` | Kafka Brokers | `3` | +| `component` | Kafka k8s selector key | `kafka` | +| `resources` | Kafka resource requests and limits | `{}` | +| `securityContext` | Kafka containers security context | `{}` | +| `kafkaHeapOptions` | Kafka broker JVM heap options | `-Xmx1G-Xms1G` | +| `logSubPath` | Subpath under `persistence.mountPath` where kafka logs will be placed. | `logs` | +| `schedulerName` | Name of Kubernetes scheduler (other than the default) | `nil` | +| `serviceAccountName` | Name of Kubernetes serviceAccount. Useful when needing to pull images from custom repositories | `nil` | +| `priorityClassName` | Name of Kubernetes Pod PriorityClass. https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass | `nil` | +| `affinity` | Defines affinities and anti-affinities for pods as defined in: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity preferences | `{}` | +| `tolerations` | List of node tolerations for the pods. https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ | `[]` | +| `headless.annotations` | List of annotations for the headless service. https://kubernetes.io/docs/concepts/services-networking/service/#headless-services | `[]` | +| `headless.targetPort` | Target port to be used for the headless service. This is not a required value. | `nil` | +| `headless.port` | Port to be used for the headless service. https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ | `9092` | +| `external.enabled` | If True, exposes Kafka brokers via NodePort (PLAINTEXT by default) | `false` | +| `external.dns.useInternal` | If True, add Annotation for internal DNS service | `false` | +| `external.dns.useExternal` | If True, add Annotation for external DNS service | `true` | +| `external.servicePort` | TCP port configured at external services (one per pod) to relay from NodePort to the external listener port. | '19092' | +| `external.firstListenerPort` | TCP port which is added pod index number to arrive at the port used for NodePort and external listener port. | '31090' | +| `external.domain` | Domain in which to advertise Kafka external listeners. | `cluster.local` | +| `external.type` | Service Type. | `NodePort` | +| `external.distinct` | Distinct DNS entries for each created A record. | `false` | +| `external.annotations` | Additional annotations for the external service. | `{}` | +| `external.labels` | Additional labels for the external service. | `{}` | +| `external.loadBalancerIP` | Add Static IP to the type Load Balancer. Depends on the provider if enabled | `[]` +| `external.loadBalancerSourceRanges` | Add IP ranges that are allowed to access the Load Balancer. | `[]` +| `podAnnotations` | Annotation to be added to Kafka pods | `{}` | +| `podLabels` | Labels to be added to Kafka pods | `{}` | +| `podDisruptionBudget` | Define a Disruption Budget for the Kafka Pods | `{}` | +| `envOverrides` | Add additional Environment Variables in the dictionary format | `{ zookeeper.sasl.enabled: "False" }` | +| `configurationOverrides` | `Kafka ` [configuration setting][brokerconfigs] overrides in the dictionary format | `{ "confluent.support.metrics.enable": false }` | +| `secrets` | Pass any secrets to the kafka pods. Each secret will be passed as an environment variable by default. The secret can also be mounted to a specific path if required. Environment variable names are generated as: `_` (All upper case) | `{}` | +| `additionalPorts` | Additional ports to expose on brokers. Useful when the image exposes metrics (like prometheus, etc.) through a javaagent instead of a sidecar | `{}` | +| `readinessProbe.initialDelaySeconds` | Number of seconds before probe is initiated. | `30` | +| `readinessProbe.periodSeconds` | How often (in seconds) to perform the probe. | `10` | +| `readinessProbe.timeoutSeconds` | Number of seconds after which the probe times out. | `5` | +| `readinessProbe.successThreshold` | Minimum consecutive successes for the probe to be considered successful after having failed. | `1` | +| `readinessProbe.failureThreshold` | After the probe fails this many times, pod will be marked Unready. | `3` | +| `terminationGracePeriodSeconds` | Wait up to this many seconds for a broker to shut down gracefully, after which it is killed | `60` | +| `updateStrategy` | StatefulSet update strategy to use. | `{ type: "OnDelete" }` | +| `podManagementPolicy` | Start and stop pods in Parallel or OrderedReady (one-by-one.) Can not change after first release. | `OrderedReady` | +| `persistence.enabled` | Use a PVC to persist data | `true` | +| `persistence.size` | Size of data volume | `1Gi` | +| `persistence.mountPath` | Mount path of data volume | `/opt/kafka/data` | +| `persistence.storageClass` | Storage class of backing PVC | `nil` | +| `jmx.configMap.enabled` | Enable the default ConfigMap for JMX | `true` | +| `jmx.configMap.overrideConfig` | Allows config file to be generated by passing values to ConfigMap | `{}` | +| `jmx.configMap.overrideName` | Allows setting the name of the ConfigMap to be used | `""` | +| `jmx.port` | The jmx port which JMX style metrics are exposed (note: these are not scrapeable by Prometheus) | `5555` | +| `jmx.whitelistObjectNames` | Allows setting which JMX objects you want to expose to via JMX stats to JMX Exporter | (see `values.yaml`) | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `prometheus.jmx.resources` | Allows setting resource limits for jmx sidecar container | `{}` | +| `prometheus.jmx.enabled` | Whether or not to expose JMX metrics to Prometheus | `false` | +| `prometheus.jmx.image` | JMX Exporter container image | `solsson/kafka-prometheus-jmx-exporter@sha256` | +| `prometheus.jmx.imageTag` | JMX Exporter container image tag | `a23062396cd5af1acdf76512632c20ea6be76885dfc20cd9ff40fb23846557e8` | +| `prometheus.jmx.interval` | Interval that Prometheus scrapes JMX metrics when using Prometheus Operator | `10s` | +| `prometheus.jmx.scrapeTimeout` | Timeout that Prometheus scrapes JMX metrics when using Prometheus Operator | `10s` | +| `prometheus.jmx.port` | JMX Exporter Port which exposes metrics in Prometheus format for scraping | `5556` | +| `prometheus.kafka.enabled` | Whether or not to create a separate Kafka exporter | `false` | +| `prometheus.kafka.image` | Kafka Exporter container image | `danielqsj/kafka-exporter` | +| `prometheus.kafka.imageTag` | Kafka Exporter container image tag | `v1.2.0` | +| `prometheus.kafka.interval` | Interval that Prometheus scrapes Kafka metrics when using Prometheus Operator | `10s` | +| `prometheus.kafka.scrapeTimeout` | Timeout that Prometheus scrapes Kafka metrics when using Prometheus Operator | `10s` | +| `prometheus.kafka.port` | Kafka Exporter Port which exposes metrics in Prometheus format for scraping | `9308` | +| `prometheus.kafka.resources` | Allows setting resource limits for kafka-exporter pod | `{}` | +| `prometheus.kafka.affinity` | Defines affinities and anti-affinities for pods as defined in: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity preferences | `{}` | +| `prometheus.kafka.tolerations` | List of node tolerations for the pods. https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ | `[]` | +| `prometheus.operator.enabled` | True if using the Prometheus Operator, False if not | `false` | +| `prometheus.operator.serviceMonitor.namespace` | Namespace in which to install the ServiceMonitor resource. Default to kube-prometheus install. | `monitoring` | +| `prometheus.operator.serviceMonitor.releaseNamespace` | Set namespace to release namespace. Default false | `false` | +| `prometheus.operator.serviceMonitor.selector` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install | `{ prometheus: kube-prometheus }` | +| `prometheus.operator.prometheusRule.enabled` | True to create a PrometheusRule resource for Prometheus Operator, False if not | `false` | +| `prometheus.operator.prometheusRule.namespace` | Namespace in which to install the PrometheusRule resource. Default to kube-prometheus install. | `monitoring` | +| `prometheus.operator.prometheusRule.releaseNamespace` | Set namespace to release namespace. Default false | `false` | +| `prometheus.operator.prometheusRule.selector` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install | `{ prometheus: kube-prometheus }` | +| `prometheus.operator.prometheusRule.rules` | Define the prometheus rules. See values file for examples | `{}` | +| `configJob.backoffLimit` | Number of retries before considering kafka-config job as failed | `6` | +| `topics` | List of topics to create & configure. Can specify name, partitions, replicationFactor, reassignPartitions, config. See values.yaml | `[]` (Empty list) | +| `testsEnabled` | Enable/disable the chart's tests | `true` | +| `zookeeper.enabled` | If True, installs Zookeeper Chart | `true` | +| `zookeeper.resources` | Zookeeper resource requests and limits | `{}` | +| `zookeeper.env` | Environmental variables provided to Zookeeper Zookeeper | `{ZK_HEAP_SIZE: "1G"}` | +| `zookeeper.storage` | Zookeeper Persistent volume size | `2Gi` | +| `zookeeper.image.PullPolicy` | Zookeeper Container pull policy | `IfNotPresent` | +| `zookeeper.url` | URL of Zookeeper Cluster (unneeded if installing Zookeeper Chart) | `""` | +| `zookeeper.port` | Port of Zookeeper Cluster | `2181` | +| `zookeeper.affinity` | Defines affinities and anti-affinities for pods as defined in: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity preferences | `{}` | +| `zookeeper.nodeSelector` | Node labels for pod assignment | `{}` | + +Specify parameters using `--set key=value[,key=value]` argument to `helm install` + +Alternatively a YAML file that specifies the values for the parameters can be provided like this: + +```bash +$ helm install --name my-kafka -f values.yaml incubator/kafka +``` + +### Connecting to Kafka from inside Kubernetes + +You can connect to Kafka by running a simple pod in the K8s cluster like this with a configuration like this: + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: testclient + namespace: kafka +spec: + containers: + - name: kafka + image: solsson/kafka:0.11.0.0 + command: + - sh + - -c + - "exec tail -f /dev/null" +``` + +Once you have the testclient pod above running, you can list all kafka +topics with: + +` kubectl -n kafka exec -ti testclient -- ./bin/kafka-topics.sh --zookeeper +my-release-zookeeper:2181 --list` + +Where `my-release` is the name of your helm release. + +## Extensions + +Kafka has a rich ecosystem, with lots of tools. This sections is intended to compile all of those tools for which a corresponding Helm chart has already been created. + +- [Schema-registry](https://github.com/kubernetes/charts/tree/master/incubator/schema-registry) - A confluent project that provides a serving layer for your metadata. It provides a RESTful interface for storing and retrieving Avro schemas. + +## Connecting to Kafka from outside Kubernetes + +### NodePort External Service Type + +Review and optionally override to enable the example text concerned with external access in `values.yaml`. + +Once configured, you should be able to reach Kafka via NodePorts, one per replica. In kops where private, +topology is enabled, this feature publishes an internal round-robin DNS record using the following naming +scheme. The external access feature of this chart was tested with kops on AWS using flannel networking. +If you wish to enable external access to Kafka running in kops, your security groups will likely need to +be adjusted to allow non-Kubernetes nodes (e.g. bastion) to access the Kafka external listener port range. + +``` +{{ .Release.Name }}.{{ .Values.external.domain }} +``` + +If `external.distinct` is set theses entries will be prefixed with the replica number or broker id. + +``` +{{ .Release.Name }}-.{{ .Values.external.domain }} +``` + +Port numbers for external access used at container and NodePort are unique to each container in the StatefulSet. +Using the default `external.firstListenerPort` number with a `replicas` value of `3`, the following +container and NodePorts will be opened for external access: `31090`, `31091`, `31092`. All of these ports should +be reachable from any host to NodePorts are exposed because Kubernetes routes each NodePort from entry node +to pod/container listening on the same port (e.g. `31091`). + +The `external.servicePort` at each external access service (one such service per pod) is a relay toward +the a `containerPort` with a number matching its respective `NodePort`. The range of NodePorts is set, but +should not actually listen, on all Kafka pods in the StatefulSet. As any given pod will listen only one +such port at a time, setting the range at every Kafka pod is a reasonably safe configuration. + +#### Example values.yml for external service type NodePort +The + lines are with the updated values. +``` + external: +- enabled: false ++ enabled: true + # type can be either NodePort or LoadBalancer + type: NodePort + # annotations: +@@ -170,14 +170,14 @@ configurationOverrides: + ## + ## Setting "advertised.listeners" here appends to "PLAINTEXT://${POD_IP}:9092,", ensure you update the domain + ## If external service type is Nodeport: +- # "advertised.listeners": |- +- # EXTERNAL://kafka.cluster.local:$((31090 + ${KAFKA_BROKER_ID})) ++ "advertised.listeners": |- ++ EXTERNAL://kafka.cluster.local:$((31090 + ${KAFKA_BROKER_ID})) + ## If external service type is LoadBalancer and distinct is true: + # "advertised.listeners": |- + # EXTERNAL://kafka-$((${KAFKA_BROKER_ID})).cluster.local:19092 + ## If external service type is LoadBalancer and distinct is false: + # "advertised.listeners": |- + # EXTERNAL://EXTERNAL://${LOAD_BALANCER_IP}:31090 + ## Uncomment to define the EXTERNAL Listener protocol +- # "listener.security.protocol.map": |- +- # PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT ++ "listener.security.protocol.map": |- ++ PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + + +$ kafkacat -b kafka.cluster.local:31090 -L +Metadata for all topics (from broker 0: kafka.cluster.local:31090/0): + 3 brokers: + broker 2 at kafka.cluster.local:31092 + broker 1 at kafka.cluster.local:31091 + broker 0 at kafka.cluster.local:31090 + 0 topics: + +$ kafkacat -b kafka.cluster.local:31090 -P -t test1 -p 0 +msg01 from external producer to topic test1 + +$ kafkacat -b kafka.cluster.local:31090 -C -t test1 -p 0 +msg01 from external producer to topic test1 +``` +### LoadBalancer External Service Type + +The load balancer external service type differs from the node port type by routing to the `external.servicePort` specified in the service for each statefulset container (if `external.distinct` is set). If `external.distinct` is false, `external.servicePort` is unused and will be set to the sum of `external.firstListenerPort` and the replica number. It is important to note that `external.firstListenerPort` does not have to be within the configured node port range for the cluster, however a node port will be allocated. + +#### Example values.yml and DNS setup for external service type LoadBalancer with external.distinct: true +The + lines are with the updated values. +``` + external: +- enabled: false ++ enabled: true + # type can be either NodePort or LoadBalancer +- type: NodePort ++ type: LoadBalancer + # annotations: + # service.beta.kubernetes.io/openstack-internal-load-balancer: "true" + dns: +@@ -138,10 +138,10 @@ external: + # If using external service type LoadBalancer and external dns, set distinct to true below. + # This creates an A record for each statefulset pod/broker. You should then map the + # A record of the broker to the EXTERNAL IP given by the LoadBalancer in your DNS server. +- distinct: false ++ distinct: true + servicePort: 19092 + firstListenerPort: 31090 +- domain: cluster.local ++ domain: example.com + loadBalancerIP: [] + init: + image: "lwolf/kubectl_deployer" +@@ -173,11 +173,11 @@ configurationOverrides: + # "advertised.listeners": |- + # EXTERNAL://kafka.cluster.local:$((31090 + ${KAFKA_BROKER_ID})) + ## If external service type is LoadBalancer and distinct is true: +- # "advertised.listeners": |- +- # EXTERNAL://kafka-$((${KAFKA_BROKER_ID})).cluster.local:19092 ++ "advertised.listeners": |- ++ EXTERNAL://kafka-$((${KAFKA_BROKER_ID})).example.com:19092 + ## Uncomment to define the EXTERNAL Listener protocol +- # "listener.security.protocol.map": |- +- # PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT ++ "listener.security.protocol.map": |- ++ PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + +$ kubectl -n kafka get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kafka ClusterIP 10.39.241.217 9092/TCP 2m39s +kafka-0-external LoadBalancer 10.39.242.45 35.200.238.174 19092:30108/TCP 2m39s +kafka-1-external LoadBalancer 10.39.241.90 35.244.44.162 19092:30582/TCP 2m39s +kafka-2-external LoadBalancer 10.39.243.160 35.200.149.80 19092:30539/TCP 2m39s +kafka-headless ClusterIP None 9092/TCP 2m39s +kafka-zookeeper ClusterIP 10.39.249.70 2181/TCP 2m39s +kafka-zookeeper-headless ClusterIP None 2181/TCP,3888/TCP,2888/TCP 2m39s + +DNS A record entries: +kafka-0.example.com A record 35.200.238.174 TTL 60sec +kafka-1.example.com A record 35.244.44.162 TTL 60sec +kafka-2.example.com A record 35.200.149.80 TTL 60sec + +$ ping kafka-0.example.com +PING kafka-0.example.com (35.200.238.174): 56 data bytes + +$ kafkacat -b kafka-0.example.com:19092 -L +Metadata for all topics (from broker 0: kafka-0.example.com:19092/0): + 3 brokers: + broker 2 at kafka-2.example.com:19092 + broker 1 at kafka-1.example.com:19092 + broker 0 at kafka-0.example.com:19092 + 0 topics: + +$ kafkacat -b kafka-0.example.com:19092 -P -t gkeTest -p 0 +msg02 for topic gkeTest + +$ kafkacat -b kafka-0.example.com:19092 -C -t gkeTest -p 0 +msg02 for topic gkeTest +``` + +#### Example values.yml and DNS setup for external service type LoadBalancer with external.distinct: false +The + lines are with the updated values. +``` + external: +- enabled: false ++ enabled: true + # type can be either NodePort or LoadBalancer +- type: NodePort ++ type: LoadBalancer + # annotations: + # service.beta.kubernetes.io/openstack-internal-load-balancer: "true" + dns: +@@ -138,10 +138,10 @@ external: + distinct: false + servicePort: 19092 + firstListenerPort: 31090 + domain: cluster.local + loadBalancerIP: [35.200.238.174,35.244.44.162,35.200.149.80] + init: + image: "lwolf/kubectl_deployer" +@@ -173,11 +173,11 @@ configurationOverrides: + # "advertised.listeners": |- + # EXTERNAL://kafka.cluster.local:$((31090 + ${KAFKA_BROKER_ID})) + ## If external service type is LoadBalancer and distinct is true: +- # "advertised.listeners": |- +- # EXTERNAL://kafka-$((${KAFKA_BROKER_ID})).cluster.local:19092 ++ "advertised.listeners": |- ++ EXTERNAL://${LOAD_BALANCER_IP}:31090 + ## Uncomment to define the EXTERNAL Listener protocol +- # "listener.security.protocol.map": |- +- # PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT ++ "listener.security.protocol.map": |- ++ PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + +$ kubectl -n kafka get svc +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +kafka ClusterIP 10.39.241.217 9092/TCP 2m39s +kafka-0-external LoadBalancer 10.39.242.45 35.200.238.174 31090:30108/TCP 2m39s +kafka-1-external LoadBalancer 10.39.241.90 35.244.44.162 31090:30582/TCP 2m39s +kafka-2-external LoadBalancer 10.39.243.160 35.200.149.80 31090:30539/TCP 2m39s +kafka-headless ClusterIP None 9092/TCP 2m39s +kafka-zookeeper ClusterIP 10.39.249.70 2181/TCP 2m39s +kafka-zookeeper-headless ClusterIP None 2181/TCP,3888/TCP,2888/TCP 2m39s + +$ kafkacat -b 35.200.238.174:31090 -L +Metadata for all topics (from broker 0: 35.200.238.174:31090/0): + 3 brokers: + broker 2 at 35.200.149.80:31090 + broker 1 at 35.244.44.162:31090 + broker 0 at 35.200.238.174:31090 + 0 topics: + +$ kafkacat -b 35.200.238.174:31090 -P -t gkeTest -p 0 +msg02 for topic gkeTest + +$ kafkacat -b 35.200.238.174:31090 -C -t gkeTest -p 0 +msg02 for topic gkeTest +``` + +## Known Limitations + +* Only supports storage options that have backends for persistent volume claims (tested mostly on AWS) +* KAFKA_PORT will be created as an envvar and brokers will fail to start when there is a service named `kafka` in the same namespace. We work around this be unsetting that envvar `unset KAFKA_PORT`. + +[brokerconfigs]: https://kafka.apache.org/documentation/#brokerconfigs + +## Prometheus Stats + +### Prometheus vs Prometheus Operator + +Standard Prometheus is the default monitoring option for this chart. This chart also supports the CoreOS Prometheus Operator, +which can provide additional functionality like automatically updating Prometheus and Alert Manager configuration. If you are +interested in installing the Prometheus Operator please see the [CoreOS repository](https://github.com/coreos/prometheus-operator/tree/master/helm) for more information or +read through the [CoreOS blog post introducing the Prometheus Operator](https://coreos.com/blog/the-prometheus-operator.html) + +### JMX Exporter + +The majority of Kafka statistics are provided via JMX and are exposed via the [Prometheus JMX Exporter](https://github.com/prometheus/jmx_exporter). + +The JMX Exporter is a general purpose prometheus provider which is intended for use with any Java application. Because of this, it produces a number of statistics which +may not be of interest. To help in reducing these statistics to their relevant components we have created a curated whitelist `whitelistObjectNames` for the JMX exporter. +This whitelist may be modified or removed via the values configuration. + +To accommodate compatibility with the Prometheus metrics, this chart performs transformations of raw JMX metrics. For example, broker names and topics names are incorporated +into the metric name instead of becoming a label. If you are curious to learn more about any default transformations to the chart metrics, please have reference the [configmap template](https://github.com/kubernetes/charts/blob/master/incubator/kafka/templates/jmx-configmap.yaml). + +### Kafka Exporter + +The [Kafka Exporter](https://github.com/danielqsj/kafka_exporter) is a complementary metrics exporter to the JMX Exporter. The Kafka Exporter provides additional statistics on Kafka Consumer Groups. diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/.helmignore b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/.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/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/Chart.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/Chart.yaml new file mode 100644 index 0000000..6e00654 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +appVersion: 3.5.5 +description: Centralized service for maintaining configuration information, naming, + providing distributed synchronization, and providing group services. +home: https://zookeeper.apache.org/ +icon: https://zookeeper.apache.org/images/zookeeper_small.gif +kubeVersion: ^1.10.0-0 +maintainers: +- email: lachlan.evenson@microsoft.com + name: lachie83 +- email: owensk@google.com + name: kow3ns +name: zookeeper +sources: +- https://github.com/apache/zookeeper +- https://github.com/kubernetes/contrib/tree/master/statefulsets/zookeeper +version: 2.1.0 diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/OWNERS b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/OWNERS new file mode 100644 index 0000000..dd9facd --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/OWNERS @@ -0,0 +1,6 @@ +approvers: +- lachie83 +- kow3ns +reviewers: +- lachie83 +- kow3ns diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/README.md b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/README.md new file mode 100644 index 0000000..c0f060e --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/README.md @@ -0,0 +1,145 @@ +# incubator/zookeeper + +This helm chart provides an implementation of the ZooKeeper [StatefulSet](http://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/) found in Kubernetes Contrib [Zookeeper StatefulSet](https://github.com/kubernetes/contrib/tree/master/statefulsets/zookeeper). + +## Prerequisites +* Kubernetes 1.10+ +* PersistentVolume support on the underlying infrastructure +* A dynamic provisioner for the PersistentVolumes +* A familiarity with [Apache ZooKeeper 3.5.x](https://zookeeper.apache.org/doc/r3.5.5/) + +## Chart Components +This chart will do the following: + +* Create a fixed size ZooKeeper ensemble using a [StatefulSet](http://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/). +* Create a [PodDisruptionBudget](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-disruption-budget/) so kubectl drain will respect the Quorum size of the ensemble. +* Create a [Headless Service](https://kubernetes.io/docs/concepts/services-networking/service/) to control the domain of the ZooKeeper ensemble. +* Create a Service configured to connect to the available ZooKeeper instance on the configured client port. +* Optionally apply a [Pod Anti-Affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#inter-pod-affinity-and-anti-affinity-beta-feature) to spread the ZooKeeper ensemble across nodes. +* Optionally start JMX Exporter and Zookeeper Exporter containers inside Zookeeper pods. +* Optionally create a job which creates Zookeeper chroots (e.g. `/kafka1`). +* Optionally create a Prometheus ServiceMonitor for each enabled exporter container + +## Installing the Chart +You can install the chart with the release name `zookeeper` as below. + +```console +$ helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator +$ helm install --name zookeeper incubator/zookeeper +``` + +If you do not specify a name, helm will select a name for you. + +### Installed Components +You can use `kubectl get` to view all of the installed components. + +```console{%raw} +$ kubectl get all -l app=zookeeper +NAME: zookeeper +LAST DEPLOYED: Wed Apr 11 17:09:48 2018 +NAMESPACE: default +STATUS: DEPLOYED + +RESOURCES: +==> v1beta1/PodDisruptionBudget +NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE +zookeeper N/A 1 1 2m + +==> v1/Service +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +zookeeper-headless ClusterIP None 2181/TCP,3888/TCP,2888/TCP 2m +zookeeper ClusterIP 10.98.179.165 2181/TCP 2m + +==> v1beta1/StatefulSet +NAME DESIRED CURRENT AGE +zookeeper 3 3 2m + +==> monitoring.coreos.com/v1/ServiceMonitor +NAME AGE +zookeeper 2m +zookeeper-exporter 2m +``` + +1. `statefulsets/zookeeper` is the StatefulSet created by the chart. +1. `po/zookeeper-<0|1|2>` are the Pods created by the StatefulSet. Each Pod has a single container running a ZooKeeper server. +1. `svc/zookeeper-headless` is the Headless Service used to control the network domain of the ZooKeeper ensemble. +1. `svc/zookeeper` is a Service that can be used by clients to connect to an available ZooKeeper server. +1. `servicemonitor/zookeeper` is a Prometheus ServiceMonitor which scrapes the jmx-exporter metrics endpoint +1. `servicemonitor/zookeeper-exporter` is a Prometheus ServiceMonitor which scrapes the zookeeper-exporter metrics endpoint + +## Configuration +You can specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +$ helm install --name my-release -f values.yaml incubator/zookeeper +``` + +## Default Values + +- You can find all user-configurable settings, their defaults and commentary about them in [values.yaml](values.yaml). + +## Deep Dive + +## Image Details +The image used for this chart is based on Alpine 3.9.0. + +## JVM Details +The Java Virtual Machine used for this chart is the OpenJDK JVM 8u192 JRE (headless). + +## ZooKeeper Details +The chart defaults to ZooKeeper 3.5 (latest released version). + +## Failover +You can test failover by killing the leader. Insert a key: +```console +$ kubectl exec zookeeper-0 -- bin/zkCli.sh create /foo bar; +$ kubectl exec zookeeper-2 -- bin/zkCli.sh get /foo; +``` + +Watch existing members: +```console +$ kubectl run --attach bbox --image=busybox --restart=Never -- sh -c 'while true; do for i in 0 1 2; do echo zk-${i} $(echo stats | nc -${i}.:2181 | grep Mode); sleep 1; done; done'; + +zk-2 Mode: follower +zk-0 Mode: follower +zk-1 Mode: leader +zk-2 Mode: follower +``` + +Delete Pods and wait for the StatefulSet controller to bring them back up: +```console +$ kubectl delete po -l app=zookeeper +$ kubectl get po --watch-only +NAME READY STATUS RESTARTS AGE +zookeeper-0 0/1 Running 0 35s +zookeeper-0 1/1 Running 0 50s +zookeeper-1 0/1 Pending 0 0s +zookeeper-1 0/1 Pending 0 0s +zookeeper-1 0/1 ContainerCreating 0 0s +zookeeper-1 0/1 Running 0 19s +zookeeper-1 1/1 Running 0 40s +zookeeper-2 0/1 Pending 0 0s +zookeeper-2 0/1 Pending 0 0s +zookeeper-2 0/1 ContainerCreating 0 0s +zookeeper-2 0/1 Running 0 19s +zookeeper-2 1/1 Running 0 41s +``` + +Check the previously inserted key: +```console +$ kubectl exec zookeeper-1 -- bin/zkCli.sh get /foo +ionid = 0x354887858e80035, negotiated timeout = 30000 + +WATCHER:: + +WatchedEvent state:SyncConnected type:None path:null +bar +``` + +## Scaling +ZooKeeper can not be safely scaled in versions prior to 3.5.x + +## Limitations +* Only supports storage options that have backends for persistent volume claims. diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/NOTES.txt b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/NOTES.txt new file mode 100644 index 0000000..6c5da85 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/NOTES.txt @@ -0,0 +1,7 @@ +Thank you for installing ZooKeeper on your Kubernetes cluster. More information +about ZooKeeper can be found at https://zookeeper.apache.org/doc/current/ + +Your connection string should look like: + {{ template "zookeeper.fullname" . }}-0.{{ template "zookeeper.fullname" . }}-headless:{{ .Values.service.ports.client.port }},{{ template "zookeeper.fullname" . }}-1.{{ template "zookeeper.fullname" . }}-headless:{{ .Values.service.ports.client.port }},... + +You can also use the client service {{ template "zookeeper.fullname" . }}:{{ .Values.service.ports.client.port }} to connect to an available ZooKeeper server. diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/_helpers.tpl b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/_helpers.tpl new file mode 100644 index 0000000..0e15107 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/_helpers.tpl @@ -0,0 +1,46 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "zookeeper.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "zookeeper.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "zookeeper.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the zookeeper headless service. +*/}} +{{- define "zookeeper.headless" -}} +{{- printf "%s-headless" (include "zookeeper.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the zookeeper chroots job. +*/}} +{{- define "zookeeper.chroots" -}} +{{- printf "%s-chroots" (include "zookeeper.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-jmx-exporter.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-jmx-exporter.yaml new file mode 100644 index 0000000..47c3f9b --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-jmx-exporter.yaml @@ -0,0 +1,20 @@ +{{- if .Values.exporters.jmx.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-jmx-exporter + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: + config.yml: |- + hostPort: 127.0.0.1:{{ .Values.env.JMXPORT }} + lowercaseOutputName: {{ .Values.exporters.jmx.config.lowercaseOutputName }} + rules: +{{ .Values.exporters.jmx.config.rules | toYaml | indent 6 }} + ssl: false + startDelaySeconds: {{ .Values.exporters.jmx.config.startDelaySeconds }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-script.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-script.yaml new file mode 100644 index 0000000..3afae07 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/config-script.yaml @@ -0,0 +1,113 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "zookeeper.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: server +data: + ok: | + #!/bin/sh + echo ruok | nc 127.0.0.1 ${1:-2181} + + ready: | + #!/bin/sh + echo ruok | nc 127.0.0.1 ${1:-2181} +'' + run: | + #!/bin/bash + + set -a + ROOT=$(echo /apache-zookeeper-*) + + ZK_USER=${ZK_USER:-"zookeeper"} + ZK_LOG_LEVEL=${ZK_LOG_LEVEL:-"INFO"} + ZK_DATA_DIR=${ZK_DATA_DIR:-"/data"} + ZK_DATA_LOG_DIR=${ZK_DATA_LOG_DIR:-"/data/log"} + ZK_CONF_DIR=${ZK_CONF_DIR:-"/conf"} + ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181} + ZK_SERVER_PORT=${ZK_SERVER_PORT:-2888} + ZK_ELECTION_PORT=${ZK_ELECTION_PORT:-3888} + ZK_TICK_TIME=${ZK_TICK_TIME:-2000} + ZK_INIT_LIMIT=${ZK_INIT_LIMIT:-10} + ZK_SYNC_LIMIT=${ZK_SYNC_LIMIT:-5} + ZK_HEAP_SIZE=${ZK_HEAP_SIZE:-2G} + ZK_MAX_CLIENT_CNXNS=${ZK_MAX_CLIENT_CNXNS:-60} + ZK_MIN_SESSION_TIMEOUT=${ZK_MIN_SESSION_TIMEOUT:- $((ZK_TICK_TIME*2))} + ZK_MAX_SESSION_TIMEOUT=${ZK_MAX_SESSION_TIMEOUT:- $((ZK_TICK_TIME*20))} + ZK_SNAP_RETAIN_COUNT=${ZK_SNAP_RETAIN_COUNT:-3} + ZK_PURGE_INTERVAL=${ZK_PURGE_INTERVAL:-0} + ID_FILE="$ZK_DATA_DIR/myid" + ZK_CONFIG_FILE="$ZK_CONF_DIR/zoo.cfg" + LOG4J_PROPERTIES="$ZK_CONF_DIR/log4j.properties" + HOST=$(hostname) + DOMAIN=`hostname -d` + ZOOCFG=zoo.cfg + ZOOCFGDIR=$ZK_CONF_DIR + JVMFLAGS="-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE" + + APPJAR=$(echo $ROOT/*jar) + CLASSPATH="${ROOT}/lib/*:${APPJAR}:${ZK_CONF_DIR}:" + + if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then + NAME=${BASH_REMATCH[1]} + ORD=${BASH_REMATCH[2]} + MY_ID=$((ORD+1)) + else + echo "Failed to extract ordinal from hostname $HOST" + exit 1 + fi + + mkdir -p $ZK_DATA_DIR + mkdir -p $ZK_DATA_LOG_DIR + echo $MY_ID >> $ID_FILE + + echo "clientPort=$ZK_CLIENT_PORT" >> $ZK_CONFIG_FILE + echo "dataDir=$ZK_DATA_DIR" >> $ZK_CONFIG_FILE + echo "dataLogDir=$ZK_DATA_LOG_DIR" >> $ZK_CONFIG_FILE + echo "tickTime=$ZK_TICK_TIME" >> $ZK_CONFIG_FILE + echo "initLimit=$ZK_INIT_LIMIT" >> $ZK_CONFIG_FILE + echo "syncLimit=$ZK_SYNC_LIMIT" >> $ZK_CONFIG_FILE + echo "maxClientCnxns=$ZK_MAX_CLIENT_CNXNS" >> $ZK_CONFIG_FILE + echo "minSessionTimeout=$ZK_MIN_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE + echo "maxSessionTimeout=$ZK_MAX_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE + echo "autopurge.snapRetainCount=$ZK_SNAP_RETAIN_COUNT" >> $ZK_CONFIG_FILE + echo "autopurge.purgeInterval=$ZK_PURGE_INTERVAL" >> $ZK_CONFIG_FILE + echo "4lw.commands.whitelist=*" >> $ZK_CONFIG_FILE + + for (( i=1; i<=$ZK_REPLICAS; i++ )) + do + echo "server.$i=$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT" >> $ZK_CONFIG_FILE + done + + rm -f $LOG4J_PROPERTIES + + echo "zookeeper.root.logger=$ZK_LOG_LEVEL, CONSOLE" >> $LOG4J_PROPERTIES + echo "zookeeper.console.threshold=$ZK_LOG_LEVEL" >> $LOG4J_PROPERTIES + echo "zookeeper.log.threshold=$ZK_LOG_LEVEL" >> $LOG4J_PROPERTIES + echo "zookeeper.log.dir=$ZK_DATA_LOG_DIR" >> $LOG4J_PROPERTIES + echo "zookeeper.log.file=zookeeper.log" >> $LOG4J_PROPERTIES + echo "zookeeper.log.maxfilesize=256MB" >> $LOG4J_PROPERTIES + echo "zookeeper.log.maxbackupindex=10" >> $LOG4J_PROPERTIES + echo "zookeeper.tracelog.dir=$ZK_DATA_LOG_DIR" >> $LOG4J_PROPERTIES + echo "zookeeper.tracelog.file=zookeeper_trace.log" >> $LOG4J_PROPERTIES + echo "log4j.rootLogger=\${zookeeper.root.logger}" >> $LOG4J_PROPERTIES + echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender" >> $LOG4J_PROPERTIES + echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}" >> $LOG4J_PROPERTIES + echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout" >> $LOG4J_PROPERTIES + echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n" >> $LOG4J_PROPERTIES + + if [ -n "$JMXDISABLE" ] + then + MAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain + else + MAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$JMXPORT -Dcom.sun.management.jmxremote.authenticate=$JMXAUTH -Dcom.sun.management.jmxremote.ssl=$JMXSSL -Dzookeeper.jmx.log4j.disable=$JMXLOG4J org.apache.zookeeper.server.quorum.QuorumPeerMain" + fi + + set -x + exec java -cp "$CLASSPATH" $JVMFLAGS $MAIN $ZK_CONFIG_FILE diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/job-chroots.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/job-chroots.yaml new file mode 100644 index 0000000..6c132c5 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/job-chroots.yaml @@ -0,0 +1,66 @@ +{{- if .Values.jobs.chroots.enabled }} +{{- $root := . }} +{{- $job := .Values.jobs.chroots }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "zookeeper.chroots" . }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: jobs + job: chroots +spec: + activeDeadlineSeconds: {{ $job.activeDeadlineSeconds }} + backoffLimit: {{ $job.backoffLimit }} + completions: {{ $job.completions }} + parallelism: {{ $job.parallelism }} + template: + metadata: + labels: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} + component: jobs + job: chroots + spec: + restartPolicy: {{ $job.restartPolicy }} +{{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" +{{- end }} + containers: + - name: main + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/bash + - -o + - pipefail + - -euc + {{- $port := .Values.service.ports.client.port }} + - > + sleep 15; + export SERVER={{ template "zookeeper.fullname" $root }}:{{ $port }}; + {{- range $job.config.create }} + echo '==> {{ . }}'; + echo '====> Create chroot if does not exist.'; + zkCli.sh -server {{ template "zookeeper.fullname" $root }}:{{ $port }} get {{ . }} 2>&1 >/dev/null | grep 'cZxid' + || zkCli.sh -server {{ template "zookeeper.fullname" $root }}:{{ $port }} create {{ . }} ""; + echo '====> Confirm chroot exists.'; + zkCli.sh -server {{ template "zookeeper.fullname" $root }}:{{ $port }} get {{ . }} 2>&1 >/dev/null | grep 'cZxid'; + echo '====> Chroot exists.'; + {{- end }} + env: + {{- range $key, $value := $job.env }} + - name: {{ $key | upper | replace "." "_" }} + value: {{ $value | quote }} + {{- end }} + resources: +{{ toYaml $job.resources | indent 12 }} +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..ff1d2c0 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/poddisruptionbudget.yaml @@ -0,0 +1,18 @@ +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "zookeeper.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: server +spec: + selector: + matchLabels: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} + component: server +{{ toYaml .Values.podDisruptionBudget | indent 2 }} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service-headless.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service-headless.yaml new file mode 100644 index 0000000..57dd9db --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service-headless.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "zookeeper.headless" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.headless.annotations }} + annotations: +{{ .Values.headless.annotations | toYaml | trimSuffix "\n" | indent 4 }} +{{- end }} +spec: + clusterIP: None + ports: +{{- range $key, $port := .Values.ports }} + - name: {{ $key }} + port: {{ $port.containerPort }} + targetPort: {{ $key }} + protocol: {{ $port.protocol }} +{{- end }} + selector: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service.yaml new file mode 100644 index 0000000..6e8287c --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/service.yaml @@ -0,0 +1,42 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "zookeeper.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- if .Values.service.annotations }}} + annotations: +{{- with .Values.service.annotations }} +{{ toYaml . | indent 4 }} +{{- end }} +{{- end }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range $key, $value := .Values.service.ports }} + - name: {{ $key }} +{{ toYaml $value | indent 6 }} + {{- end }} +{{- if .Values.exporters.jmx.enabled }} + {{- range $key, $port := .Values.exporters.jmx.ports }} + - name: {{ $key }} + port: {{ $port.containerPort }} + targetPort: {{ $key }} + protocol: {{ $port.protocol }} + {{- end }} +{{- end}} +{{- if .Values.exporters.zookeeper.enabled }} + {{- range $key, $port := .Values.exporters.zookeeper.ports }} + - name: {{ $key }} + port: {{ $port.containerPort }} + targetPort: {{ $key }} + protocol: {{ $port.protocol }} + {{- end }} +{{- end}} + selector: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/servicemonitors.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/servicemonitors.yaml new file mode 100644 index 0000000..20621b9 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/servicemonitors.yaml @@ -0,0 +1,60 @@ +{{- if and .Values.exporters.jmx.enabled .Values.prometheus.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "zookeeper.fullname" . }} + {{- if .Values.prometheus.serviceMonitor.namespace }} + namespace: {{ .Values.prometheus.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: +{{ toYaml .Values.prometheus.serviceMonitor.selector | indent 4 }} +spec: + endpoints: + {{- range $key, $port := .Values.exporters.jmx.ports }} + - port: {{ $key }} + path: {{ $.Values.exporters.jmx.path }} + interval: {{ $.Values.exporters.jmx.serviceMonitor.interval }} + scrapeTimeout: {{ $.Values.exporters.jmx.serviceMonitor.scrapeTimeout }} + scheme: {{ $.Values.exporters.jmx.serviceMonitor.scheme }} + {{- end }} + selector: + matchLabels: + app: {{ include "zookeeper.name" . }} + release: {{ .Release.Name }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end }} +--- + +{{- if and .Values.exporters.zookeeper.enabled .Values.prometheus.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "zookeeper.fullname" . }}-exporter + {{- if .Values.prometheus.serviceMonitor.namespace }} + namespace: {{ .Values.prometheus.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: +{{ toYaml .Values.prometheus.serviceMonitor.selector | indent 4 }} +spec: + endpoints: + {{- range $key, $port := .Values.exporters.zookeeper.ports }} + - port: {{ $key }} + path: {{ $.Values.exporters.zookeeper.path }} + interval: {{ $.Values.exporters.zookeeper.serviceMonitor.interval }} + scrapeTimeout: {{ $.Values.exporters.zookeeper.serviceMonitor.scrapeTimeout }} + scheme: {{ $.Values.exporters.zookeeper.serviceMonitor.scheme }} + {{- end }} + selector: + matchLabels: + app: {{ include "zookeeper.name" . }} + release: {{ .Release.Name }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end }} \ No newline at end of file diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/statefulset.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/statefulset.yaml new file mode 100644 index 0000000..a2fede1 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/templates/statefulset.yaml @@ -0,0 +1,227 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "zookeeper.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "zookeeper.name" . }} + chart: {{ template "zookeeper.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + component: server +spec: + serviceName: {{ template "zookeeper.headless" . }} + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} + component: server + updateStrategy: +{{ toYaml .Values.updateStrategy | indent 4 }} + template: + metadata: + labels: + app: {{ template "zookeeper.name" . }} + release: {{ .Release.Name }} + component: server + {{- if .Values.podLabels }} + ## Custom pod labels + {{- range $key, $value := .Values.podLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} +{{- if .Values.podAnnotations }} + annotations: + ## Custom pod annotations + {{- range $key, $value := .Values.podAnnotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} +{{- end }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} +{{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" +{{- end }} + securityContext: +{{ toYaml .Values.securityContext | indent 8 }} +{{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" +{{- end }} + containers: + + - name: zookeeper + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.command }} + command: {{ range . }} + - {{ . | quote }} + {{- end }} + {{- end }} + ports: +{{- range $key, $port := .Values.ports }} + - name: {{ $key }} +{{ toYaml $port | indent 14 }} +{{- end }} + livenessProbe: + exec: + command: + - sh + - /config-scripts/ok + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 2 + successThreshold: 1 + readinessProbe: + exec: + command: + - sh + - /config-scripts/ready + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 2 + successThreshold: 1 + env: + - name: ZK_REPLICAS + value: {{ .Values.replicaCount | quote }} + {{- range $key, $value := .Values.env }} + - name: {{ $key | upper | replace "." "_" }} + value: {{ $value | quote }} + {{- end }} + {{- range $secret := .Values.secrets }} + {{- range $key := $secret.keys }} + - name: {{ (print $secret.name "_" $key) | upper }} + valueFrom: + secretKeyRef: + name: {{ $secret.name }} + key: {{ $key }} + {{- end }} + {{- end }} + resources: +{{ toYaml .Values.resources | indent 12 }} + volumeMounts: + - name: data + mountPath: /data + {{- range $secret := .Values.secrets }} + {{- if $secret.mountPath }} + {{- range $key := $secret.keys }} + - name: {{ $.Release.Name }}-{{ $secret.name }} + mountPath: {{ $secret.mountPath }}/{{ $key }} + subPath: {{ $key }} + readOnly: true + {{- end }} + {{- end }} + {{- end }} + - name: config + mountPath: /config-scripts + + +{{- if .Values.exporters.jmx.enabled }} + - name: jmx-exporter + image: "{{ .Values.exporters.jmx.image.repository }}:{{ .Values.exporters.jmx.image.tag }}" + imagePullPolicy: {{ .Values.exporters.jmx.image.pullPolicy }} + ports: + {{- range $key, $port := .Values.exporters.jmx.ports }} + - name: {{ $key }} +{{ toYaml $port | indent 14 }} + {{- end }} + livenessProbe: +{{ toYaml .Values.exporters.jmx.livenessProbe | indent 12 }} + readinessProbe: +{{ toYaml .Values.exporters.jmx.readinessProbe | indent 12 }} + env: + - name: SERVICE_PORT + value: {{ .Values.exporters.jmx.ports.jmxxp.containerPort | quote }} + {{- with .Values.exporters.jmx.env }} + {{- range $key, $value := . }} + - name: {{ $key | upper | replace "." "_" }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + resources: +{{ toYaml .Values.exporters.jmx.resources | indent 12 }} + volumeMounts: + - name: config-jmx-exporter + mountPath: /opt/jmx_exporter/config.yml + subPath: config.yml +{{- end }} + +{{- if .Values.exporters.zookeeper.enabled }} + - name: zookeeper-exporter + image: "{{ .Values.exporters.zookeeper.image.repository }}:{{ .Values.exporters.zookeeper.image.tag }}" + imagePullPolicy: {{ .Values.exporters.zookeeper.image.pullPolicy }} + args: + - -bind-addr=:{{ .Values.exporters.zookeeper.ports.zookeeperxp.containerPort }} + - -metrics-path={{ .Values.exporters.zookeeper.path }} + - -zookeeper=localhost:{{ .Values.ports.client.containerPort }} + - -log-level={{ .Values.exporters.zookeeper.config.logLevel }} + - -reset-on-scrape={{ .Values.exporters.zookeeper.config.resetOnScrape }} + ports: + {{- range $key, $port := .Values.exporters.zookeeper.ports }} + - name: {{ $key }} +{{ toYaml $port | indent 14 }} + {{- end }} + livenessProbe: +{{ toYaml .Values.exporters.zookeeper.livenessProbe | indent 12 }} + readinessProbe: +{{ toYaml .Values.exporters.zookeeper.readinessProbe | indent 12 }} + env: + {{- range $key, $value := .Values.exporters.zookeeper.env }} + - name: {{ $key | upper | replace "." "_" }} + value: {{ $value | quote }} + {{- end }} + resources: +{{ toYaml .Values.exporters.zookeeper.resources | indent 12 }} +{{- end }} + + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ template "zookeeper.fullname" . }} + defaultMode: 0555 + {{- range .Values.secrets }} + - name: {{ $.Release.Name }}-{{ .name }} + secret: + secretName: {{ .name }} + {{- end }} + {{- if .Values.exporters.jmx.enabled }} + - name: config-jmx-exporter + configMap: + name: {{ .Release.Name }}-jmx-exporter + {{- end }} + {{- if not .Values.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.persistence.storageClass }} + {{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" + {{- end }} + {{- end }} + {{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/values.yaml b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/values.yaml new file mode 100644 index 0000000..2fa6286 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/charts/zookeeper/values.yaml @@ -0,0 +1,295 @@ +## As weighted quorums are not supported, it is imperative that an odd number of replicas +## be chosen. Moreover, the number of replicas should be either 1, 3, 5, or 7. +## +## ref: https://github.com/kubernetes/contrib/tree/master/statefulsets/zookeeper#stateful-set +replicaCount: 3 # Desired quantity of ZooKeeper pods. This should always be (1,3,5, or 7) + +podDisruptionBudget: + maxUnavailable: 1 # Limits how many Zokeeper pods may be unavailable due to voluntary disruptions. + +terminationGracePeriodSeconds: 1800 # Duration in seconds a Zokeeper pod needs to terminate gracefully. + +updateStrategy: + type: RollingUpdate + +## refs: +## - https://github.com/kubernetes/contrib/tree/master/statefulsets/zookeeper +## - https://github.com/kubernetes/contrib/blob/master/statefulsets/zookeeper/Makefile#L1 +image: + repository: zookeeper # Container image repository for zookeeper container. + tag: 3.5.5 # Container image tag for zookeeper container. + pullPolicy: IfNotPresent # Image pull criteria for zookeeper container. + +service: + type: ClusterIP # Exposes zookeeper on a cluster-internal IP. + annotations: {} # Arbitrary non-identifying metadata for zookeeper service. + ## AWS example for use with LoadBalancer service type. + # external-dns.alpha.kubernetes.io/hostname: zookeeper.cluster.local + # service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" + # service.beta.kubernetes.io/aws-load-balancer-internal: "true" + ports: + client: + port: 2181 # Service port number for client port. + targetPort: client # Service target port for client port. + protocol: TCP # Service port protocol for client port. + +## Headless service. +## +headless: + annotations: {} + +ports: + client: + containerPort: 2181 # Port number for zookeeper container client port. + protocol: TCP # Protocol for zookeeper container client port. + election: + containerPort: 3888 # Port number for zookeeper container election port. + protocol: TCP # Protocol for zookeeper container election port. + server: + containerPort: 2888 # Port number for zookeeper container server port. + protocol: TCP # Protocol for zookeeper container server port. + +resources: {} # Optionally specify how much CPU and memory (RAM) each zookeeper container needs. + # 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 + +priorityClassName: "" + +nodeSelector: {} # Node label-values required to run zookeeper pods. + +tolerations: [] # Node taint overrides for zookeeper pods. + +affinity: {} # Criteria by which pod label-values influence scheduling for zookeeper pods. + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - topologyKey: "kubernetes.io/hostname" + # labelSelector: + # matchLabels: + # release: zookeeper + +podAnnotations: {} # Arbitrary non-identifying metadata for zookeeper pods. + # prometheus.io/scrape: "true" + # prometheus.io/path: "/metrics" + # prometheus.io/port: "9141" + +podLabels: {} # Key/value pairs that are attached to zookeeper pods. + # team: "developers" + # service: "zookeeper" + +securityContext: + fsGroup: 1000 + runAsUser: 1000 + +## Useful, if you want to use an alternate image. +command: + - /bin/bash + - -xec + - /config-scripts/run + +## Useful if using any custom authorizer. +## Pass any secrets to the kafka pods. Each secret will be passed as an +## environment variable by default. The secret can also be mounted to a +## specific path (in addition to environment variable) if required. Environment +## variable names are generated as: `_` (All upper case) +# secrets: +# - name: myKafkaSecret +# keys: +# - username +# - password +# # mountPath: /opt/kafka/secret +# - name: myZkSecret +# keys: +# - user +# - pass +# mountPath: /opt/zookeeper/secret + +persistence: + enabled: true + ## zookeeper 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: "-" + accessMode: ReadWriteOnce + size: 5Gi + +## Exporters query apps for metrics and make those metrics available for +## Prometheus to scrape. +exporters: + + jmx: + enabled: false + image: + repository: sscaling/jmx-prometheus-exporter + tag: 0.3.0 + pullPolicy: IfNotPresent + config: + lowercaseOutputName: false + ## ref: https://github.com/prometheus/jmx_exporter/blob/master/example_configs/zookeeper.yaml + rules: + - pattern: "org.apache.ZooKeeperService<>(\\w+)" + name: "zookeeper_$2" + - pattern: "org.apache.ZooKeeperService<>(\\w+)" + name: "zookeeper_$3" + labels: + replicaId: "$2" + - pattern: "org.apache.ZooKeeperService<>(\\w+)" + name: "zookeeper_$4" + labels: + replicaId: "$2" + memberType: "$3" + - pattern: "org.apache.ZooKeeperService<>(\\w+)" + name: "zookeeper_$4_$5" + labels: + replicaId: "$2" + memberType: "$3" + startDelaySeconds: 30 + env: {} + resources: {} + path: /metrics + ports: + jmxxp: + containerPort: 9404 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: jmxxp + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 60 + failureThreshold: 8 + successThreshold: 1 + readinessProbe: + httpGet: + path: /metrics + port: jmxxp + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 60 + failureThreshold: 8 + successThreshold: 1 + serviceMonitor: + interval: 30s + scrapeTimeout: 30s + scheme: http + + zookeeper: + ## refs: + ## - https://github.com/carlpett/zookeeper_exporter + ## - https://hub.docker.com/r/josdotso/zookeeper-exporter/ + ## - https://www.datadoghq.com/blog/monitoring-kafka-performance-metrics/#zookeeper-metrics + enabled: false + image: + repository: josdotso/zookeeper-exporter + tag: v1.1.2 + pullPolicy: IfNotPresent + config: + logLevel: info + resetOnScrape: "true" + env: {} + resources: {} + path: /metrics + ports: + zookeeperxp: + containerPort: 9141 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: zookeeperxp + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 60 + failureThreshold: 8 + successThreshold: 1 + readinessProbe: + httpGet: + path: /metrics + port: zookeeperxp + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 60 + failureThreshold: 8 + successThreshold: 1 + serviceMonitor: + interval: 30s + scrapeTimeout: 30s + scheme: http + +## ServiceMonitor configuration in case you are using Prometheus Operator +prometheus: + serviceMonitor: + ## If true a ServiceMonitor for each enabled exporter will be installed + enabled: false + ## The namespace where the ServiceMonitor(s) will be installed + # namespace: monitoring + ## The selector the Prometheus instance is searching for + ## [Default Prometheus Operator selector] (https://github.com/helm/charts/blob/f5a751f174263971fafd21eee4e35416d6612a3d/stable/prometheus-operator/templates/prometheus/prometheus.yaml#L74) + selector: {} + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## ref: https://github.com/kubernetes/contrib/tree/master/statefulsets/zookeeper +env: + + ## Options related to JMX exporter. + ## ref: https://github.com/apache/zookeeper/blob/master/bin/zkServer.sh#L36 + JMXAUTH: "false" + JMXDISABLE: "false" + JMXPORT: 1099 + JMXSSL: "false" + + ## The port on which the server will accept client requests. + ZOO_PORT: 2181 + + ## The number of Ticks that an ensemble member is allowed to perform leader + ## election. + ZOO_INIT_LIMIT: 5 + + ZOO_TICK_TIME: 2000 + + ## The maximum number of concurrent client connections that + ## a server in the ensemble will accept. + ZOO_MAX_CLIENT_CNXNS: 60 + + ## The number of Tick by which a follower may lag behind the ensembles leader. + ZK_SYNC_LIMIT: 10 + + ## The number of wall clock ms that corresponds to a Tick for the ensembles + ## internal time. + ZK_TICK_TIME: 2000 + + ZOO_AUTOPURGE_PURGEINTERVAL: 0 + ZOO_AUTOPURGE_SNAPRETAINCOUNT: 3 + ZOO_STANDALONE_ENABLED: false + +jobs: + ## ref: http://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkSessions + chroots: + enabled: false + activeDeadlineSeconds: 300 + backoffLimit: 5 + completions: 1 + config: + create: [] + # - /kafka + # - /ureplicator + env: [] + parallelism: 1 + resources: {} + restartPolicy: Never diff --git a/rds/base/charts/jaeger/charts/kafka/requirements.lock b/rds/base/charts/jaeger/charts/kafka/requirements.lock new file mode 100644 index 0000000..35c0583 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/requirements.lock @@ -0,0 +1,6 @@ +dependencies: +- name: zookeeper + repository: file://charts/zookeeper + version: 2.1.0 +digest: sha256:15b2dd453a6aeb0ecc8193bbde64cb23af32b98605e67240286eb4e7b84e361d +generated: "2022-08-18T15:37:24.60553345+02:00" diff --git a/rds/base/charts/jaeger/charts/kafka/requirements.yaml b/rds/base/charts/jaeger/charts/kafka/requirements.yaml new file mode 100644 index 0000000..ef02ce4 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/requirements.yaml @@ -0,0 +1,6 @@ +dependencies: +- name: zookeeper + version: 2.1.0 + repository: file://charts/zookeeper + condition: kafka.zookeeper.enabled,zookeeper.enabled +version: 0.20.6 \ No newline at end of file diff --git a/rds/base/charts/jaeger/charts/kafka/templates/NOTES.txt b/rds/base/charts/jaeger/charts/kafka/templates/NOTES.txt new file mode 100644 index 0000000..9609f39 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/NOTES.txt @@ -0,0 +1,76 @@ +### Connecting to Kafka from inside Kubernetes + +You can connect to Kafka by running a simple pod in the K8s cluster like this with a configuration like this: + + apiVersion: v1 + kind: Pod + metadata: + name: testclient + namespace: {{ .Values.global.namespace.name | default .Release.Namespace }} + spec: + containers: + - name: kafka + image: {{ .Values.image }}:{{ .Values.imageTag }} + command: + - sh + - -c + - "exec tail -f /dev/null" + +Once you have the testclient pod above running, you can list all kafka +topics with: + + kubectl -n {{ .Release.Namespace }} exec testclient -- ./bin/kafka-topics.sh --zookeeper {{ .Release.Name }}-zookeeper:2181 --list + +To create a new topic: + + kubectl -n {{ .Release.Namespace }} exec testclient -- ./bin/kafka-topics.sh --zookeeper {{ .Release.Name }}-zookeeper:2181 --topic test1 --create --partitions 1 --replication-factor 1 + +To listen for messages on a topic: + + kubectl -n {{ .Release.Namespace }} exec -ti testclient -- ./bin/kafka-console-consumer.sh --bootstrap-server {{ include "kafka.fullname" . }}:9092 --topic test1 --from-beginning + +To stop the listener session above press: Ctrl+C + +To start an interactive message producer session: + kubectl -n {{ .Release.Namespace }} exec -ti testclient -- ./bin/kafka-console-producer.sh --broker-list {{ include "kafka.fullname" . }}-headless:9092 --topic test1 + +To create a message in the above session, simply type the message and press "enter" +To end the producer session try: Ctrl+C + +If you specify "zookeeper.connect" in configurationOverrides, please replace "{{ .Release.Name }}-zookeeper:2181" with the value of "zookeeper.connect", or you will get error. + +{{ if .Values.external.enabled }} +### Connecting to Kafka from outside Kubernetes + +You have enabled the external access feature of this chart. + +**WARNING:** By default this feature allows Kafka clients outside Kubernetes to +connect to Kafka via NodePort(s) in `PLAINTEXT`. + +Please see this chart's README.md for more details and guidance. + +If you wish to connect to Kafka from outside please configure your external Kafka +clients to point at the following brokers. Please allow a few minutes for all +associated resources to become healthy. + {{ $fullName := include "kafka.fullname" . }} + {{- $replicas := .Values.replicas | int }} + {{- $servicePort := .Values.external.servicePort | int}} + {{- $root := . }} + {{- range $i, $e := until $replicas }} + {{- $externalListenerPort := add $root.Values.external.firstListenerPort $i }} + {{- if $root.Values.external.distinct }} +{{ printf "%s-%d.%s:%d" $root.Release.Name $i $root.Values.external.domain $servicePort | indent 2 }} + {{- else }} +{{ printf "%s.%s:%d" $root.Release.Name $root.Values.external.domain $externalListenerPort | indent 2 }} + {{- end }} + {{- end }} +{{- end }} + +{{ if .Values.prometheus.jmx.enabled }} +To view JMX configuration (pull request/updates to improve defaults are encouraged): + {{ if .Values.jmx.configMap.overrideName }} + kubectl -n {{ .Release.Namespace }} describe configmap {{ .Values.jmx.configMap.overrideName }} + {{ else }} + kubectl -n {{ .Release.Namespace }} describe configmap {{ include "kafka.fullname" . }}-metrics + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/_helpers.tpl b/rds/base/charts/jaeger/charts/kafka/templates/_helpers.tpl new file mode 100644 index 0000000..03bfc0a --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/_helpers.tpl @@ -0,0 +1,128 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "kafka.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "kafka.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 -}} + +{{/* +Create a default fully qualified zookeeper name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "kafka.zookeeper.fullname" -}} +{{- if .Values.zookeeper.fullnameOverride -}} +{{- .Values.zookeeper.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "zookeeper" .Values.zookeeper.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Form the Zookeeper URL. If zookeeper is installed as part of this chart, use k8s service discovery, +else use user-provided URL +*/}} +{{- define "zookeeper.url" }} +{{- $port := .Values.zookeeper.port | toString }} +{{- if .Values.zookeeper.enabled -}} +{{- printf "%s:%s" (include "kafka.zookeeper.fullname" .) $port }} +{{- else -}} +{{- $zookeeperConnect := printf "%s:%s" .Values.zookeeper.url $port }} +{{- $zookeeperConnectOverride := index .Values "configurationOverrides" "zookeeper.connect" }} +{{- default $zookeeperConnect $zookeeperConnectOverride }} +{{- end -}} +{{- end -}} + +{{/* +Derive offsets.topic.replication.factor in following priority order: configurationOverrides, replicas +*/}} +{{- define "kafka.replication.factor" }} +{{- $replicationFactorOverride := index .Values "configurationOverrides" "offsets.topic.replication.factor" }} +{{- default .Values.replicas $replicationFactorOverride }} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "kafka.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create unified labels for kafka components +*/}} + +{{- define "kafka.common.matchLabels" -}} +app.kubernetes.io/name: {{ include "kafka.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{- define "kafka.common.metaLabels" -}} +helm.sh/chart: {{ include "kafka.chart" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "kafka.broker.matchLabels" -}} +app.kubernetes.io/component: kafka-broker +{{ include "kafka.common.matchLabels" . }} +{{- end -}} + +{{- define "kafka.broker.labels" -}} +{{ include "kafka.common.metaLabels" . }} +{{ include "kafka.broker.matchLabels" . }} +{{- end -}} + +{{- define "kafka.config.matchLabels" -}} +app.kubernetes.io/component: kafka-config +{{ include "kafka.common.matchLabels" . }} +{{- end -}} + +{{- define "kafka.config.labels" -}} +{{ include "kafka.common.metaLabels" . }} +{{ include "kafka.config.matchLabels" . }} +{{- end -}} + +{{- define "kafka.monitor.matchLabels" -}} +app.kubernetes.io/component: kafka-monitor +{{ include "kafka.common.matchLabels" . }} +{{- end -}} + +{{- define "kafka.monitor.labels" -}} +{{ include "kafka.common.metaLabels" . }} +{{ include "kafka.monitor.matchLabels" . }} +{{- end -}} + +{{- define "serviceMonitor.namespace" -}} +{{- if .Values.prometheus.operator.serviceMonitor.releaseNamespace -}} +{{ .Release.Namespace }} +{{- else -}} +{{ .Values.prometheus.operator.serviceMonitor.namespace }} +{{- end -}} +{{- end -}} + +{{- define "prometheusRule.namespace" -}} +{{- if .Values.prometheus.operator.prometheusRule.releaseNamespace -}} +{{ .Release.Namespace }} +{{- else -}} +{{ .Values.prometheus.operator.prometheusRule.namespace }} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/configmap-config.yaml b/rds/base/charts/jaeger/charts/kafka/templates/configmap-config.yaml new file mode 100644 index 0000000..78194c5 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/configmap-config.yaml @@ -0,0 +1,80 @@ +{{- if .Values.topics -}} +{{- $zk := include "zookeeper.url" . -}} +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + {{- include "kafka.config.labels" . | nindent 4 }} + name: {{ template "kafka.fullname" . }}-config + namespace: {{ .Release.Namespace }} +data: + runtimeConfig.sh: | + #!/bin/bash + set -e + cd /usr/bin + until kafka-configs --zookeeper {{ $zk }} --entity-type topics --describe || (( count++ >= 6 )) + do + echo "Waiting for ZooKeeper..." + sleep 20 + done + + # expected='0,1,2,3,...,n,' + # the trailing comma is significant + expected='{{ until (int .Values.replicas) | join "," | trim }},' + connected_brokers='' + until [[ "$connected_brokers" == "$expected" ]] + do + echo "Waiting for all Kafka brokers to be connected to ZooKeeper..." + connected_brokers=$(zookeeper-shell {{ $zk }} ls /brokers/ids | \ + # brokers formatted as: [ 0, 1, 2 ] + tail -1 | \ + # broker ids separated by newline + grep -o '[0-9]\+' | \ + # they may have connected in a random order + sort | \ + # trim the leading and trailing whitespace + sed 's/ *$//' | \ + # Replace newline with comma + # The result has a trailing comma + tr '\n' ',' + ) + echo "Currently available brokers: $connected_brokers" + echo "Expected brokers: $expected" + sleep 20 + done + + echo "Applying runtime configuration using {{ .Values.image }}:{{ .Values.imageTag }}" + {{- range $n, $topic := .Values.topics }} + {{- if and $topic.partitions $topic.replicationFactor $topic.reassignPartitions }} + cat << EOF > {{ $topic.name }}-increase-replication-factor.json + {"version":1, "partitions":[ + {{- $partitions := (int $topic.partitions) }} + {{- $replicas := (int $topic.replicationFactor) }} + {{- range $i := until $partitions }} + {"topic":"{{ $topic.name }}","partition":{{ $i }},"replicas":[{{- range $j := until $replicas }}{{ $j }}{{- if ne $j (sub $replicas 1) }},{{- end }}{{- end }}]}{{- if ne $i (sub $partitions 1) }},{{- end }} + {{- end }} + ]} + EOF + kafka-reassign-partitions --zookeeper {{ $zk }} --reassignment-json-file {{ $topic.name }}-increase-replication-factor.json --execute + kafka-reassign-partitions --zookeeper {{ $zk }} --reassignment-json-file {{ $topic.name }}-increase-replication-factor.json --verify + {{- else if and $topic.partitions $topic.replicationFactor }} + kafka-topics --zookeeper {{ $zk }} --create --if-not-exists --force --topic {{ $topic.name }} --partitions {{ $topic.partitions }} --replication-factor {{ $topic.replicationFactor }} + {{- else if $topic.partitions }} + kafka-topics --zookeeper {{ $zk }} --alter --force --topic {{ $topic.name }} --partitions {{ $topic.partitions }} || true + {{- end }} + {{- if $topic.defaultConfig }} + kafka-configs --zookeeper {{ $zk }} --entity-type topics --entity-name {{ $topic.name }} --alter --force --delete-config {{ nospace $topic.defaultConfig }} || true + {{- end }} + {{- if $topic.config }} + kafka-configs --zookeeper {{ $zk }} --entity-type topics --entity-name {{ $topic.name }} --alter --force --add-config {{ nospace $topic.config }} + {{- end }} + kafka-configs --zookeeper {{ $zk }} --entity-type topics --entity-name {{ $topic.name }} --describe + {{- if $topic.acls }} + {{- range $a, $acl := $topic.acls }} + {{ if and $acl.user $acl.operations }} + kafka-acls --authorizer-properties zookeeper.connect={{ $zk }} --force --add --allow-principal User:{{ $acl.user }}{{- range $operation := $acl.operations }} --operation {{ $operation }} {{- end }} --topic {{ $topic.name }} {{ $topic.extraParams }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/configmap-jmx.yaml b/rds/base/charts/jaeger/charts/kafka/templates/configmap-jmx.yaml new file mode 100644 index 0000000..5b8deb2 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/configmap-jmx.yaml @@ -0,0 +1,65 @@ +{{- if and .Values.prometheus.jmx.enabled .Values.jmx.configMap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "kafka.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} +data: + jmx-kafka-prometheus.yml: |+ +{{- if .Values.jmx.configMap.overrideConfig }} +{{ toYaml .Values.jmx.configMap.overrideConfig | indent 4 }} +{{- else }} + jmxUrl: service:jmx:rmi:///jndi/rmi://127.0.0.1:{{ .Values.jmx.port }}/jmxrmi + lowercaseOutputName: true + lowercaseOutputLabelNames: true + ssl: false + {{ if .Values.jmx.whitelistObjectNames }} + whitelistObjectNames: ["{{ join "\",\"" .Values.jmx.whitelistObjectNames }}"] + {{ end }} + rules: + - pattern: kafka.controller<>(Value) + name: kafka_controller_$1_$2_$4 + labels: + broker_id: "$3" + - pattern: kafka.controller<>(Value) + name: kafka_controller_$1_$2_$3 + - pattern: kafka.controller<>(Value) + name: kafka_controller_$1_$2_$3 + - pattern: kafka.controller<>(Count) + name: kafka_controller_$1_$2_$3 + - pattern: kafka.server<>(Value) + name: kafka_server_$1_$2_$4 + labels: + client_id: "$3" + - pattern : kafka.network<>(Value) + name: kafka_network_$1_$2_$4 + labels: + network_processor: $3 + - pattern : kafka.network<>(Count) + name: kafka_network_$1_$2_$4 + labels: + request: $3 + - pattern: kafka.server<>(Count|OneMinuteRate) + name: kafka_server_$1_$2_$4 + labels: + topic: $3 + - pattern: kafka.server<>(Value) + name: kafka_server_$1_$2_$3_$4 + - pattern: kafka.server<>(Count|Value|OneMinuteRate) + name: kafka_server_$1_total_$2_$3 + - pattern: kafka.server<>(queue-size) + name: kafka_server_$1_$2 + - pattern: java.lang<(.+)>(\w+) + name: java_lang_$1_$4_$3_$2 + - pattern: java.lang<>(\w+) + name: java_lang_$1_$3_$2 + - pattern : java.lang + - pattern: kafka.log<>Value + name: kafka_log_$1_$2 + labels: + topic: $3 + partition: $4 +{{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/deployment-kafka-exporter.yaml b/rds/base/charts/jaeger/charts/kafka/templates/deployment-kafka-exporter.yaml new file mode 100644 index 0000000..2c5cad4 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/deployment-kafka-exporter.yaml @@ -0,0 +1,46 @@ +{{- if .Values.prometheus.kafka.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "kafka.fullname" . }}-exporter + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "kafka.monitor.matchLabels" . | nindent 6 }} + template: + metadata: + annotations: +{{- if and .Values.prometheus.kafka.enabled (not .Values.prometheus.operator.enabled) }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ .Values.prometheus.kafka.port | quote }} +{{- end }} + labels: + {{- include "kafka.monitor.labels" . | nindent 8 }} + spec: + containers: + - image: "{{ .Values.prometheus.kafka.image }}:{{ .Values.prometheus.kafka.imageTag }}" + name: kafka-exporter + args: + - --kafka.server={{ template "kafka.fullname" . }}:9092 + - --web.listen-address=:{{ .Values.prometheus.kafka.port }} + ports: + - containerPort: {{ .Values.prometheus.kafka.port }} + resources: +{{ toYaml .Values.prometheus.kafka.resources | indent 10 }} +{{- if .Values.prometheus.kafka.tolerations }} + tolerations: +{{ toYaml .Values.prometheus.kafka.tolerations | indent 8 }} +{{- end }} +{{- if .Values.prometheus.kafka.affinity }} + affinity: +{{ toYaml .Values.prometheus.kafka.affinity | indent 8 }} +{{- end }} +{{- if .Values.prometheus.kafka.nodeSelector }} + nodeSelector: +{{ toYaml .Values.prometheus.kafka.nodeSelector | indent 8 }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/job-config.yaml b/rds/base/charts/jaeger/charts/kafka/templates/job-config.yaml new file mode 100644 index 0000000..c049422 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/job-config.yaml @@ -0,0 +1,30 @@ +{{- if .Values.topics -}} +{{- $scriptHash := include (print $.Template.BasePath "/configmap-config.yaml") . | sha256sum | trunc 8 -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ template "kafka.fullname" . }}-config-{{ $scriptHash }}" + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.config.labels" . | nindent 4 }} +spec: + backoffLimit: {{ .Values.configJob.backoffLimit }} + template: + metadata: + labels: + {{- include "kafka.config.matchLabels" . | nindent 8 }} + spec: + restartPolicy: OnFailure + volumes: + - name: config-volume + configMap: + name: {{ template "kafka.fullname" . }}-config + defaultMode: 0744 + containers: + - name: {{ template "kafka.fullname" . }}-config + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + command: ["/usr/local/script/runtimeConfig.sh"] + volumeMounts: + - name: config-volume + mountPath: "/usr/local/script" +{{- end -}} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/podisruptionbudget.yaml b/rds/base/charts/jaeger/charts/kafka/templates/podisruptionbudget.yaml new file mode 100644 index 0000000..6406ea5 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/podisruptionbudget.yaml @@ -0,0 +1,15 @@ +{{- if .Values.podDisruptionBudget }} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ include "kafka.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.broker.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "kafka.broker.matchLabels" . | nindent 6 }} +{{ toYaml .Values.podDisruptionBudget | indent 2 }} + +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/prometheusrules.yaml b/rds/base/charts/jaeger/charts/kafka/templates/prometheusrules.yaml new file mode 100644 index 0000000..a119c18 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/prometheusrules.yaml @@ -0,0 +1,16 @@ +{{ if and .Values.prometheus.operator.enabled .Values.prometheus.operator.prometheusRule.enabled .Values.prometheus.operator.prometheusRule.rules }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "kafka.fullname" . }} + namespace: {{ include "serviceMonitor.namespace" . | default .Release.Namespace }} + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} + {{- toYaml .Values.prometheus.operator.prometheusRule.selector | nindent 4 }} +spec: + groups: + - name: {{ include "kafka.fullname" . }} + rules: + {{- toYaml .Values.prometheus.operator.prometheusRule.rules | nindent 6 }} +{{- end }} + diff --git a/rds/base/charts/jaeger/charts/kafka/templates/service-brokers-external.yaml b/rds/base/charts/jaeger/charts/kafka/templates/service-brokers-external.yaml new file mode 100644 index 0000000..3991e57 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/service-brokers-external.yaml @@ -0,0 +1,78 @@ +{{- if .Values.external.enabled }} + {{- $fullName := include "kafka.fullname" . }} + {{- $replicas := .Values.replicas | int }} + {{- $servicePort := .Values.external.servicePort }} + {{- $firstListenerPort := .Values.external.firstListenerPort }} + {{- $dnsPrefix := printf "%s" .Release.Name }} + {{- $root := . }} + {{- range $i, $e := until $replicas }} + {{- $externalListenerPort := add $root.Values.external.firstListenerPort $i }} + {{- $responsiblePod := printf "%s-%d" (printf "%s" $fullName) $i }} + {{- $distinctPrefix := printf "%s-%d" $dnsPrefix $i }} + {{- $loadBalancerIPLen := len $root.Values.external.loadBalancerIP }} + +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + {{- if $root.Values.external.distinct }} + {{- if $root.Values.external.dns.useInternal }} + dns.alpha.kubernetes.io/internal: "{{ $distinctPrefix }}.{{ $root.Values.external.domain }}" + {{- end }} + {{- if $root.Values.external.dns.useExternal }} + external-dns.alpha.kubernetes.io/hostname: "{{ $distinctPrefix }}.{{ $root.Values.external.domain }}" + {{- end }} + {{- else }} + {{- if $root.Values.external.dns.useInternal }} + dns.alpha.kubernetes.io/internal: "{{ $dnsPrefix }}.{{ $root.Values.external.domain }}" + {{- end }} + {{- if $root.Values.external.dns.useExternal }} + external-dns.alpha.kubernetes.io/hostname: "{{ $dnsPrefix }}.{{ $root.Values.external.domain }}" + {{- end }} + {{- end }} + {{- if $root.Values.external.annotations }} +{{ toYaml $root.Values.external.annotations | indent 4 }} + {{- end }} + name: {{ $root.Release.Name }}-{{ $i }}-external + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.broker.labels" $root | nindent 4 }} + pod: {{ $responsiblePod | quote }} + {{- if $root.Values.external.labels }} +{{ toYaml $root.Values.external.labels | indent 4 }} + {{- end }} +spec: + type: {{ $root.Values.external.type }} + ports: + - name: external-broker + {{- if and (eq $root.Values.external.type "LoadBalancer") (not $root.Values.external.distinct) }} + port: {{ $firstListenerPort }} + {{- else }} + port: {{ $servicePort }} + {{- end }} + {{- if and (eq $root.Values.external.type "LoadBalancer") ($root.Values.external.distinct) }} + targetPort: {{ $servicePort }} + {{- else if and (eq $root.Values.external.type "LoadBalancer") (not $root.Values.external.distinct) }} + targetPort: {{ $firstListenerPort }} + {{- else }} + targetPort: {{ $externalListenerPort }} + {{- end }} + {{- if eq $root.Values.external.type "NodePort" }} + nodePort: {{ $externalListenerPort }} + {{- end }} + protocol: TCP + {{- if and (eq $root.Values.external.type "LoadBalancer") (eq $loadBalancerIPLen $replicas) }} + loadBalancerIP: {{ index $root.Values.external.loadBalancerIP $i }} + {{- end }} + {{- if $root.Values.external.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $root.Values.external.loadBalancerSourceRanges }} + - {{ . | quote}} + {{- end }} + {{- end }} + selector: + {{- include "kafka.broker.matchLabels" $root | nindent 4 }} + statefulset.kubernetes.io/pod-name: {{ $responsiblePod | quote }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/service-brokers.yaml b/rds/base/charts/jaeger/charts/kafka/templates/service-brokers.yaml new file mode 100644 index 0000000..6f64024 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/service-brokers.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "kafka.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.broker.labels" . | nindent 4 }} +spec: + ports: + - name: broker + port: 9092 + targetPort: kafka +{{- if and .Values.prometheus.jmx.enabled .Values.prometheus.operator.enabled }} + - name: jmx-exporter + protocol: TCP + port: {{ .Values.jmx.port }} + targetPort: prometheus +{{- end }} + selector: + {{- include "kafka.broker.matchLabels" . | nindent 4 }} +--- +{{- if and .Values.prometheus.kafka.enabled .Values.prometheus.operator.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "kafka.fullname" . }}-exporter + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} +spec: + ports: + - name: kafka-exporter + protocol: TCP + port: {{ .Values.prometheus.kafka.port }} + targetPort: {{ .Values.prometheus.kafka.port }} + selector: + {{- include "kafka.monitor.matchLabels" . | nindent 4 }} +{{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/service-headless.yaml b/rds/base/charts/jaeger/charts/kafka/templates/service-headless.yaml new file mode 100644 index 0000000..4a6b99e --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/service-headless.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "kafka.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.broker.labels" . | nindent 4 }} + annotations: + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" +{{- if .Values.headless.annotations }} +{{ .Values.headless.annotations | toYaml | trimSuffix "\n" | indent 4 }} +{{- end }} +spec: + ports: + - name: broker + port: {{ .Values.headless.port }} +{{- if .Values.headless.targetPort }} + targetPort: {{ .Values.headless.targetPort }} +{{- end }} + clusterIP: None + selector: + {{- include "kafka.broker.matchLabels" . | nindent 4 }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/servicemonitors.yaml b/rds/base/charts/jaeger/charts/kafka/templates/servicemonitors.yaml new file mode 100644 index 0000000..4d63960 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/servicemonitors.yaml @@ -0,0 +1,47 @@ +{{ if and .Values.prometheus.jmx.enabled .Values.prometheus.operator.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "kafka.fullname" . }} + namespace: {{ include "serviceMonitor.namespace" . | default .Release.Namespace }} + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} + {{- toYaml .Values.prometheus.operator.serviceMonitor.selector | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "kafka.broker.matchLabels" . | nindent 6 }} + endpoints: + - port: jmx-exporter + interval: {{ .Values.prometheus.jmx.interval }} + {{- if .Values.prometheus.jmx.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.jmx.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{ end }} +--- +{{ if and .Values.prometheus.kafka.enabled .Values.prometheus.operator.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "kafka.fullname" . }}-exporter + namespace: {{ include "serviceMonitor.namespace" . }} + labels: + {{- include "kafka.monitor.labels" . | nindent 4 }} + {{ toYaml .Values.prometheus.operator.serviceMonitor.selector | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "kafka.monitor.matchLabels" . | nindent 6 }} + endpoints: + - port: kafka-exporter + interval: {{ .Values.prometheus.kafka.interval }} + {{- if .Values.prometheus.kafka.scrapeTimeout }} + scrapeTimeout: {{ .Values.prometheus.kafka.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{ end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/statefulset.yaml b/rds/base/charts/jaeger/charts/kafka/templates/statefulset.yaml new file mode 100644 index 0000000..de3a284 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/statefulset.yaml @@ -0,0 +1,273 @@ +{{- $advertisedListenersOverride := first (pluck "advertised.listeners" .Values.configurationOverrides) }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "kafka.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "kafka.broker.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "kafka.broker.matchLabels" . | nindent 6 }} + serviceName: {{ include "kafka.fullname" . }}-headless + podManagementPolicy: {{ .Values.podManagementPolicy }} + updateStrategy: +{{ toYaml .Values.updateStrategy | indent 4 }} + replicas: {{ default 3 .Values.replicas }} + template: + metadata: +{{- if or .Values.podAnnotations (and .Values.prometheus.jmx.enabled (not .Values.prometheus.operator.enabled)) }} + annotations: +{{- if and .Values.prometheus.jmx.enabled (not .Values.prometheus.operator.enabled) }} + prometheus.io/scrape: "true" + prometheus.io/port: {{ .Values.prometheus.jmx.port | quote }} +{{- end }} +{{- if .Values.podAnnotations }} +{{ toYaml .Values.podAnnotations | indent 8 }} +{{- end }} +{{- end }} + labels: + {{- include "kafka.broker.labels" . | nindent 8 }} + {{- if .Values.podLabels }} + ## Custom pod labels +{{ toYaml .Values.podLabels | indent 8 }} + {{- end }} + spec: +{{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" +{{- end }} +{{- if .Values.serviceAccountName }} + serviceAccountName: {{ .Values.serviceAccountName }} +{{- end }} +{{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" +{{- end }} +{{- if .Values.tolerations }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} +{{- end }} +{{- if .Values.affinity }} + affinity: +{{ toYaml .Values.affinity | indent 8 }} +{{- end }} +{{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} +{{- end }} + containers: + {{- if .Values.prometheus.jmx.enabled }} + - name: metrics + image: "{{ .Values.prometheus.jmx.image }}:{{ .Values.prometheus.jmx.imageTag }}" + command: + - sh + - -exc + - | + trap "exit 0" TERM; \ + while :; do \ + java \ + -XX:+UnlockExperimentalVMOptions \ + -XX:+UseCGroupMemoryLimitForHeap \ + -XX:MaxRAMFraction=1 \ + -XshowSettings:vm \ + -jar \ + jmx_prometheus_httpserver.jar \ + {{ .Values.prometheus.jmx.port | quote }} \ + /etc/jmx-kafka/jmx-kafka-prometheus.yml & \ + wait $! || sleep 3; \ + done + ports: + - containerPort: {{ .Values.prometheus.jmx.port }} + name: prometheus + resources: +{{ toYaml .Values.prometheus.jmx.resources | indent 10 }} + volumeMounts: + - name: jmx-config + mountPath: /etc/jmx-kafka + {{- end }} + - name: {{ include "kafka.name" . }}-broker + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + livenessProbe: + exec: + command: + - sh + - -ec + - /usr/bin/jps | /bin/grep -q SupportedKafka + {{- if not .Values.livenessProbe }} + initialDelaySeconds: 30 + timeoutSeconds: 5 + {{- else }} + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds | default 30}} + {{- if .Values.livenessProbe.periodSeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + {{- end }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds | default 5}} + {{- if .Values.livenessProbe.successThreshold }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.livenessProbe.failureThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- end }} + readinessProbe: + tcpSocket: + port: kafka + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + ports: + - containerPort: 9092 + name: kafka + {{- if .Values.external.enabled }} + {{- $replicas := .Values.replicas | int }} + {{- $root := . }} + {{- range $i, $e := until $replicas }} + - containerPort: {{ add $root.Values.external.firstListenerPort $i }} + name: external-{{ $i }} + {{- end }} + {{- end }} + {{- if .Values.prometheus.jmx.enabled }} + - containerPort: {{ .Values.jmx.port }} + name: jmx + {{- end }} + {{- if .Values.additionalPorts }} +{{ toYaml .Values.additionalPorts | indent 8 }} + {{- end }} + resources: +{{ toYaml .Values.resources | indent 10 }} + env: + {{- if .Values.prometheus.jmx.enabled }} + - name: JMX_PORT + value: "{{ .Values.jmx.port }}" + {{- end }} + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: KAFKA_HEAP_OPTS + value: {{ .Values.kafkaHeapOptions }} + - name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR + value: {{ include "kafka.replication.factor" . | quote }} + {{- if not (hasKey .Values.configurationOverrides "zookeeper.connect") }} + - name: KAFKA_ZOOKEEPER_CONNECT + value: {{ include "zookeeper.url" . | quote }} + {{- end }} + {{- if not (hasKey .Values.configurationOverrides "log.dirs") }} + - name: KAFKA_LOG_DIRS + value: {{ printf "%s/%s" .Values.persistence.mountPath .Values.logSubPath | quote }} + {{- end }} + {{- range $key, $value := .Values.configurationOverrides }} + - name: {{ printf "KAFKA_%s" $key | replace "." "_" | upper | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.jmx.port }} + - name: KAFKA_JMX_PORT + value: "{{ .Values.jmx.port }}" + {{- end }} + {{- range $secret := .Values.secrets }} + {{- if not $secret.mountPath }} + {{- range $key := $secret.keys }} + - name: {{ (print ($secret.name | replace "-" "_") "_" $key) | upper }} + valueFrom: + secretKeyRef: + name: {{ $secret.name }} + key: {{ $key }} + {{- end }} + {{- end }} + {{- end }} + {{- range $key, $value := .Values.envOverrides }} + - name: {{ printf "%s" $key | replace "." "_" | upper | quote }} + value: {{ $value | quote }} + {{- end }} + # This is required because the Downward API does not yet support identification of + # pod numbering in statefulsets. Thus, we are required to specify a command which + # allows us to extract the pod ID for usage as the Kafka Broker ID. + # See: https://github.com/kubernetes/kubernetes/issues/31218 + command: + - sh + - -exc + - | + unset KAFKA_PORT && \ + export KAFKA_BROKER_ID=${POD_NAME##*-} && \ + {{- if eq .Values.external.type "LoadBalancer" }} + export LOAD_BALANCER_IP=$(echo '{{ .Values.external.loadBalancerIP }}' | tr -d '[]' | cut -d ' ' -f "$(($KAFKA_BROKER_ID + 1))") && \ + {{- end }} + {{- if eq .Values.external.type "NodePort" }} + export KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://${POD_IP}:9092{{ if kindIs "string" $advertisedListenersOverride }}{{ printf ",%s" $advertisedListenersOverride }}{{ end }} && \ + {{- else }} + export KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://${POD_NAME}.{{ include "kafka.fullname" . }}-headless.${POD_NAMESPACE}.svc.cluster.local:9092{{ if kindIs "string" $advertisedListenersOverride }}{{ printf ",%s" $advertisedListenersOverride }}{{ end }} && \ + {{- end }} + exec /etc/confluent/docker/run + volumeMounts: + - name: datadir + mountPath: {{ .Values.persistence.mountPath | quote }} + {{- range $secret := .Values.secrets }} + {{- if $secret.mountPath }} + {{- if $secret.keys }} + {{- range $key := $secret.keys }} + - name: {{ include "kafka.fullname" $ }}-{{ $secret.name }} + mountPath: {{ $secret.mountPath }}/{{ $key }} + subPath: {{ $key }} + readOnly: true + {{- end }} + {{- else }} + - name: {{ include "kafka.fullname" $ }}-{{ $secret.name }} + mountPath: {{ $secret.mountPath }} + readOnly: true + {{- end }} + {{- end }} + {{- end }} + volumes: + {{- if not .Values.persistence.enabled }} + - name: datadir + emptyDir: {} + {{- end }} + {{- if .Values.prometheus.jmx.enabled }} + - name: jmx-config + configMap: + {{- if .Values.jmx.configMap.overrideName }} + name: {{ .Values.jmx.configMap.overrideName }} + {{- else }} + name: {{ include "kafka.fullname" . }}-metrics + {{- end }} + {{- end }} + {{- if .Values.securityContext }} + securityContext: +{{ toYaml .Values.securityContext | indent 8 }} + {{- end }} + {{- range .Values.secrets }} + {{- if .mountPath }} + - name: {{ include "kafka.fullname" $ }}-{{ .name }} + secret: + secretName: {{ .name }} + {{- end }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: datadir + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- if .Values.persistence.storageClass }} + {{- if (eq "-" .Values.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.persistence.storageClass }}" + {{- end }} + {{- end }} + {{- end }} diff --git a/rds/base/charts/jaeger/charts/kafka/templates/tests/test_topic_create_consume_produce.yaml b/rds/base/charts/jaeger/charts/kafka/templates/tests/test_topic_create_consume_produce.yaml new file mode 100644 index 0000000..e7dd5c9 --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/templates/tests/test_topic_create_consume_produce.yaml @@ -0,0 +1,25 @@ +{{- if .Values.testsEnabled -}} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test-topic-create-consume-produce" + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: {{ .Release.Name }}-test-consume + image: {{ .Values.image }}:{{ .Values.imageTag }} + command: + - sh + - -c + - | + # Create the topic + kafka-topics --zookeeper {{ include "zookeeper.url" . }} --topic helm-test-topic-create-consume-produce --create --partitions 1 --replication-factor 1 --if-not-exists && \ + # Create a message + MESSAGE="`date -u`" && \ + # Produce a test message to the topic + echo "$MESSAGE" | kafka-console-producer --broker-list {{ include "kafka.fullname" . }}:9092 --topic helm-test-topic-create-consume-produce && \ + # Consume a test message from the topic + kafka-console-consumer --bootstrap-server {{ include "kafka.fullname" . }}-headless:9092 --topic helm-test-topic-create-consume-produce --from-beginning --timeout-ms 2000 --max-messages 1 | grep "$MESSAGE" + restartPolicy: Never +{{- end }} \ No newline at end of file diff --git a/rds/base/charts/jaeger/charts/kafka/values.yaml b/rds/base/charts/jaeger/charts/kafka/values.yaml new file mode 100644 index 0000000..c9e608c --- /dev/null +++ b/rds/base/charts/jaeger/charts/kafka/values.yaml @@ -0,0 +1,511 @@ +# ------------------------------------------------------------------------------ +# Kafka: +# ------------------------------------------------------------------------------ + +## The StatefulSet installs 3 pods by default +replicas: 3 + +## The kafka image repository +image: "confluentinc/cp-kafka" + +## The kafka image tag +imageTag: "5.0.1" # Confluent image for Kafka 2.0.0 + +## Specify a imagePullPolicy +## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images +imagePullPolicy: "IfNotPresent" + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +resources: {} + # limits: + # cpu: 200m + # memory: 1536Mi + # requests: + # cpu: 100m + # memory: 1024Mi +kafkaHeapOptions: "-Xmx1G -Xms1G" + +## Optional Container Security context +securityContext: {} + +## The StatefulSet Update Strategy which Kafka will use when changes are applied. +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +updateStrategy: + type: "OnDelete" + +## Start and stop pods in Parallel or OrderedReady (one-by-one.) Note - Can not change after first release. +## ref: https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy +podManagementPolicy: OrderedReady + +## Useful if using any custom authorizer +## Pass in some secrets to use (if required) +# secrets: +# - name: myKafkaSecret +# keys: +# - username +# - password +# # mountPath: /opt/kafka/secret +# - name: myZkSecret +# keys: +# - user +# - pass +# mountPath: /opt/zookeeper/secret + + +## The subpath within the Kafka container's PV where logs will be stored. +## This is combined with `persistence.mountPath`, to create, by default: /opt/kafka/data/logs +logSubPath: "logs" + +## Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +# schedulerName: + +## Use an alternate serviceAccount +## Useful when using images in custom repositories +# serviceAccountName: + +## Set a pod priorityClassName +# priorityClassName: high-priority + +## Pod scheduling preferences (by default keep pods within a release on separate nodes). +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## By default we don't set affinity +affinity: {} +## Alternatively, this typical example defines: +## antiAffinity (to keep Kafka pods on separate pods) +## and affinity (to encourage Kafka pods to be collocated with Zookeeper pods) +# affinity: +# podAntiAffinity: +# requiredDuringSchedulingIgnoredDuringExecution: +# - labelSelector: +# matchExpressions: +# - key: app +# operator: In +# values: +# - kafka +# topologyKey: "kubernetes.io/hostname" +# podAffinity: +# preferredDuringSchedulingIgnoredDuringExecution: +# - weight: 50 +# podAffinityTerm: +# labelSelector: +# matchExpressions: +# - key: app +# operator: In +# values: +# - zookeeper +# topologyKey: "kubernetes.io/hostname" + +## Node labels for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector +nodeSelector: {} + +## Readiness probe config. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ +## +readinessProbe: + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + +## Period to wait for broker graceful shutdown (sigterm) before pod is killed (sigkill) +## ref: https://kubernetes-v1-4.github.io/docs/user-guide/production-pods/#lifecycle-hooks-and-termination-notice +## ref: https://kafka.apache.org/10/documentation.html#brokerconfigs controlled.shutdown.* +terminationGracePeriodSeconds: 60 + +# Tolerations for nodes that have taints on them. +# Useful if you want to dedicate nodes to just run kafka +# https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] +# tolerations: +# - key: "key" +# operator: "Equal" +# value: "value" +# effect: "NoSchedule" + +## Headless service. +## +headless: + # annotations: + # targetPort: + port: 9092 + +## External access. +## +external: + enabled: false + # type can be either NodePort or LoadBalancer + type: NodePort + # annotations: + # service.beta.kubernetes.io/openstack-internal-load-balancer: "true" + # Labels to be added to external services + # labels: + # aLabel: "value" + dns: + useInternal: false + useExternal: true + # If using external service type LoadBalancer and external dns, set distinct to true below. + # This creates an A record for each statefulset pod/broker. You should then map the + # A record of the broker to the EXTERNAL IP given by the LoadBalancer in your DNS server. + distinct: false + servicePort: 19092 + firstListenerPort: 31090 + domain: cluster.local + loadBalancerIP: [] + loadBalancerSourceRanges: [] + init: + image: "lwolf/kubectl_deployer" + imageTag: "0.4" + imagePullPolicy: "IfNotPresent" + +# Annotation to be added to Kafka pods +podAnnotations: {} + +# Labels to be added to Kafka pods +podLabels: {} + # service: broker + # team: developers + +podDisruptionBudget: {} + # maxUnavailable: 1 # Limits how many Kafka pods may be unavailable due to voluntary disruptions. + +## Configuration Overrides. Specify any Kafka settings you would like set on the StatefulSet +## here in map format, as defined in the official docs. +## ref: https://kafka.apache.org/documentation/#brokerconfigs +## +configurationOverrides: + "confluent.support.metrics.enable": false # Disables confluent metric submission + # "auto.leader.rebalance.enable": true + # "auto.create.topics.enable": true + # "controlled.shutdown.enable": true + # "controlled.shutdown.max.retries": 100 + + ## Options required for external access via NodePort + ## ref: + ## - http://kafka.apache.org/documentation/#security_configbroker + ## - https://cwiki.apache.org/confluence/display/KAFKA/KIP-103%3A+Separation+of+Internal+and+External+traffic + ## + ## Setting "advertised.listeners" here appends to "PLAINTEXT://${POD_IP}:9092,", ensure you update the domain + ## If external service type is Nodeport: + # "advertised.listeners": |- + # EXTERNAL://kafka.cluster.local:$((31090 + ${KAFKA_BROKER_ID})) + ## If external service type is LoadBalancer and distinct is true: + # "advertised.listeners": |- + # EXTERNAL://kafka-$((${KAFKA_BROKER_ID})).cluster.local:19092 + ## If external service type is LoadBalancer and distinct is false: + # "advertised.listeners": |- + # EXTERNAL://${LOAD_BALANCER_IP}:31090 + ## Uncomment to define the EXTERNAL Listener protocol + # "listener.security.protocol.map": |- + # PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT + +## set extra ENVs +# key: "value" +envOverrides: {} + + +## A collection of additional ports to expose on brokers (formatted as normal containerPort yaml) +# Useful when the image exposes metrics (like prometheus, etc.) through a javaagent instead of a sidecar +additionalPorts: {} + +## Persistence configuration. Specify if and how to persist data to a persistent volume. +## +persistence: + enabled: true + + ## The size of the PersistentVolume to allocate to each Kafka Pod in the StatefulSet. For + ## production servers this number should likely be much larger. + ## + size: "1Gi" + + ## The location within the Kafka container where the PV will mount its storage and Kafka will + ## store its logs. + ## + mountPath: "/opt/kafka/data" + + ## Kafka 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: + +jmx: + ## Rules to apply to the Prometheus JMX Exporter. Note while lots of stats have been cleaned and exposed, + ## there are still more stats to clean up and expose, others will never get exposed. They keep lots of duplicates + ## that can be derived easily. The configMap in this chart cleans up the metrics it exposes to be in a Prometheus + ## format, eg topic, broker are labels and not part of metric name. Improvements are gladly accepted and encouraged. + configMap: + + ## Allows disabling the default configmap, note a configMap is needed + enabled: true + + ## Allows setting values to generate confimap + ## To allow all metrics through (warning its crazy excessive) comment out below `overrideConfig` and set + ## `whitelistObjectNames: []` + overrideConfig: {} + # jmxUrl: service:jmx:rmi:///jndi/rmi://127.0.0.1:5555/jmxrmi + # lowercaseOutputName: true + # lowercaseOutputLabelNames: true + # ssl: false + # rules: + # - pattern: ".*" + + ## If you would like to supply your own ConfigMap for JMX metrics, supply the name of that + ## ConfigMap as an `overrideName` here. + overrideName: "" + + ## Port the jmx metrics are exposed in native jmx format, not in Prometheus format + port: 5555 + + ## JMX Whitelist Objects, can be set to control which JMX metrics are exposed. Only whitelisted + ## values will be exposed via JMX Exporter. They must also be exposed via Rules. To expose all metrics + ## (warning its crazy excessive and they aren't formatted in a prometheus style) (1) `whitelistObjectNames: []` + ## (2) commented out above `overrideConfig`. + whitelistObjectNames: # [] + - kafka.controller:* + - kafka.server:* + - java.lang:* + - kafka.network:* + - kafka.log:* + +## Prometheus Exporters / Metrics +## +prometheus: + ## Prometheus JMX Exporter: exposes the majority of Kafkas metrics + jmx: + enabled: false + + ## The image to use for the metrics collector + image: solsson/kafka-prometheus-jmx-exporter@sha256 + + ## The image tag to use for the metrics collector + imageTag: a23062396cd5af1acdf76512632c20ea6be76885dfc20cd9ff40fb23846557e8 + + ## Interval at which Prometheus scrapes metrics, note: only used by Prometheus Operator + interval: 10s + + ## Timeout at which Prometheus timeouts scrape run, note: only used by Prometheus Operator + scrapeTimeout: 10s + + ## Port jmx-exporter exposes Prometheus format metrics to scrape + port: 5556 + + resources: {} + # limits: + # cpu: 200m + # memory: 1Gi + # requests: + # cpu: 100m + # memory: 100Mi + + ## Prometheus Kafka Exporter: exposes complimentary metrics to JMX Exporter + kafka: + enabled: false + + ## The image to use for the metrics collector + image: danielqsj/kafka-exporter + + ## The image tag to use for the metrics collector + imageTag: v1.2.0 + + ## Interval at which Prometheus scrapes metrics, note: only used by Prometheus Operator + interval: 10s + + ## Timeout at which Prometheus timeouts scrape run, note: only used by Prometheus Operator + scrapeTimeout: 10s + + ## Port kafka-exporter exposes for Prometheus to scrape metrics + port: 9308 + + ## Resource limits + resources: {} +# limits: +# cpu: 200m +# memory: 1Gi +# requests: +# cpu: 100m +# memory: 100Mi + + # Tolerations for nodes that have taints on them. + # Useful if you want to dedicate nodes to just run kafka-exporter + # https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + tolerations: [] + # tolerations: + # - key: "key" + # operator: "Equal" + # value: "value" + # effect: "NoSchedule" + + ## Pod scheduling preferences (by default keep pods within a release on separate nodes). + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## By default we don't set affinity + affinity: {} + ## Alternatively, this typical example defines: + ## affinity (to encourage Kafka Exporter pods to be collocated with Kafka pods) + # affinity: + # podAffinity: + # preferredDuringSchedulingIgnoredDuringExecution: + # - weight: 50 + # podAffinityTerm: + # labelSelector: + # matchExpressions: + # - key: app + # operator: In + # values: + # - kafka + # topologyKey: "kubernetes.io/hostname" + + ## Node labels for pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + nodeSelector: {} + + operator: + ## Are you using Prometheus Operator? + enabled: false + + serviceMonitor: + # Namespace in which to install the ServiceMonitor resource. + namespace: monitoring + # Use release namespace instead + releaseNamespace: false + + ## Defaults to whats used if you follow CoreOS [Prometheus Install Instructions](https://github.com/coreos/prometheus-operator/tree/master/helm#tldr) + ## [Prometheus Selector Label](https://github.com/coreos/prometheus-operator/blob/master/helm/prometheus/templates/prometheus.yaml#L65) + ## [Kube Prometheus Selector Label](https://github.com/coreos/prometheus-operator/blob/master/helm/kube-prometheus/values.yaml#L298) + selector: + prometheus: kube-prometheus + + prometheusRule: + ## Add Prometheus Rules? + enabled: false + + ## Namespace in which to install the PrometheusRule resource. + namespace: monitoring + # Use release namespace instead + releaseNamespace: false + + ## Defaults to whats used if you follow CoreOS [Prometheus Install Instructions](https://github.com/coreos/prometheus-operator/tree/master/helm#tldr) + ## [Prometheus Selector Label](https://github.com/coreos/prometheus-operator/blob/master/helm/prometheus/templates/prometheus.yaml#L65) + ## [Kube Prometheus Selector Label](https://github.com/coreos/prometheus-operator/blob/master/helm/kube-prometheus/values.yaml#L298) + selector: + prometheus: kube-prometheus + + ## Some example rules. + ## e.g. max(kafka_controller_kafkacontroller_activecontrollercount_value{service="my-kafka-release"}) by (service) < 1 + rules: + - alert: KafkaNoActiveControllers + annotations: + message: The number of active controllers in {{ "{{" }} $labels.namespace {{ "}}" }} is less than 1. This usually means that some of the Kafka nodes aren't communicating properly. If it doesn't resolve itself you can try killing the pods (one by one whilst monitoring the under-replicated partitions graph). + expr: max(kafka_controller_kafkacontroller_activecontrollercount_value) by (namespace) < 1 + for: 5m + labels: + severity: critical + - alert: KafkaMultipleActiveControllers + annotations: + message: The number of active controllers in {{ "{{" }} $labels.namespace {{ "}}" }} is greater than 1. This usually means that some of the Kafka nodes aren't communicating properly. If it doesn't resolve itself you can try killing the pods (one by one whilst monitoring the under-replicated partitions graph). + expr: max(kafka_controller_kafkacontroller_activecontrollercount_value) by (namespace) > 1 + for: 5m + labels: + severity: critical + +## Kafka Config job configuration +## +configJob: + ## Specify the number of retries before considering kafka-config job as failed. + ## https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#pod-backoff-failure-policy + backoffLimit: 6 + +## Topic creation and configuration. +## The job will be run on a deployment only when the config has been changed. +## - If 'partitions' and 'replicationFactor' are specified we create the topic (with --if-not-exists.) +## - If 'partitions', 'replicationFactor' and 'reassignPartitions' are specified we reassign the partitions to +## increase the replication factor of an existing topic. +## - If 'partitions' is specified we 'alter' the number of partitions. This will +## silently and safely fail if the new setting isn’t strictly larger than the old (i.e. a NOOP.) Do be aware of the +## implications for keyed topics (ref: https://docs.confluent.io/current/kafka/post-deployment.html#admin-operations) +## - If 'defaultConfig' is specified it's deleted from the topic configuration. If it isn't present, +## it will silently and safely fail. +## - If 'config' is specified it's added to the topic configuration. +## +## Note: To increase the 'replicationFactor' of a topic, 'reassignPartitions' must be set to true (see above). +## +topics: [] + # - name: myExistingTopicConfig + # config: "cleanup.policy=compact,delete.retention.ms=604800000" + # - name: myExistingTopicReassignPartitions + # partitions: 8 + # replicationFactor: 5 + # reassignPartitions: true + # - name: myExistingTopicPartitions + # partitions: 8 + # - name: myNewTopicWithConfig + # partitions: 8 + # replicationFactor: 3 + # defaultConfig: "segment.bytes,segment.ms" + # config: "cleanup.policy=compact,delete.retention.ms=604800000" + # - name: myAclTopicPartitions + # partitions: 8 + # acls: + # - user: read + # operations: [ Read ] + # - user: read_and_write + # operations: + # - Read + # - Write + # - user: all + # operations: [ All ] + +## Enable/disable the chart's tests. Useful if using this chart as a dependency of +## another chart and you don't want these tests running when trying to develop and +## test your own chart. +testsEnabled: true + +# ------------------------------------------------------------------------------ +# Zookeeper: +# ------------------------------------------------------------------------------ + +zookeeper: + ## If true, install the Zookeeper chart alongside Kafka + ## ref: https://github.com/kubernetes/charts/tree/master/incubator/zookeeper + enabled: true + + ## Configure Zookeeper resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + resources: ~ + + ## Environmental variables to set in Zookeeper + env: + ## The JVM heap size to allocate to Zookeeper + ZK_HEAP_SIZE: "1G" + + persistence: + enabled: false + ## The amount of PV storage allocated to each Zookeeper pod in the statefulset + # size: "2Gi" + + ## Specify a Zookeeper imagePullPolicy + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + image: + PullPolicy: "IfNotPresent" + + ## If the Zookeeper Chart is disabled a URL and port are required to connect + url: "" + port: 2181 + + ## Pod scheduling preferences (by default keep pods within a release on separate nodes). + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## By default we don't set affinity: + affinity: {} # Criteria by which pod label-values influence scheduling for zookeeper pods. + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - topologyKey: "kubernetes.io/hostname" + # labelSelector: + # matchLabels: + # release: zookeeper diff --git a/rds/base/charts/jaeger/requirements.lock b/rds/base/charts/jaeger/requirements.lock new file mode 100644 index 0000000..3df5f5b --- /dev/null +++ b/rds/base/charts/jaeger/requirements.lock @@ -0,0 +1,12 @@ +dependencies: +- name: cassandra + repository: file://charts/cassandra + version: 0.15.2 +- name: elasticsearch + repository: file://charts/elasticsearch + version: 7.8.1 +- name: kafka + repository: file://charts/kafka + version: 0.20.6 +digest: sha256:c83f2652150b6feb9e1637810bcb9ac271917958b7776792f28d3eb210eb7c2b +generated: "2022-08-18T15:36:39.919682019+02:00" diff --git a/rds/base/charts/jaeger/requirements.yaml b/rds/base/charts/jaeger/requirements.yaml new file mode 100644 index 0000000..c6bdaaa --- /dev/null +++ b/rds/base/charts/jaeger/requirements.yaml @@ -0,0 +1,14 @@ +dependencies: + - name: cassandra + version: ^0.15.0 + repository: file://charts/cassandra + condition: provisionDataStore.cassandra + - name: elasticsearch + version: ^7.5.1 + repository: file://charts/elasticsearch + condition: provisionDataStore.elasticsearch + - name: kafka + version: ^0.20.6 + repository: file://charts/kafka + condition: provisionDataStore.kafka +version: 0.34.0 diff --git a/rds/base/charts/jaeger/templates/NOTES.txt b/rds/base/charts/jaeger/templates/NOTES.txt new file mode 100644 index 0000000..f9664d2 --- /dev/null +++ b/rds/base/charts/jaeger/templates/NOTES.txt @@ -0,0 +1,27 @@ + +################################################################### +### IMPORTANT: The use of .env: {...} is deprecated. ### +### Please use .extraEnv: [] instead. ### +################################################################### + +You can log into the Jaeger Query UI here: + +{{- if contains "NodePort" .Values.query.service.type }} + + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "jaeger.fullname" . }}-query) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT/ + +{{- else if contains "LoadBalancer" .Values.query.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ include "jaeger.fullname" . }}-query' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jaeger.fullname" . }}-query -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP/ +{{- else if contains "ClusterIP" .Values.query.service.type }} + + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=query" -o jsonpath="{.items[0].metadata.name}") + echo http://127.0.0.1:8080/ + kubectl port-forward --namespace {{ .Release.Namespace }} $POD_NAME 8080:16686 +{{- end }} diff --git a/rds/base/charts/jaeger/templates/_helpers.tpl b/rds/base/charts/jaeger/templates/_helpers.tpl new file mode 100644 index 0000000..96cba36 --- /dev/null +++ b/rds/base/charts/jaeger/templates/_helpers.tpl @@ -0,0 +1,370 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "jaeger.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "jaeger.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "jaeger.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "jaeger.labels" -}} +helm.sh/chart: {{ include "jaeger.chart" . }} +{{ include "jaeger.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "jaeger.selectorLabels" -}} +app.kubernetes.io/name: {{ include "jaeger.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the cassandra schema service account to use +*/}} +{{- define "jaeger.cassandraSchema.serviceAccountName" -}} +{{- if .Values.schema.serviceAccount.create -}} + {{ default (printf "%s-cassandra-schema" (include "jaeger.fullname" .)) .Values.schema.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.schema.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the spark service account to use +*/}} +{{- define "jaeger.spark.serviceAccountName" -}} +{{- if .Values.spark.serviceAccount.create -}} + {{ default (printf "%s-spark" (include "jaeger.fullname" .)) .Values.spark.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.spark.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the esIndexCleaner service account to use +*/}} +{{- define "jaeger.esIndexCleaner.serviceAccountName" -}} +{{- if .Values.esIndexCleaner.serviceAccount.create -}} + {{ default (printf "%s-es-index-cleaner" (include "jaeger.fullname" .)) .Values.esIndexCleaner.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.esIndexCleaner.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the hotrod service account to use +*/}} +{{- define "jaeger.hotrod.serviceAccountName" -}} +{{- if .Values.hotrod.serviceAccount.create -}} + {{ default (printf "%s-hotrod" (include "jaeger.fullname" .)) .Values.hotrod.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.hotrod.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the query service account to use +*/}} +{{- define "jaeger.query.serviceAccountName" -}} +{{- if .Values.query.serviceAccount.create -}} + {{ default (include "jaeger.query.name" .) .Values.query.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.query.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the agent service account to use +*/}} +{{- define "jaeger.agent.serviceAccountName" -}} +{{- if .Values.agent.serviceAccount.create -}} + {{ default (include "jaeger.agent.name" .) .Values.agent.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.agent.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the collector service account to use +*/}} +{{- define "jaeger.collector.serviceAccountName" -}} +{{- if .Values.collector.serviceAccount.create -}} + {{ default (include "jaeger.collector.name" .) .Values.collector.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.collector.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the ingester service account to use +*/}} +{{- define "jaeger.ingester.serviceAccountName" -}} +{{- if .Values.ingester.serviceAccount.create -}} + {{ default (include "jaeger.ingester.name" .) .Values.ingester.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.ingester.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified query name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "jaeger.query.name" -}} +{{- $nameGlobalOverride := printf "%s-query" (include "jaeger.fullname" .) -}} +{{- if .Values.query.fullnameOverride -}} +{{- printf "%s" .Values.query.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $nameGlobalOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified agent name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "jaeger.agent.name" -}} +{{- $nameGlobalOverride := printf "%s-agent" (include "jaeger.fullname" .) -}} +{{- if .Values.agent.fullnameOverride -}} +{{- printf "%s" .Values.agent.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $nameGlobalOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified collector name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "jaeger.collector.name" -}} +{{- $nameGlobalOverride := printf "%s-collector" (include "jaeger.fullname" .) -}} +{{- if .Values.collector.fullnameOverride -}} +{{- printf "%s" .Values.collector.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $nameGlobalOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified ingester name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "jaeger.ingester.name" -}} +{{- $nameGlobalOverride := printf "%s-ingester" (include "jaeger.fullname" .) -}} +{{- if .Values.ingester.fullnameOverride -}} +{{- printf "%s" .Values.ingester.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $nameGlobalOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{- define "cassandra.host" -}} +{{- if .Values.provisionDataStore.cassandra -}} +{{- if .Values.storage.cassandra.nameOverride }} +{{- printf "%s" .Values.storage.cassandra.nameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name "cassandra" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- else }} +{{- .Values.storage.cassandra.host }} +{{- end -}} +{{- end -}} + +{{- define "cassandra.contact_points" -}} +{{- $port := .Values.storage.cassandra.port | toString }} +{{- if .Values.provisionDataStore.cassandra -}} +{{- if .Values.storage.cassandra.nameOverride }} +{{- $host := printf "%s" .Values.storage.cassandra.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- printf "%s:%s" $host $port }} +{{- else }} +{{- $host := printf "%s-%s" .Release.Name "cassandra" | trunc 63 | trimSuffix "-" -}} +{{- printf "%s:%s" $host $port }} +{{- end -}} +{{- else }} +{{- printf "%s:%s" .Values.storage.cassandra.host $port }} +{{- end -}} +{{- 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.client.url" -}} +{{- $port := .Values.storage.elasticsearch.port | toString -}} +{{- printf "%s://%s:%s" .Values.storage.elasticsearch.scheme .Values.storage.elasticsearch.host $port }} +{{- end -}} + +{{- define "jaeger.hotrod.tracing.host" -}} +{{- default (include "jaeger.agent.name" .) .Values.hotrod.tracing.host -}} +{{- end -}} + + +{{/* +Configure list of IP CIDRs allowed access to load balancer (if supported) +*/}} +{{- define "loadBalancerSourceRanges" -}} +{{- if .service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} +{{- end -}} + +{{- define "helm-toolkit.utils.joinListWithComma" -}} +{{- $local := dict "first" true -}} +{{- range $k, $v := . -}}{{- if not $local.first -}},{{- end -}}{{- $v -}}{{- $_ := set $local "first" false -}}{{- end -}} +{{- end -}} + + +{{/* +Cassandra related environment variables +*/}} +{{- define "cassandra.env" -}} +- name: CASSANDRA_SERVERS + value: {{ include "cassandra.host" . }} +- name: CASSANDRA_PORT + value: {{ .Values.storage.cassandra.port | quote }} +{{ if .Values.storage.cassandra.tls.enabled }} +- name: CASSANDRA_TLS_ENABLED + value: "true" +- name: CASSANDRA_TLS_SERVER_NAME + valueFrom: + secretKeyRef: + name: {{ .Values.storage.cassandra.tls.secretName }} + key: commonName +- name: CASSANDRA_TLS_KEY + value: "/cassandra-tls/client-key.pem" +- name: CASSANDRA_TLS_CERT + value: "/cassandra-tls/client-cert.pem" +- name: CASSANDRA_TLS_CA + value: "/cassandra-tls/ca-cert.pem" +{{- end }} +{{- if .Values.storage.cassandra.keyspace }} +- name: CASSANDRA_KEYSPACE + value: {{ .Values.storage.cassandra.keyspace }} +{{- end }} +- name: CASSANDRA_USERNAME + value: {{ .Values.storage.cassandra.user }} +- name: CASSANDRA_PASSWORD + valueFrom: + secretKeyRef: + name: {{ if .Values.storage.cassandra.existingSecret }}{{ .Values.storage.cassandra.existingSecret }}{{- else }}{{ include "jaeger.fullname" . }}-cassandra{{- end }} + key: password +{{- range $key, $value := .Values.storage.cassandra.env }} +- name: {{ $key | quote }} + value: {{ $value | quote }} +{{ end -}} +{{- if .Values.storage.cassandra.extraEnv }} +{{ toYaml .Values.storage.cassandra.extraEnv }} +{{- end }} +{{- end -}} + +{{/* +Elasticsearch related environment variables +*/}} +{{- define "elasticsearch.env" -}} +- name: ES_SERVER_URLS + value: {{ include "elasticsearch.client.url" . }} +- name: ES_USERNAME + value: {{ .Values.storage.elasticsearch.user }} +{{- if .Values.storage.elasticsearch.usePassword }} +- name: ES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ if .Values.storage.elasticsearch.existingSecret }}{{ .Values.storage.elasticsearch.existingSecret }}{{- else }}{{ include "jaeger.fullname" . }}-elasticsearch{{- end }} + key: {{ default "password" .Values.storage.elasticsearch.existingSecretKey }} +{{- end }} +{{- if .Values.storage.elasticsearch.indexPrefix }} +- name: ES_INDEX_PREFIX + value: {{ .Values.storage.elasticsearch.indexPrefix }} +{{- end }} +{{- range $key, $value := .Values.storage.elasticsearch.env }} +- name: {{ $key | quote }} + value: {{ $value | quote }} +{{ end -}} +{{- if .Values.storage.elasticsearch.extraEnv }} +{{ toYaml .Values.storage.elasticsearch.extraEnv }} +{{- end }} +{{- end -}} + +{{/* +Cassandra or Elasticsearch related environment variables depending on which is used +*/}} +{{- define "storage.env" -}} +{{- if eq .Values.storage.type "cassandra" -}} +{{ include "cassandra.env" . }} +{{- else if eq .Values.storage.type "elasticsearch" -}} +{{ include "elasticsearch.env" . }} +{{- end -}} +{{- end -}} + +{{/* +Cassandra related command line options +*/}} +{{- define "cassandra.cmdArgs" -}} +{{- range $key, $value := .Values.storage.cassandra.cmdlineParams -}} +{{- if $value -}} +- --{{ $key }}={{ $value }} +{{- else }} +- --{{ $key }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Elasticsearch related command line options +*/}} +{{- define "elasticsearch.cmdArgs" -}} +{{- range $key, $value := .Values.storage.elasticsearch.cmdlineParams -}} +{{- if $value -}} +- --{{ $key }}={{ $value }} +{{- else }} +- --{{ $key }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Cassandra or Elasticsearch related command line options depending on which is used +*/}} +{{- define "storage.cmdArgs" -}} +{{- if eq .Values.storage.type "cassandra" -}} +{{- include "cassandra.cmdArgs" . -}} +{{- else if eq .Values.storage.type "elasticsearch" -}} +{{- include "elasticsearch.cmdArgs" . -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/agent-ds.yaml b/rds/base/charts/jaeger/templates/agent-ds.yaml new file mode 100644 index 0000000..2b4f8b7 --- /dev/null +++ b/rds/base/charts/jaeger/templates/agent-ds.yaml @@ -0,0 +1,142 @@ +{{- if .Values.agent.enabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ template "jaeger.agent.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +{{- if .Values.agent.annotations }} + annotations: + {{- toYaml .Values.agent.annotations | nindent 4 }} +{{- end }} +spec: + selector: + matchLabels: + {{- include "jaeger.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: agent + template: + metadata: +{{- if .Values.agent.podAnnotations }} + annotations: + {{- toYaml .Values.agent.podAnnotations | nindent 8 }} +{{- end }} + labels: + {{- include "jaeger.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: agent +{{- if .Values.agent.podLabels }} + {{- toYaml .Values.agent.podLabels | nindent 8 }} +{{- end }} + spec: + securityContext: + {{- toYaml .Values.agent.podSecurityContext | nindent 8 }} + {{- if .Values.agent.useHostNetwork }} + hostNetwork: true + {{- end }} + dnsPolicy: {{ .Values.agent.dnsPolicy }} + {{- with .Values.agent.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + serviceAccountName: {{ template "jaeger.agent.serviceAccountName" . }} + {{- with .Values.agent.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ template "jaeger.agent.name" . }} + securityContext: + {{- toYaml .Values.agent.securityContext | nindent 10 }} + image: {{ .Values.agent.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.agent.pullPolicy }} + args: + {{- range $key, $value := .Values.agent.cmdlineParams }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + env: + {{- if .Values.agent.extraEnv }} + {{- toYaml .Values.agent.extraEnv | nindent 10 }} + {{- end }} + {{- if not (hasKey .Values.agent.cmdlineParams "reporter.grpc.host-port") }} + - name: REPORTER_GRPC_HOST_PORT + value: {{ include "jaeger.collector.name" . }}:{{ .Values.collector.service.grpc.port }} + {{- end }} + ports: + - name: zipkin-compact + containerPort: {{ .Values.agent.service.zipkinThriftPort }} + protocol: UDP + {{- if .Values.agent.daemonset.useHostPort }} + hostPort: {{ .Values.agent.service.zipkinThriftPort }} + {{- end }} + - name: jaeger-compact + containerPort: {{ .Values.agent.service.compactPort }} + protocol: UDP + {{- if .Values.agent.daemonset.useHostPort }} + hostPort: {{ .Values.agent.service.compactPort }} + {{- end }} + - name: jaeger-binary + containerPort: {{ .Values.agent.service.binaryPort }} + protocol: UDP + {{- if .Values.agent.daemonset.useHostPort }} + hostPort: {{ .Values.agent.service.binaryPort }} + {{- end }} + - name: http + containerPort: {{ .Values.agent.service.samplingPort }} + protocol: TCP + {{- if .Values.agent.daemonset.useHostPort }} + hostPort: {{ .Values.agent.service.samplingPort }} + {{- end }} + - name: admin + containerPort: 14271 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: admin + readinessProbe: + httpGet: + path: / + port: admin + resources: + {{- toYaml .Values.agent.resources | nindent 10 }} + volumeMounts: + {{- range .Values.agent.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.agent.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + volumes: + {{- range .Values.agent.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.agent.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- with .Values.agent.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.agent.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.agent.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/agent-sa.yaml b/rds/base/charts/jaeger/templates/agent-sa.yaml new file mode 100644 index 0000000..211119f --- /dev/null +++ b/rds/base/charts/jaeger/templates/agent-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.agent.enabled .Values.agent.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.agent.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/agent-servicemonitor.yaml b/rds/base/charts/jaeger/templates/agent-servicemonitor.yaml new file mode 100644 index 0000000..10be1e8 --- /dev/null +++ b/rds/base/charts/jaeger/templates/agent-servicemonitor.yaml @@ -0,0 +1,38 @@ +{{- if and (.Values.agent.enabled) (.Values.agent.serviceMonitor.enabled)}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "jaeger.agent.name" . }} + {{- if .Values.agent.serviceMonitor.namespace }} + namespace: {{ .Values.agent.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: agent + {{- if .Values.agent.serviceMonitor.additionalLabels }} + {{- toYaml .Values.agent.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.agent.serviceMonitor.annotations }} + annotations: + {{- toYaml .Values.agent.serviceMonitor.annotations | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: admin + path: /metrics + {{- if .Values.agent.serviceMonitor.interval }} + interval: {{ .Values.agent.serviceMonitor.interval }} + {{- end }} + {{- if .Values.agent.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.agent.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app.kubernetes.io/component: agent + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/agent-svc.yaml b/rds/base/charts/jaeger/templates/agent-svc.yaml new file mode 100644 index 0000000..1bb71fe --- /dev/null +++ b/rds/base/charts/jaeger/templates/agent-svc.yaml @@ -0,0 +1,41 @@ +{{- if .Values.agent.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jaeger.agent.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: agent +{{- if .Values.agent.service.annotations }} + annotations: + {{- toYaml .Values.agent.service.annotations | nindent 4 }} +{{- end }} +spec: + ports: + - name: zipkin-compact + port: {{ .Values.agent.service.zipkinThriftPort }} + protocol: UDP + targetPort: zipkin-compact + - name: jaeger-compact + port: {{ .Values.agent.service.compactPort }} + protocol: UDP + targetPort: jaeger-compact + - name: jaeger-binary + port: {{ .Values.agent.service.binaryPort }} + protocol: UDP + targetPort: jaeger-binary + - name: http + port: {{ .Values.agent.service.samplingPort }} + protocol: TCP + targetPort: http + - name: admin + port: 14271 + protocol: TCP + targetPort: admin + type: {{ .Values.agent.service.type }} + selector: + {{- include "jaeger.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: agent +{{- template "loadBalancerSourceRanges" .Values.agent }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/cassandra-schema-job.yaml b/rds/base/charts/jaeger/templates/cassandra-schema-job.yaml new file mode 100644 index 0000000..95487cf --- /dev/null +++ b/rds/base/charts/jaeger/templates/cassandra-schema-job.yaml @@ -0,0 +1,98 @@ +{{- if .Values.collector.enabled -}} +{{- if eq .Values.storage.type "cassandra" -}} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "jaeger.fullname" . }}-cassandra-schema + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: cassandra-schema +{{- if .Values.schema.annotations }} + annotations: + {{- toYaml .Values.schema.annotations | nindent 4 }} +{{- end }} +spec: + activeDeadlineSeconds: {{ .Values.schema.activeDeadlineSeconds }} + template: + metadata: + name: {{ include "jaeger.fullname" . }}-cassandra-schema +{{- if .Values.schema.podAnnotations }} + annotations: + {{- toYaml .Values.schema.podAnnotations | nindent 8 }} +{{- end }} +{{- if .Values.schema.podLabels }} + labels: + {{- toYaml .Values.schema.podLabels | nindent 8 }} +{{- end }} + spec: + securityContext: + {{- toYaml .Values.schema.podSecurityContext | nindent 8 }} + serviceAccountName: {{ template "jaeger.cassandraSchema.serviceAccountName" . }} + {{- with .Values.schema.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "jaeger.fullname" . }}-cassandra-schema + image: {{ .Values.schema.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.schema.pullPolicy }} + securityContext: + {{- toYaml .Values.schema.securityContext | nindent 10 }} + env: + {{- if .Values.schema.extraEnv }} + {{- toYaml .Values.schema.extraEnv | nindent 10 }} + {{- end }} + {{ range $key, $value := .Values.schema.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{ end }} + {{- include "cassandra.env" . | nindent 10 }} + - name: CQLSH_HOST + value: {{ template "cassandra.host" . }} + {{ if .Values.storage.cassandra.tls.enabled }} + - name: CQLSH_SSL + value: "--ssl" + {{- end }} + - name: DATACENTER + value: {{ .Values.cassandra.config.dc_name | quote }} + {{- if .Values.storage.cassandra.keyspace }} + - name: KEYSPACE + value: {{ .Values.storage.cassandra.keyspace }} + {{- end }} + resources: + {{- toYaml .Values.schema.resources | nindent 10 }} + volumeMounts: + {{- range .Values.schema.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.storage.cassandra.tls.enabled }} + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/root/.cassandra/ca-cert.pem" + subPath: "ca-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/root/.cassandra/client-cert.pem" + subPath: "client-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/root/.cassandra/client-key.pem" + subPath: "client-key.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/root/.cassandra/cqlshrc" + subPath: "cqlshrc" + readOnly: true + {{- end }} + restartPolicy: OnFailure + volumes: + {{- range .Values.schema.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/cassandra-schema-sa.yaml b/rds/base/charts/jaeger/templates/cassandra-schema-sa.yaml new file mode 100644 index 0000000..2b3a2fd --- /dev/null +++ b/rds/base/charts/jaeger/templates/cassandra-schema-sa.yaml @@ -0,0 +1,10 @@ +{{- if and (eq .Values.storage.type "cassandra") .Values.schema.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.cassandraSchema.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: cassandra-schema +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/cassandra-secret.yaml b/rds/base/charts/jaeger/templates/cassandra-secret.yaml new file mode 100644 index 0000000..4fb7573 --- /dev/null +++ b/rds/base/charts/jaeger/templates/cassandra-secret.yaml @@ -0,0 +1,12 @@ +{{ if and (eq .Values.storage.type "cassandra") .Values.storage.cassandra.usePassword (not .Values.storage.cassandra.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "jaeger.fullname" . }}-cassandra + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} +type: Opaque +data: + password: {{ .Values.storage.cassandra.password | b64enc | quote }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/collector-configmap.yaml b/rds/base/charts/jaeger/templates/collector-configmap.yaml new file mode 100644 index 0000000..ab88378 --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.collector.samplingConfig }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "jaeger.fullname" . }}-sampling-strategies + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector +data: + strategies.json: |- +{{ tpl .Values.collector.samplingConfig . | indent 4 }} +{{- end }} + diff --git a/rds/base/charts/jaeger/templates/collector-deploy.yaml b/rds/base/charts/jaeger/templates/collector-deploy.yaml new file mode 100644 index 0000000..26bc400 --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-deploy.yaml @@ -0,0 +1,181 @@ +{{- if .Values.collector.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "jaeger.collector.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector +{{- if .Values.collector.annotations }} + annotations: + {{- toYaml .Values.collector.annotations | nindent 4 }} +{{- end }} +spec: +{{- if not .Values.collector.autoscaling.enabled }} + replicas: {{ .Values.collector.replicaCount }} +{{- end }} + selector: + matchLabels: + {{- include "jaeger.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: collector + strategy: + type: Recreate + template: + metadata: + annotations: + checksum/config-env: {{ include (print $.Template.BasePath "/collector-configmap.yaml") . | sha256sum }} +{{- if .Values.collector.podAnnotations }} + {{- toYaml .Values.collector.podAnnotations | nindent 8 }} +{{- end }} + labels: + {{- include "jaeger.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: collector +{{- if .Values.collector.podLabels }} + {{- toYaml .Values.collector.podLabels | nindent 8 }} +{{- end }} + spec: + {{- with .Values.collector.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + securityContext: + {{- toYaml .Values.collector.podSecurityContext | nindent 8 }} + serviceAccountName: {{ template "jaeger.collector.serviceAccountName" . }} + {{- with .Values.collector.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ template "jaeger.collector.name" . }} + securityContext: + {{- toYaml .Values.collector.securityContext | nindent 10 }} + image: {{ .Values.collector.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.collector.pullPolicy }} + args: + {{- range $key, $value := .Values.collector.cmdlineParams -}} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end -}} + {{- if not .Values.ingester.enabled -}} + {{- include "storage.cmdArgs" . | nindent 10 }} + {{- end }} + env: + {{- if .Values.collector.service.zipkin }} + - name: COLLECTOR_ZIPKIN_HTTP_PORT + value: {{ .Values.collector.service.zipkin.port | quote }} + {{- end }} + {{- if .Values.ingester.enabled }} + - name: SPAN_STORAGE_TYPE + value: kafka + {{- range $key, $value := .Values.storage.kafka.env }} + - name: {{ $key | quote }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.storage.kafka.extraEnv }} + {{- toYaml .Values.storage.kafka.extraEnv | nindent 10 }} + {{- end }} + - name: KAFKA_PRODUCER_BROKERS + value: {{ include "helm-toolkit.utils.joinListWithComma" .Values.storage.kafka.brokers }} + - name: KAFKA_PRODUCER_TOPIC + value: {{ .Values.storage.kafka.topic }} + - name: KAFKA_PRODUCER_AUTHENTICATION + value: {{ .Values.storage.kafka.authentication }} + {{ else }} + - name: SPAN_STORAGE_TYPE + value: {{ .Values.storage.type }} + {{- include "storage.env" . | nindent 10 }} + {{- end }} + {{- if .Values.collector.samplingConfig}} + - name: SAMPLING_STRATEGIES_FILE + value: /etc/conf/strategies.json + {{- end }} + ports: + - containerPort: {{ .Values.collector.service.grpc.port }} + name: grpc + protocol: TCP + - containerPort: {{ .Values.collector.service.http.port }} + name: http + protocol: TCP + - containerPort: 14269 + name: admin + protocol: TCP + {{- if .Values.collector.service.zipkin }} + - containerPort: {{ .Values.collector.service.zipkin.port }} + name: zipkin + protocol: TCP + {{- end }} + readinessProbe: + httpGet: + path: / + port: admin + livenessProbe: + httpGet: + path: / + port: admin + resources: + {{- toYaml .Values.collector.resources | nindent 10 }} + volumeMounts: + {{- range .Values.collector.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.collector.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.storage.cassandra.tls.enabled }} + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/ca-cert.pem" + subPath: "ca-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-cert.pem" + subPath: "client-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-key.pem" + subPath: "client-key.pem" + readOnly: true + {{- end }} + {{- if .Values.collector.samplingConfig}} + - name: strategies + mountPath: /etc/conf/ + {{- end }} + dnsPolicy: {{ .Values.collector.dnsPolicy }} + restartPolicy: Always + volumes: + {{- range .Values.collector.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.collector.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- if .Values.collector.samplingConfig}} + - name: strategies + configMap: + name: {{ include "jaeger.fullname" . }}-sampling-strategies + {{- end }} + {{- with .Values.collector.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.collector.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.collector.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/collector-hpa.yaml b/rds/base/charts/jaeger/templates/collector-hpa.yaml new file mode 100644 index 0000000..c73f44a --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.collector.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "jaeger.collector.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ template "jaeger.collector.name" . }} + minReplicas: {{ .Values.collector.autoscaling.minReplicas }} + maxReplicas: {{ .Values.collector.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.collector.autoscaling.targetCPUUtilizationPercentage | default 80 }} + {{- if .Values.collector.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.collector.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/collector-sa.yaml b/rds/base/charts/jaeger/templates/collector-sa.yaml new file mode 100644 index 0000000..98b2f06 --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.collector.enabled .Values.collector.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.collector.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/collector-servicemonitor.yaml b/rds/base/charts/jaeger/templates/collector-servicemonitor.yaml new file mode 100644 index 0000000..8e01db1 --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-servicemonitor.yaml @@ -0,0 +1,38 @@ +{{- if and (.Values.collector.enabled) (.Values.collector.serviceMonitor.enabled)}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "jaeger.collector.name" . }} + {{- if .Values.collector.serviceMonitor.namespace }} + namespace: {{ .Values.collector.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector + {{- if .Values.collector.serviceMonitor.additionalLabels }} + {{- toYaml .Values.collector.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.collector.serviceMonitor.annotations }} + annotations: + {{- toYaml .Values.collector.serviceMonitor.annotations | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: admin + path: /metrics + {{- if .Values.collector.serviceMonitor.interval }} + interval: {{ .Values.collector.serviceMonitor.interval }} + {{- end }} + {{- if .Values.collector.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.collector.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app.kubernetes.io/component: collector + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/collector-svc.yaml b/rds/base/charts/jaeger/templates/collector-svc.yaml new file mode 100644 index 0000000..165124a --- /dev/null +++ b/rds/base/charts/jaeger/templates/collector-svc.yaml @@ -0,0 +1,47 @@ +{{- if .Values.collector.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jaeger.collector.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: collector +{{- if .Values.collector.service.annotations }} + annotations: + {{- toYaml .Values.collector.service.annotations | nindent 4 }} +{{- end }} +spec: + ports: + - name: grpc + port: {{ .Values.collector.service.grpc.port }} +{{- if and (eq .Values.collector.service.type "NodePort") (.Values.collector.service.grpc.nodePort) }} + nodePort: {{ .Values.collector.service.grpc.nodePort }} +{{- end }} + protocol: TCP + targetPort: grpc + - name: http + port: {{ .Values.collector.service.http.port }} +{{- if and (eq .Values.collector.service.type "NodePort") (.Values.collector.service.http.nodePort) }} + nodePort: {{ .Values.collector.service.http.nodePort }} +{{- end }} + protocol: TCP + targetPort: http +{{- if .Values.collector.service.zipkin }} + - name: zipkin + port: {{ .Values.collector.service.zipkin.port }} +{{- if and (eq .Values.collector.service.type "NodePort") (.Values.collector.service.zipkin.nodePort) }} + nodePort: {{ .Values.collector.service.zipkin.nodePort }} +{{- end }} + protocol: TCP + targetPort: zipkin +{{- end }} + - name: admin + port: 14269 + targetPort: admin + selector: + {{- include "jaeger.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: collector + type: {{ .Values.collector.service.type }} +{{- template "loadBalancerSourceRanges" .Values.collector }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/elasticsearch-secret.yaml b/rds/base/charts/jaeger/templates/elasticsearch-secret.yaml new file mode 100644 index 0000000..14eb7bb --- /dev/null +++ b/rds/base/charts/jaeger/templates/elasticsearch-secret.yaml @@ -0,0 +1,12 @@ +{{ if and (eq .Values.storage.type "elasticsearch") .Values.storage.elasticsearch.usePassword (not .Values.storage.elasticsearch.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "jaeger.fullname" . }}-elasticsearch + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} +type: Opaque +data: + password: {{ .Values.storage.elasticsearch.password | b64enc | quote }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/es-index-cleaner-cronjob.yaml b/rds/base/charts/jaeger/templates/es-index-cleaner-cronjob.yaml new file mode 100644 index 0000000..10da669 --- /dev/null +++ b/rds/base/charts/jaeger/templates/es-index-cleaner-cronjob.yaml @@ -0,0 +1,84 @@ +{{- if .Values.esIndexCleaner.enabled -}} +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: {{ include "jaeger.fullname" . }}-es-index-cleaner + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: es-index-cleaner +{{- if .Values.esIndexCleaner.annotations }} + annotations: + {{- toYaml .Values.esIndexCleaner.annotations | nindent 4 }} +{{- end }} +spec: + concurrencyPolicy: "Forbid" + schedule: {{ .Values.esIndexCleaner.schedule | quote }} + successfulJobsHistoryLimit: {{ .Values.esIndexCleaner.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.esIndexCleaner.failedJobsHistoryLimit }} + suspend: false + jobTemplate: + spec: + template: + metadata: + {{- if .Values.esIndexCleaner.podAnnotations }} + annotations: + {{- toYaml .Values.esIndexCleaner.podAnnotations | nindent 12 }} + {{- end }} + labels: + {{- include "jaeger.selectorLabels" . | nindent 12 }} + app.kubernetes.io/component: es-index-cleaner + {{- if .Values.esIndexCleaner.podLabels }} + {{- toYaml .Values.esIndexCleaner.podLabels | nindent 12 }} + {{- end }} + spec: + serviceAccountName: {{ template "jaeger.esIndexCleaner.serviceAccountName" . }} + {{- with .Values.esIndexCleaner.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.esIndexCleaner.podSecurityContext | nindent 12 }} + containers: + - name: {{ include "jaeger.fullname" . }}-es-index-cleaner + securityContext: + {{- toYaml .Values.esIndexCleaner.securityContext | nindent 14 }} + image: "{{ .Values.esIndexCleaner.image }}:{{ .Values.esIndexCleaner.tag }}" + imagePullPolicy: {{ .Values.esIndexCleaner.pullPolicy }} + args: + - {{ .Values.esIndexCleaner.numberOfDays | quote }} + - {{ include "elasticsearch.client.url" . }} + env: + {{- if .Values.esIndexCleaner.extraEnv }} + {{- toYaml .Values.esIndexCleaner.extraEnv | nindent 14 }} + {{- end }} + {{ include "elasticsearch.env" . | nindent 14 }} + resources: + {{- toYaml .Values.esIndexCleaner.resources | nindent 14 }} + volumeMounts: + {{- range .Values.esIndexCleaner.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.esIndexCleaner.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + restartPolicy: OnFailure + {{- with .Values.esIndexCleaner.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.esIndexCleaner.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.esIndexCleaner.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/es-index-cleaner-sa.yaml b/rds/base/charts/jaeger/templates/es-index-cleaner-sa.yaml new file mode 100644 index 0000000..cd26fa7 --- /dev/null +++ b/rds/base/charts/jaeger/templates/es-index-cleaner-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.esIndexCleaner.enabled .Values.esIndexCleaner.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.esIndexCleaner.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: es-index-cleaner +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/hotrod-deploy.yaml b/rds/base/charts/jaeger/templates/hotrod-deploy.yaml new file mode 100644 index 0000000..9dbb9ff --- /dev/null +++ b/rds/base/charts/jaeger/templates/hotrod-deploy.yaml @@ -0,0 +1,66 @@ +{{- if .Values.hotrod.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "jaeger.fullname" . }}-hotrod + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: hotrod +spec: + replicas: {{ .Values.hotrod.replicaCount }} + selector: + matchLabels: + {{- include "jaeger.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: hotrod + template: + metadata: + labels: + {{- include "jaeger.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: hotrod + spec: + securityContext: + {{- toYaml .Values.hotrod.podSecurityContext | nindent 8 }} + serviceAccountName: {{ template "jaeger.hotrod.serviceAccountName" . }} + {{- with .Values.hotrod.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "jaeger.fullname" . }}-hotrod + securityContext: + {{- toYaml .Values.hotrod.securityContext | nindent 12 }} + image: {{ .Values.hotrod.image.repository }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.hotrod.image.pullPolicy }} + env: + - name: JAEGER_AGENT_HOST + value: {{ template "jaeger.hotrod.tracing.host" . }} + - name: JAEGER_AGENT_PORT + value: {{ .Values.hotrod.tracing.port | quote }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.hotrod.resources | nindent 12 }} + {{- with .Values.hotrod.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hotrod.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hotrod.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/hotrod-ing.yaml b/rds/base/charts/jaeger/templates/hotrod-ing.yaml new file mode 100644 index 0000000..f9a9009 --- /dev/null +++ b/rds/base/charts/jaeger/templates/hotrod-ing.yaml @@ -0,0 +1,33 @@ +{{- if .Values.hotrod.enabled -}} +{{- if .Values.hotrod.ingress.enabled -}} +{{- $serviceName := include "jaeger.fullname" . -}} +{{- $servicePort := .Values.hotrod.service.port -}} +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: {{ include "jaeger.fullname" . }}-hotrod + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: hotrod +{{- if .Values.hotrod.ingress.annotations }} + annotations: + {{- toYaml .Values.hotrod.ingress.annotations | nindent 4 }} +{{- end }} +spec: + rules: + {{- range $host := .Values.hotrod.ingress.hosts }} + - host: {{ $host }} + http: + paths: + - path: / + backend: + serviceName: {{ $serviceName }}-hotrod + servicePort: {{ $servicePort }} + {{- end -}} + {{- if .Values.hotrod.ingress.tls }} + tls: + {{- toYaml .Values.hotrod.ingress.tls | nindent 4 }} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/hotrod-sa.yaml b/rds/base/charts/jaeger/templates/hotrod-sa.yaml new file mode 100644 index 0000000..1674e6d --- /dev/null +++ b/rds/base/charts/jaeger/templates/hotrod-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.hotrod.enabled .Values.hotrod.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.hotrod.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: hotrod +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/hotrod-svc.yaml b/rds/base/charts/jaeger/templates/hotrod-svc.yaml new file mode 100644 index 0000000..41fdef9 --- /dev/null +++ b/rds/base/charts/jaeger/templates/hotrod-svc.yaml @@ -0,0 +1,25 @@ +{{- if .Values.hotrod.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "jaeger.fullname" . }}-hotrod + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: hotrod +{{- if .Values.hotrod.service.annotations }} + annotations: + {{- toYaml .Values.hotrod.service.annotations | nindent 4 }} +{{- end }} +spec: + type: {{ .Values.hotrod.service.type }} + ports: + - name: http + port: {{ .Values.hotrod.service.port }} + protocol: TCP + targetPort: http + selector: + {{- include "jaeger.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: hotrod +{{- template "loadBalancerSourceRanges" .Values.hotrod }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/ingester-deploy.yaml b/rds/base/charts/jaeger/templates/ingester-deploy.yaml new file mode 100644 index 0000000..6532f09 --- /dev/null +++ b/rds/base/charts/jaeger/templates/ingester-deploy.yaml @@ -0,0 +1,131 @@ +{{- if .Values.ingester.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "jaeger.fullname" . }}-ingester + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: ingester +{{- if .Values.ingester.annotations }} + annotations: + {{- toYaml .Values.ingester.annotations | nindent 4 }} +{{- end }} +spec: +{{- if not .Values.ingester.autoscaling.enabled }} + replicas: {{ .Values.ingester.replicaCount }} +{{- end }} + selector: + matchLabels: + {{- include "jaeger.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: ingester + strategy: + type: Recreate + template: + metadata: + annotations: +{{- if .Values.ingester.podAnnotations }} + {{- toYaml .Values.ingester.podAnnotations | nindent 8 }} +{{- end }} + labels: + {{- include "jaeger.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: ingester +{{- if .Values.ingester.podLabels }} + {{- toYaml .Values.ingester.podLabels | nindent 8 }} +{{- end }} + spec: + securityContext: + {{- toYaml .Values.ingester.podSecurityContext | nindent 8 }} + {{- with .Values.ingester.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + {{- toYaml .Values.ingester.nodeSelector | nindent 8 }} +{{- if .Values.ingester.tolerations }} + tolerations: + {{- toYaml .Values.ingester.tolerations | nindent 8 }} +{{- end }} + containers: + - name: {{ include "jaeger.fullname" . }}-ingester + securityContext: + {{- toYaml .Values.ingester.securityContext | nindent 10 }} + image: {{ .Values.ingester.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.ingester.pullPolicy }} + args: + {{- range $key, $value := .Values.ingester.cmdlineParams }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + {{- include "storage.cmdArgs" . | nindent 10 }} + env: + {{- if .Values.ingester.extraEnv }} + {{- toYaml .Values.ingester.extraEnv | nindent 10 }} + {{- end }} + - name: SPAN_STORAGE_TYPE + value: {{ .Values.storage.type }} + {{- include "storage.env" . | nindent 10 }} + - name: KAFKA_CONSUMER_BROKERS + value: {{ include "helm-toolkit.utils.joinListWithComma" .Values.storage.kafka.brokers }} + - name: KAFKA_CONSUMER_TOPIC + value: {{ .Values.storage.kafka.topic }} + - name: KAFKA_CONSUMER_AUTHENTICATION + value: {{ .Values.storage.kafka.authentication }} + ports: + - containerPort: 14270 + name: admin + protocol: TCP + readinessProbe: + httpGet: + path: / + port: admin + livenessProbe: + httpGet: + path: / + port: admin + resources: + {{- toYaml .Values.ingester.resources | nindent 10 }} + volumeMounts: + {{- range .Values.ingester.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.ingester.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.storage.cassandra.tls.enabled }} + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/ca-cert.pem" + subPath: "ca-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-cert.pem" + subPath: "client-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-key.pem" + subPath: "client-key.pem" + readOnly: true + {{- end }} + dnsPolicy: {{ .Values.ingester.dnsPolicy }} + restartPolicy: Always + volumes: + {{- range .Values.ingester.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.ingester.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/ingester-hpa.yaml b/rds/base/charts/jaeger/templates/ingester-hpa.yaml new file mode 100644 index 0000000..8cd9298 --- /dev/null +++ b/rds/base/charts/jaeger/templates/ingester-hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.ingester.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "jaeger.ingester.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: ingester +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ template "jaeger.ingester.name" . }} + minReplicas: {{ .Values.ingester.autoscaling.minReplicas }} + maxReplicas: {{ .Values.ingester.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.ingester.autoscaling.targetCPUUtilizationPercentage | default 80 }} + {{- if .Values.ingester.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.ingester.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/ingester-sa.yaml b/rds/base/charts/jaeger/templates/ingester-sa.yaml new file mode 100644 index 0000000..9ea02b5 --- /dev/null +++ b/rds/base/charts/jaeger/templates/ingester-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.ingester.enabled .Values.ingester.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.ingester.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: ingester +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/ingester-servicemonitor.yaml b/rds/base/charts/jaeger/templates/ingester-servicemonitor.yaml new file mode 100644 index 0000000..1897c1e --- /dev/null +++ b/rds/base/charts/jaeger/templates/ingester-servicemonitor.yaml @@ -0,0 +1,38 @@ +{{- if and (.Values.ingester.enabled) (.Values.ingester.serviceMonitor.enabled)}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "jaeger.ingester.name" . }} + {{- if .Values.ingester.serviceMonitor.namespace }} + namespace: {{ .Values.ingester.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: ingester + {{- if .Values.ingester.serviceMonitor.additionalLabels }} + {{- toYaml .Values.ingester.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.ingester.serviceMonitor.annotations }} + annotations: + {{- toYaml .Values.ingester.serviceMonitor.annotations | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: admin + path: /metrics + {{- if .Values.ingester.serviceMonitor.interval }} + interval: {{ .Values.ingester.serviceMonitor.interval }} + {{- end }} + {{- if .Values.ingester.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.ingester.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app.kubernetes.io/component: ingester + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/ingester-svc.yaml b/rds/base/charts/jaeger/templates/ingester-svc.yaml new file mode 100644 index 0000000..659f07b --- /dev/null +++ b/rds/base/charts/jaeger/templates/ingester-svc.yaml @@ -0,0 +1,24 @@ +{{- if .Values.ingester.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jaeger.ingester.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: ingester +{{- if .Values.ingester.service.annotations }} + annotations: + {{- toYaml .Values.ingester.service.annotations | nindent 4 }} +{{- end }} +spec: + ports: + - name: admin + port: 14270 + targetPort: admin + selector: + {{- include "jaeger.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: ingester + type: {{ .Values.ingester.service.type }} +{{- template "loadBalancerSourceRanges" .Values.ingester }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/query-configmap.yaml b/rds/base/charts/jaeger/templates/query-configmap.yaml new file mode 100644 index 0000000..3643c73 --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-configmap.yaml @@ -0,0 +1,13 @@ +{{- if .Values.query.config }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "jaeger.fullname" . }}-ui-configuration + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query +data: + query-ui-config.json: |- +{{ tpl .Values.query.config . | indent 4 }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/query-deploy.yaml b/rds/base/charts/jaeger/templates/query-deploy.yaml new file mode 100644 index 0000000..90f53e2 --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-deploy.yaml @@ -0,0 +1,212 @@ +{{- if .Values.query.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "jaeger.query.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query +{{- if .Values.query.annotations }} + annotations: + {{- toYaml .Values.query.annotations | nindent 4 }} +{{- end }} +spec: + replicas: {{ .Values.query.replicaCount }} + selector: + matchLabels: + {{- include "jaeger.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: query + strategy: + type: Recreate + template: + metadata: +{{- if .Values.query.podAnnotations }} + annotations: + {{- toYaml .Values.query.podAnnotations | nindent 8 }} +{{- end }} + labels: + {{- include "jaeger.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: query +{{- if .Values.query.podLabels }} + {{- toYaml .Values.query.podLabels | nindent 8 }} +{{- end }} + spec: + {{- with .Values.query.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + securityContext: + {{- toYaml .Values.query.podSecurityContext | nindent 8 }} + serviceAccountName: {{ template "jaeger.query.serviceAccountName" . }} + {{- with .Values.query.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ template "jaeger.query.name" . }} + securityContext: + {{- toYaml .Values.query.securityContext | nindent 10 }} + image: {{ .Values.query.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.query.pullPolicy }} + args: + {{- range $key, $value := .Values.query.cmdlineParams }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + {{- include "storage.cmdArgs" . | nindent 10 }} + env: + {{- if .Values.query.extraEnv }} + {{- toYaml .Values.query.extraEnv | nindent 10 }} + {{- end }} + - name: SPAN_STORAGE_TYPE + value: {{ .Values.storage.type }} + {{- include "storage.env" . | nindent 10 }} + {{- if .Values.query.basePath }} + - name: QUERY_BASE_PATH + value: {{ .Values.query.basePath | quote }} + {{- end }} + - name: JAEGER_AGENT_PORT + value: "6831" + {{- if .Values.query.config}} + - name: QUERY_UI_CONFIG + value: /etc/conf/query-ui-config.json + {{- end }} + ports: + - name: query + containerPort: 16686 + protocol: TCP + - name: admin + containerPort: 16687 + protocol: TCP + resources: + {{- toYaml .Values.query.resources | nindent 10 }} + volumeMounts: + {{- range .Values.query.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.query.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- if .Values.storage.cassandra.tls.enabled }} + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/ca-cert.pem" + subPath: "ca-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-cert.pem" + subPath: "client-cert.pem" + readOnly: true + - name: {{ .Values.storage.cassandra.tls.secretName }} + mountPath: "/cassandra-tls/client-key.pem" + subPath: "client-key.pem" + readOnly: true + {{- end }} + {{- if .Values.query.config}} + - name: ui-configuration + mountPath: /etc/conf/ + {{- end }} + livenessProbe: + httpGet: + path: / + port: admin + readinessProbe: + httpGet: + path: / + port: admin +{{- if .Values.query.agentSidecar.enabled }} + - name: {{ template "jaeger.agent.name" . }}-sidecar + securityContext: + {{- toYaml .Values.query.securityContext | nindent 10 }} + image: {{ .Values.agent.image }}:{{ .Values.tag }} + imagePullPolicy: {{ .Values.agent.pullPolicy }} + args: + {{- range $key, $value := .Values.agent.cmdlineParams }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + env: + {{- if not (hasKey .Values.agent.cmdlineParams "reporter.grpc.host-port") }} + - name: REPORTER_GRPC_HOST_PORT + value: {{ include "jaeger.collector.name" . }}:{{ .Values.collector.service.grpc.port }} + {{- end }} + ports: + - name: admin + containerPort: 14271 + protocol: TCP + volumeMounts: + {{- range .Values.agent.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.agent.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: admin + readinessProbe: + httpGet: + path: / + port: admin +{{- end }} + dnsPolicy: {{ .Values.query.dnsPolicy }} + restartPolicy: Always + volumes: + {{- range .Values.query.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.query.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- if .Values.query.config}} + - name: ui-configuration + configMap: + name: {{ include "jaeger.fullname" . }}-ui-configuration + {{- end }} +{{- if .Values.query.agentSidecar.enabled }} + {{- range .Values.agent.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- range .Values.agent.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} +{{- end }} + {{- with .Values.query.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.query.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.query.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/query-ing.yaml b/rds/base/charts/jaeger/templates/query-ing.yaml new file mode 100644 index 0000000..88ba4eb --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-ing.yaml @@ -0,0 +1,31 @@ +{{- if .Values.query.ingress.enabled -}} +{{- $servicePort := .Values.query.service.port -}} +{{- $basePath := .Values.query.basePath -}} +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: {{ template "jaeger.query.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query + {{- if .Values.query.ingress.annotations }} + annotations: + {{- toYaml .Values.query.ingress.annotations | nindent 4 }} + {{- end }} +spec: + rules: + {{- range $host := .Values.query.ingress.hosts }} + - host: {{ $host }} + http: + paths: + - path: {{ $basePath }} + backend: + serviceName: {{ template "jaeger.query.name" $ }} + servicePort: {{ $servicePort }} + {{- end -}} + {{- if .Values.query.ingress.tls }} + tls: + {{- toYaml .Values.query.ingress.tls | nindent 4 }} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/query-sa.yaml b/rds/base/charts/jaeger/templates/query-sa.yaml new file mode 100644 index 0000000..32171bc --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.query.enabled .Values.query.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.query.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/query-servicemonitor.yaml b/rds/base/charts/jaeger/templates/query-servicemonitor.yaml new file mode 100644 index 0000000..12c8cfe --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-servicemonitor.yaml @@ -0,0 +1,37 @@ +{{- if and (.Values.query.enabled) (.Values.query.serviceMonitor.enabled)}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "jaeger.query.name" . }} + {{- if .Values.query.serviceMonitor.namespace }} + namespace: {{ .Values.query.serviceMonitor.namespace }} + {{- else }} + {{- end }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query + {{- if .Values.query.serviceMonitor.additionalLabels }} + {{- toYaml .Values.query.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} + {{- if .Values.query.serviceMonitor.annotations }} + annotations: + {{- toYaml .Values.query.serviceMonitor.annotations | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: admin + path: /metrics + {{- if .Values.query.serviceMonitor.interval }} + interval: {{ .Values.query.serviceMonitor.interval }} + {{- end }} + {{- if .Values.query.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.query.serviceMonitor.scrapeTimeout }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + app.kubernetes.io/component: query + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/rds/base/charts/jaeger/templates/query-svc.yaml b/rds/base/charts/jaeger/templates/query-svc.yaml new file mode 100644 index 0000000..6a5095f --- /dev/null +++ b/rds/base/charts/jaeger/templates/query-svc.yaml @@ -0,0 +1,32 @@ +{{- if .Values.query.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "jaeger.query.name" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: query +{{- if .Values.query.service.annotations }} + annotations: + {{- toYaml .Values.query.service.annotations | nindent 4 }} +{{- end }} +spec: + ports: + - name: query + port: {{ .Values.query.service.port }} + protocol: TCP + targetPort: query +{{- if and (eq .Values.query.service.type "NodePort") (.Values.query.service.nodePort) }} + nodePort: {{ .Values.query.service.nodePort }} +{{- end }} + - name: admin + port: 16687 + protocol: TCP + targetPort: admin + selector: + {{- include "jaeger.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: query + type: {{ .Values.query.service.type }} +{{- template "loadBalancerSourceRanges" .Values.query }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/spark-cronjob.yaml b/rds/base/charts/jaeger/templates/spark-cronjob.yaml new file mode 100644 index 0000000..1b99725 --- /dev/null +++ b/rds/base/charts/jaeger/templates/spark-cronjob.yaml @@ -0,0 +1,98 @@ +{{- if .Values.spark.enabled -}} +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: {{ include "jaeger.fullname" . }}-spark + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: spark +{{- if .Values.spark.annotations }} + annotations: + {{- toYaml .Values.spark.annotations | nindent 4 }} +{{- end }} +spec: + schedule: {{ .Values.spark.schedule | quote }} + successfulJobsHistoryLimit: {{ .Values.spark.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.spark.failedJobsHistoryLimit }} + jobTemplate: + spec: + template: + metadata: + labels: + {{- include "jaeger.selectorLabels" . | nindent 12 }} + app.kubernetes.io/component: spark + {{- if .Values.spark.podLabels }} + {{- toYaml .Values.spark.podLabels | nindent 12 }} + {{- end }} + spec: + serviceAccountName: {{ template "jaeger.spark.serviceAccountName" . }} + {{- with .Values.spark.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} + containers: + - name: {{ include "jaeger.fullname" . }}-spark + image: {{ .Values.spark.image }}:{{ .Values.spark.tag }} + imagePullPolicy: {{ .Values.spark.pullPolicy }} + args: + {{- range $key, $value := .Values.spark.cmdlineParams }} + {{- if $value }} + - --{{ $key }}={{ $value }} + {{- else }} + - --{{ $key }} + {{- end }} + {{- end }} + env: + - name: STORAGE + value: {{ .Values.storage.type }} + {{- include "storage.env" . | nindent 14 }} + {{- if .Values.spark.extraEnv }} + {{- toYaml .Values.spark.extraEnv | nindent 14 }} + {{- end }} + - name: CASSANDRA_CONTACT_POINTS + value: {{ include "cassandra.contact_points" . }} + - name: ES_NODES + value: {{ include "elasticsearch.client.url" . }} + - name: ES_NODES_WAN_ONLY + value: {{ .Values.storage.elasticsearch.nodesWanOnly | quote }} + resources: + {{- toYaml .Values.spark.resources | nindent 14 }} + volumeMounts: + {{- range .Values.spark.extraConfigmapMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + {{- range .Values.spark.extraSecretMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + subPath: {{ .subPath }} + readOnly: {{ .readOnly }} + {{- end }} + restartPolicy: OnFailure + volumes: + {{- range .Values.spark.extraConfigmapMounts }} + - name: {{ .name }} + configMap: + name: {{ .configMap }} + {{- end }} + {{- range .Values.spark.extraSecretMounts }} + - name: {{ .name }} + secret: + secretName: {{ .secretName }} + {{- end }} + {{- with .Values.spark.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.spark.affinity }} + affinity: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.spark.tolerations }} + tolerations: + {{- toYaml . | nindent 12 }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/jaeger/templates/spark-sa.yaml b/rds/base/charts/jaeger/templates/spark-sa.yaml new file mode 100644 index 0000000..6ac0732 --- /dev/null +++ b/rds/base/charts/jaeger/templates/spark-sa.yaml @@ -0,0 +1,10 @@ +{{- if and .Values.spark.enabled .Values.spark.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "jaeger.spark.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "jaeger.labels" . | nindent 4 }} + app.kubernetes.io/component: spark +{{- end -}} diff --git a/rds/base/charts/jaeger/values.yaml b/rds/base/charts/jaeger/values.yaml new file mode 100644 index 0000000..4f70601 --- /dev/null +++ b/rds/base/charts/jaeger/values.yaml @@ -0,0 +1,538 @@ +# Default values for jaeger. +# This is a YAML-formatted file. +# Jaeger values are grouped by component. Cassandra values override subchart values + +provisionDataStore: + cassandra: true + elasticsearch: false + kafka: false + +tag: 1.18.0 + +nameOverride: "" +fullnameOverride: "" + +storage: + # allowed values (cassandra, elasticsearch) + type: cassandra + cassandra: + host: cassandra + port: 9042 + tls: + enabled: false + secretName: cassandra-tls-secret + user: user + usePassword: true + password: password + keyspace: jaeger_v1_test + ## Use existing secret (ignores previous password) + # existingSecret: + ## Cassandra related env vars to be configured on the concerned components + extraEnv: [] + # - name: CASSANDRA_SERVERS + # value: cassandra + # - name: CASSANDRA_PORT + # value: 9042 + # - name: CASSANDRA_KEYSPACE + # value: jaeger_v1_test + # - name: CASSANDRA_TLS_ENABLED + # value: false + ## Cassandra related cmd line opts to be configured on the concerned components + cmdlineParams: {} + # cassandra.servers: cassandra + # cassandra.port: 9042 + # cassandra.keyspace: jaeger_v1_test + # cassandra.tls.enabled: false + elasticsearch: + scheme: http + host: elasticsearch-master + port: 9200 + user: elastic + usePassword: true + password: changeme + # indexPrefix: test + ## Use existing secret (ignores previous password) + # existingSecret: + # existingSecretKey: + nodesWanOnly: false + extraEnv: [] + ## ES related env vars to be configured on the concerned components + # - name: ES_SERVER_URLS + # value: http://elasticsearch-master:9200 + # - name: ES_USERNAME + # value: elastic + # - name: ES_INDEX_PREFIX + # value: test + ## ES related cmd line opts to be configured on the concerned components + cmdlineParams: {} + # es.server-urls: http://elasticsearch-master:9200 + # es.username: elastic + # es.index-prefix: test + kafka: + brokers: + - kafka:9092 + topic: jaeger_v1_test + authentication: none + extraEnv: [] + +# Begin: Override values on the Cassandra subchart to customize for Jaeger +cassandra: + persistence: + # To enable persistence, please see the documentation for the Cassandra chart + enabled: false + config: + cluster_name: jaeger + seed_size: 1 + dc_name: dc1 + rack_name: rack1 + endpoint_snitch: GossipingPropertyFileSnitch +# End: Override values on the Cassandra subchart to customize for Jaeger + +# Begin: Override values on the Kafka subchart to customize for Jaeger +kafka: + replicas: 1 + configurationOverrides: + "auto.create.topics.enable": true + zookeeper: + replicaCount: 1 +# End: Override values on the Kafka subchart to customize for Jaeger + +# Begin: Default values for the various components of Jaeger +# This chart has been based on the Kubernetes integration found in the following repo: +# https://github.com/jaegertracing/jaeger-kubernetes/blob/master/production/jaeger-production-template.yml +# +# This is the jaeger-cassandra-schema Job which sets up the Cassandra schema for +# use by Jaeger +schema: + annotations: {} + image: jaegertracing/jaeger-cassandra-schema + imagePullSecrets: [] + pullPolicy: IfNotPresent + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 256m + # memory: 128Mi + serviceAccount: + create: true + name: + podAnnotations: {} + podLabels: {} + securityContext: {} + podSecurityContext: {} + ## Deadline for cassandra schema creation job + activeDeadlineSeconds: 300 + extraEnv: [] + # - name: MODE + # value: prod + # - name: TRACE_TTL + # value: 172800 + # - name: DEPENDENCIES_TTL + # value: 0 + +# For configurable values of the elasticsearch if provisioned, please see: +# https://github.com/elastic/helm-charts/tree/master/elasticsearch#configuration +elasticsearch: {} + +ingester: + enabled: false + podSecurityContext: {} + securityContext: {} + annotations: {} + image: jaegertracing/jaeger-ingester + imagePullSecrets: [] + pullPolicy: IfNotPresent + dnsPolicy: ClusterFirst + cmdlineParams: {} + replicaCount: 1 + autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + # targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + service: + annotations: {} + # List of IP ranges that are allowed to access the load balancer (if supported) + loadBalancerSourceRanges: [] + type: ClusterIP + resources: {} + # limits: + # cpu: 1 + # memory: 1Gi + # requests: + # cpu: 500m + # memory: 512Mi + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + podAnnotations: {} + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + extraSecretMounts: [] + extraConfigmapMounts: [] + + serviceMonitor: + enabled: false + additionalLabels: {} + +agent: + podSecurityContext: {} + securityContext: {} + enabled: true + annotations: {} + image: jaegertracing/jaeger-agent + imagePullSecrets: [] + pullPolicy: IfNotPresent + cmdlineParams: {} + extraEnv: [] + daemonset: + useHostPort: false + service: + annotations: {} + # List of IP ranges that are allowed to access the load balancer (if supported) + loadBalancerSourceRanges: [] + type: ClusterIP + # zipkinThriftPort :accept zipkin.thrift over compact thrift protocol + zipkinThriftPort: 5775 + # compactPort: accept jaeger.thrift over compact thrift protocol + compactPort: 6831 + # binaryPort: accept jaeger.thrift over binary thrift protocol + binaryPort: 6832 + # samplingPort: (HTTP) serve configs, sampling strategies + samplingPort: 5778 + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 256m + # memory: 128Mi + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + podAnnotations: {} + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + extraSecretMounts: [] + # - name: jaeger-tls + # mountPath: /tls + # subPath: "" + # secretName: jaeger-tls + # readOnly: true + extraConfigmapMounts: [] + # - name: jaeger-config + # mountPath: /config + # subPath: "" + # configMap: jaeger-config + # readOnly: true + useHostNetwork: false + dnsPolicy: ClusterFirst + priorityClassName: "" + + serviceMonitor: + enabled: false + additionalLabels: {} + +collector: + podSecurityContext: {} + securityContext: {} + enabled: true + annotations: {} + image: jaegertracing/jaeger-collector + imagePullSecrets: [] + pullPolicy: IfNotPresent + dnsPolicy: ClusterFirst + cmdlineParams: {} + replicaCount: 1 + autoscaling: + enabled: false + minReplicas: 2 + maxReplicas: 10 + # targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + service: + annotations: {} + # List of IP ranges that are allowed to access the load balancer (if supported) + loadBalancerSourceRanges: [] + type: ClusterIP + grpc: + port: 14250 + # nodePort: + # httpPort: can accept spans directly from clients in jaeger.thrift format + http: + port: 14268 + # nodePort: + # can accept Zipkin spans in JSON or Thrift + zipkin: {} + # port: 9411 + # nodePort: + resources: {} + # limits: + # cpu: 1 + # memory: 1Gi + # requests: + # cpu: 500m + # memory: 512Mi + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + podAnnotations: {} + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + extraSecretMounts: [] + # - name: jaeger-tls + # mountPath: /tls + # subPath: "" + # secretName: jaeger-tls + # readOnly: true + extraConfigmapMounts: [] + # - name: jaeger-config + # mountPath: /config + # subPath: "" + # configMap: jaeger-config + # readOnly: true + # samplingConfig: |- + # { + # "service_strategies": [ + # { + # "service": "foo", + # "type": "probabilistic", + # "param": 0.8, + # "operation_strategies": [ + # { + # "operation": "op1", + # "type": "probabilistic", + # "param": 0.2 + # }, + # { + # "operation": "op2", + # "type": "probabilistic", + # "param": 0.4 + # } + # ] + # }, + # { + # "service": "bar", + # "type": "ratelimiting", + # "param": 5 + # } + # ], + # "default_strategy": { + # "type": "probabilistic", + # "param": 1 + # } + # } + priorityClassName: "" + serviceMonitor: + enabled: false + additionalLabels: {} + +query: + enabled: true + podSecurityContext: {} + securityContext: {} + agentSidecar: + enabled: true + annotations: {} + image: jaegertracing/jaeger-query + imagePullSecrets: [] + pullPolicy: IfNotPresent + dnsPolicy: ClusterFirst + cmdlineParams: {} + extraEnv: [] + replicaCount: 1 + service: + annotations: {} + type: ClusterIP + # List of IP ranges that are allowed to access the load balancer (if supported) + loadBalancerSourceRanges: [] + port: 80 + # Specify a specific node port when type is NodePort + # nodePort: 32500 + ingress: + enabled: false + annotations: {} + # Used to create an Ingress record. + # hosts: + # - chart-example.local + # annotations: + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # tls: + # Secrets must be manually created in the namespace. + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 256m + # memory: 128Mi + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + podAnnotations: {} + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + extraConfigmapMounts: [] + # - name: jaeger-config + # mountPath: /config + # subPath: "" + # configMap: jaeger-config + # readOnly: true + priorityClassName: "" + serviceMonitor: + enabled: false + additionalLabels: {} + # config: |- + # { + # "dependencies": { + # "dagMaxNumServices": 200, + # "menuEnabled": true + # }, + # "archiveEnabled": true, + # "tracking": { + # "gaID": "UA-000000-2", + # "trackErrors": true + # } + # } + +spark: + enabled: false + annotations: {} + image: jaegertracing/spark-dependencies + imagePullSecrets: [] + tag: latest + pullPolicy: Always + cmdlineParams: {} + extraEnv: [] + schedule: "49 23 * * *" + successfulJobsHistoryLimit: 5 + failedJobsHistoryLimit: 5 + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 256m + # memory: 128Mi + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + extraSecretMounts: [] + extraConfigmapMounts: [] + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} + +esIndexCleaner: + enabled: false + securityContext: + runAsUser: 1000 + podSecurityContext: + runAsUser: 1000 + annotations: {} + image: jaegertracing/jaeger-es-index-cleaner + imagePullSecrets: [] + tag: latest + pullPolicy: Always + cmdlineParams: {} + extraEnv: [] + schedule: "55 23 * * *" + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 256m + # memory: 128Mi + numberOfDays: 7 + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + extraSecretMounts: [] + extraConfigmapMounts: [] + podAnnotations: {} + ## Additional pod labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + podLabels: {} +# End: Default values for the various components of Jaeger + +hotrod: + enabled: false + podSecurityContext: {} + securityContext: {} + replicaCount: 1 + image: + repository: jaegertracing/example-hotrod + pullPolicy: Always + pullSecrets: [] + service: + annotations: {} + name: hotrod + type: ClusterIP + # List of IP ranges that are allowed to access the load balancer (if supported) + loadBalancerSourceRanges: [] + port: 80 + ingress: + enabled: false + # Used to create Ingress record (should be used with service.type: ClusterIP). + hosts: + - chart-example.local + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + tls: + # Secrets must be manually created in the namespace. + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + 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 + serviceAccount: + create: true + name: + nodeSelector: {} + tolerations: [] + affinity: {} + tracing: + host: null + port: 6831 diff --git a/rds/base/charts/layer0_describo/.helmignore b/rds/base/charts/layer0_describo/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer0_describo/.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/rds/base/charts/layer0_describo/Chart.lock b/rds/base/charts/layer0_describo/Chart.lock new file mode 100644 index 0000000..0a9e907 --- /dev/null +++ b/rds/base/charts/layer0_describo/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: file://../postgresql + version: 10.14.3 +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:812d36067d088cad7eeb94005e23171d9532f1dc2b8f23bd633db1b795c4a577 +generated: "2023-02-07T10:30:53.443729026+01:00" diff --git a/rds/base/charts/layer0_describo/Chart.yaml b/rds/base/charts/layer0_describo/Chart.yaml new file mode 100644 index 0000000..ded3346 --- /dev/null +++ b/rds/base/charts/layer0_describo/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer0-describo +version: 0.2.9 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - describo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: postgresql + version: 10.14.3 + repository: file://../postgresql + tags: + - storage + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer0-describo-common + diff --git a/rds/base/charts/layer0_describo/charts/common-0.1.2.tgz b/rds/base/charts/layer0_describo/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b3acafe6e59b8856b01549d25f9356101b81b78b GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%*bZPIhecALMbT~%7Xf-&(9&4q zMj{DvBX7|>HvLbSnN$!u z|5Jn})yoVOaWb#69X!+j=i~FB)&HaMU~uUFw;;A!`8{@nlv@RqAQ-C+T9BED5sH>( zs})EUrg?a%QV-riBtrtP9NuV69`IX{=IFQ)lyK6V5?pJ87pHnAQV&kv!@~pX z`410lwdzBJbEzJ=?r)?g7GvqiE`Jmm!sHV8HW;r&b!DTSB>jYXCEOawIdrkxel9z; zP(?cn5^*bB!TB9XPsvIZH$twFC{O?ZXmU4bZDdc$cXO{PR?1DxE}ydOw_;TD*&DZ* zRlh4{A!vu{fZ(ZsZ9wbaa^9NKhHtD?Gzah0`KNAj>b#a(dl^rvtVwDH301~teUjxK>egs zDr8p)b}Dw|)+mi;j#=*L2edJMT%f(TlJF&d8&3XeA(8$?Wa(pHjiu~bOG@1oLyJ_D zLv0z|mRYQ#=XF8eg0ov%MA!p(O^erJc7a_JUp{C51r7OC3#tXiiwySQvLBoMuDbTR zu8s1g(Cu~_uaQq%_pQ$V&Haxjm}W9o+T!cSc<%pibUteT|6K&*!~O3q=oo$yL}--* z(y*=oxKB{QESG6i9NR{OcOpi|*)jZ_NCz%AMq6^=5=>Jl8VE8$VIo!hf3TPek_&|5 z3RvR{6|rMb7?&F_yfYT(@>h)3=iUF_bNHyzC1_Pz7AZk-5ly8+&iM88)0DJDb_`c# z$-?c`6e4LIae6G`HeD<65%V)(%V!wD)L$^@=&e2SSce#PNOwq9(clU<%_)Off z9;dAM)>g)>_Xj(MTVbWn9ej8Fjbq$c{TV}ITt+DTwYt@x8DF?iN9Z5^Sr2re1O1cf R&j0`b|Nl!7J^BC^008NE+2jBK literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer0_describo/charts/postgresql-10.14.3.tgz b/rds/base/charts/layer0_describo/charts/postgresql-10.14.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..e4364382343a254a4e182b9950b6c9b97c357359 GIT binary patch literal 53725 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

UKM^@za5AR5Ojdhsso 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 BhbI64 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer0_describo/defaults/nginx.conf b/rds/base/charts/layer0_describo/defaults/nginx.conf new file mode 100644 index 0000000..68e4d86 --- /dev/null +++ b/rds/base/charts/layer0_describo/defaults/nginx.conf @@ -0,0 +1,82 @@ +upstream api_socket_nodes { + ip_hash; + server 127.0.0.1:8080; +} + +server { + listen 80; + listen [::]:80; + server_name 127.0.0.1; + + proxy_buffering off; + + #charset koi8-r; + #access_log /var/log/nginx/host.access.log main; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + location ~ ^/api/(.*) { + #resolver 127.0.0.11 valid=30s; + set $api 127.0.0.1; + add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + #proxy_set_header Authorization $http_authorization; + # auth_request_set $token $upstream_http_x_auth_request_access_token; + # add_header 'Authorization' $token; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_send_timeout 120; + proxy_read_timeout 120; + send_timeout 120; + proxy_pass http://$api:8080/$1$is_args$args; + } + + location /socket.io/ { + proxy_http_version 1.1; + proxy_redirect off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://api_socket_nodes/socket.io/; + } + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} diff --git a/rds/base/charts/layer0_describo/defaults/type-definitions-lookup.json b/rds/base/charts/layer0_describo/defaults/type-definitions-lookup.json new file mode 100644 index 0000000..d3a628c --- /dev/null +++ b/rds/base/charts/layer0_describo/defaults/type-definitions-lookup.json @@ -0,0 +1,16 @@ +[ + { "name": "Person", "help": "A person (alive, dead, undead, or fictional)." }, + { "name": "Thing", "help": "The most generic type of item." }, + { + "name": "Organization", + "help": "An organization such as a school, NGO, corporation, club, etc." + }, + { + "name": "CreativeWork", + "help": "The most generic kind of creative work, including books, movies, photographs, software programs, etc." + }, + { + "name": "Dataset", + "help": "A body of structured information describing some topic(s) of interest." + } +] diff --git a/rds/base/charts/layer0_describo/defaults/type-definitions.json b/rds/base/charts/layer0_describo/defaults/type-definitions.json new file mode 100644 index 0000000..5fa4d81 --- /dev/null +++ b/rds/base/charts/layer0_describo/defaults/type-definitions.json @@ -0,0 +1,200 @@ +{ + "Person": { + "id": "http://schema.org/Person", + "name": "Person", + "help": "A person (alive, dead, undead, or fictional).", + "subClassOf": [ + "Thing" + ], + "allowAdditionalProperties": false, + "inputs": [ + { + "id": "http://schema.org/address", + "name": "address", + "help": "Physical address of the item.", + "multiple": false, + "type": [ + "Text" + ] + }, + { + "id": "http://schema.org/affiliation", + "name": "affiliation", + "help": "An organization that this person is affiliated with. For example, a school/university, a club, or a team.", + "multiple": false, + "type": [ + "Organization" + ] + }, + { + "id": "http://schema.org/email", + "name": "email", + "help": "Email address.", + "multiple": false, + "type": [ + "Text" + ] + }, + { + "id": "http://schema.org/familyName", + "name": "familyName", + "help": "Family name. In the U.S., the last name of a Person.", + "multiple": false, + "type": [ + "Text" + ] + }, + { + "id": "http://schema.org/givenName", + "name": "givenName", + "help": "Given name. In the U.S., the first name of a Person.", + "multiple": false, + "type": [ + "Text" + ] + } + ], + "linksTo": [ + "Organization" + ], + "hierarchy": [ + "Person", + "Thing" + ] + }, + "Thing": { + "id": "http://schema.org/Thing", + "name": "Thing", + "help": "The most generic type of item.", + "subClassOf": [], + "allowAdditionalProperties": false, + "inputs": [ + { + "id": "http://schema.org/description", + "name": "description", + "help": "A description of the item.", + "multiple": false, + "type": [ + "Text" + ] + }, + { + "id": "http://schema.org/name", + "name": "name", + "help": "The name of the item.", + "multiple": false, + "type": [ + "Text" + ] + } + ], + "linksTo": [ + "CreativeWork", + "Organization", + "Person" + ], + "hierarchy": [ + "Thing" + ] + }, + "Organization": { + "id": "http://schema.org/Organization", + "name": "Organization", + "help": "An organization such as a school, NGO, corporation, club, etc.", + "subClassOf": [ + "Thing" + ], + "allowAdditionalProperties": false, + "inputs": [ + { + "id": "http://schema.org/address", + "name": "address", + "help": "Physical address of the item.", + "multiple": false, + "type": [ + "Text" + ] + } + ], + "linksTo": [], + "hierarchy": [ + "Organization", + "Thing" + ] + }, + "CreativeWork": { + "id": "http://schema.org/CreativeWork", + "name": "CreativeWork", + "help": "The most generic kind of creative work, including books, movies, photographs, software programs, etc.", + "subClassOf": [ + "Thing" + ], + "allowAdditionalProperties": false, + "inputs": [ + { + "id": "http://schema.org/author", + "name": "creator", + "help": "The author of this content or rating. Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably. ", + "multiple": false, + "type": [ + "Person", + "Organization" + ] + } + ], + "linksTo": [ + "Organization", + "Person" + ], + "hierarchy": [ + "CreativeWork", + "Thing" + ] + }, + "Dataset": { + "id": "http://schema.org/Dataset", + "name": "Dataset", + "help": "A body of structured information describing some topic(s) of interest.", + "subClassOf": [ + "CreativeWork" + ], + "allowAdditionalProperties": false, + "inputs": [ + { + "id": "http://schema.org/datePublished", + "name": "datePublished", + "help": "Date of first broadcast/publication.", + "multiple": false, + "type": [ + "Date" + ] + }, + { + "id": "http://schema.org/zenodocategory", + "name": "zenodocategory", + "help": "The Zenodo Category: [ 'publication/book', 'publication section', '...', 'dataset', 'image/plot', '...' ]", + "multiple": false, + "type": [ + "Text" + ] + }, + { + "id": "http://schema.org/osfcategory", + "name": "osfcategory", + "help": "The OSF Category: [ 'analysis', 'communication', '...', 'procedure', 'instrumentation', '...' ]", + "multiple": false, + "type": [ + "Text" + ] + } + ], + "linksTo": [ + "CreativeWork" + ], + "hierarchy": [ + "Dataset", + "CreativeWork", + "Thing" + ] + } +} \ No newline at end of file diff --git a/rds/base/charts/layer0_describo/templates/NOTES.txt b/rds/base/charts/layer0_describo/templates/NOTES.txt new file mode 100644 index 0000000..e670bfc --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/NOTES.txt @@ -0,0 +1,21 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "layer0_describo.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "layer0_describo.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "layer0_describo.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "layer0_describo.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:80 +{{- end }} diff --git a/rds/base/charts/layer0_describo/templates/_helpers.tpl b/rds/base/charts/layer0_describo/templates/_helpers.tpl new file mode 100644 index 0000000..8b726c2 --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/_helpers.tpl @@ -0,0 +1,91 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer0_describo.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "layer0_describo.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer0_describo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the proper describo image name +*/}} +{{- define "image" -}} +{{- $registryName := .imageRoot.registry -}} +{{- $repositoryName := .imageRoot.repository -}} +{{- if .repository -}} +{{- $repositoryName = .repository -}} +{{- end -}} +{{- $tag := .imageRoot.tag | toString -}} +{{- if .global }} + {{- if .global.image }} + {{- if .global.image.registry }} + {{- $registryName = .global.image.registry -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper describo image name +*/}} +{{- define "layer0_describo.apiImage" -}} +{{- include "image" (dict "imageRoot" .Values.image "global" .Values.global "repository" .Values.image.apiRepository) -}} +{{- end -}} + +{{- define "layer0_describo.uiImage" -}} +{{ include "image" (dict "imageRoot" .Values.image "global" .Values.global "repository" .Values.image.uiRepository ) }} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer0_describo.labels" -}} +app.kubernetes.io/name: {{ include "layer0_describo.name" . }} +helm.sh/chart: {{ include "layer0_describo.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + +{{- define "layer0_describo.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain }} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain }} +{{- else }}localhost{{- end -}} +{{- end -}} diff --git a/rds/base/charts/layer0_describo/templates/configmap.yaml b/rds/base/charts/layer0_describo/templates/configmap.yaml new file mode 100644 index 0000000..7d08a0d --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/configmap.yaml @@ -0,0 +1,59 @@ +{{- $domains := .Values.domains -}} + {{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} + {{- end -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: describoconfig + namespace: {{ .Release.Namespace }} +data: + DB_HOST: {{ .Values.postgresql.fullnameOverride | quote }} + DB_PORT: {{ .Values.postgresql.service.port | quote }} + DB_USER: {{ .Values.postgresql.postgresqlUsername | quote }} + DB_PASSWORD: {{ .Values.postgresql.postgresqlPassword | quote }} + DB_DATABASE: {{ .Values.postgresql.postgresqlDatabase | quote }} + NODE_ENV: "production" + LOG_LEVEL: {{ .Values.environment.LOG_LEVEL | quote }} + ADMIN_PASSWORD: {{ .Values.environment.ADMIN_PASSWORD | quote }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: describo-configuration-file + namespace: {{ .Release.Namespace }} +data: + {{- $files := .Files }} + {{- range tuple "nginx.conf" "type-definitions-lookup.json" "type-definitions.json" }} + {{ . }}: |- +{{ printf "defaults/%s" . | $files.Get | indent 4 }} + {{- end }} + configuration.json: |- + { + "ui": { + "siteName": "Sciebo - Describo Online", + "logo": "http://www.researchobject.org/ro-crate/assets/img/ro-crate.svg", + "login": "", + "services": { + "owncloud": false, + "reva": false, + "s3": false, + "onedrive": false + }, + "basePath": "/", + "maxSessionLifetime": "86400", + "maxEntitiesPerTemplate": "100" + }, + "api": { + "port": 8080, + "periodicProcessInterval": 300, + "applications": [ + { + "name": "Owncloud ScieboRDS", + "secret": "{{ .Values.global.describo.api_secret }}" + } + ] + } + } diff --git a/rds/base/charts/layer0_describo/templates/deployment.yaml b/rds/base/charts/layer0_describo/templates/deployment.yaml new file mode 100644 index 0000000..1fe67c4 --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/deployment.yaml @@ -0,0 +1,121 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer0_describo.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer0_describo.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer0_describo.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer0_describo.labels" . | indent 8 }} + spec: + volumes: + - name: describo-configuration + configMap: + name: describo-configuration-file + items: + - key: configuration.json + path: configuration.json + - key: type-definitions-lookup.json + path: type-definitions-lookup.json + - key: type-definitions.json + path: type-definitions.json + - key: nginx.conf + path: nginx.conf + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: "api" + image: {{ template "layer0_describo.apiImage" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - name: describo-configuration + mountPath: /srv/api/configuration.json + subPath: configuration.json + readOnly: true + - name: describo-configuration + mountPath: /srv/profiles/type-definitions-lookup.json + subPath: type-definitions-lookup.json + readOnly: true + - name: describo-configuration + mountPath: /srv/profiles/type-definitions.json + subPath: type-definitions.json + readOnly: true + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: describo-pg-passwd + key: postgresql-password + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: admin-passwd + key: passwd + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: describoconfig + resources: + {{- toYaml .Values.resources | nindent 12 }} + - name: "ui" + image: {{ template "layer0_describo.uiImage" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - name: describo-configuration + mountPath: /etc/nginx/conf.d/default.conf + subPath: nginx.conf + readOnly: true + env: + - name: "VUE_APP_BASE_URL" + value: "{{ .Values.ingress.path }}" + - name: "NODE_ENV" + value: "production" + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer0_describo/templates/ingress.yaml b/rds/base/charts/layer0_describo/templates/ingress.yaml new file mode 100644 index 0000000..1e679a0 --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/ingress.yaml @@ -0,0 +1,28 @@ +{{- $fullName := include "layer0_describo.fullname" . -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: +{{ include "layer0_describo.labels" . | indent 4 }} + annotations: +{{- include "common.ingressAnnotations" . | nindent 4 }} +spec: + {{- if (include "common.tlsSecretName" .) }} + tls: + - hosts: + - {{ .Values.global.describo.domain }} + secretName: {{ include "common.tlsSecretName" . }} + {{- end }} + rules: + - host: {{ .Values.global.describo.domain }} + http: + paths: + - path: {{ .Values.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + # number: 80 + name: http \ No newline at end of file diff --git a/rds/base/charts/layer0_describo/templates/service.yaml b/rds/base/charts/layer0_describo/templates/service.yaml new file mode 100644 index 0000000..c730fe2 --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer0_describo.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer0_describo.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer0_describo.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer0_describo/templates/tests/test-connection.yaml b/rds/base/charts/layer0_describo/templates/tests/test-connection.yaml new file mode 100644 index 0000000..c21eae5 --- /dev/null +++ b/rds/base/charts/layer0_describo/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer0_describo.fullname" . }}-test-research" + labels: +{{ include "layer0_describo.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer0_describo.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer0_describo/values.yaml b/rds/base/charts/layer0_describo/values.yaml new file mode 100644 index 0000000..8fb3036 --- /dev/null +++ b/rds/base/charts/layer0_describo/values.yaml @@ -0,0 +1,87 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 + +image: + tag: 0.26.6 + pullPolicy: Always + registry: docker.io + uiRepository: arkisto/describo-online-ui + apiRepository: arkisto/describo-online-api + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: service + research-data-services.org/layer: layer0 + +fullnameOverride: layer0-describo + +service: + type: ClusterIP + port: 80 + targetPort: 80 + +ingress: + path: / + annotations: + nginx.org/server-snippets: | + location /socket.io/ { + proxy_http_version 1.1; + proxy_redirect off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://layer0-describo/socket.io/; + } +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +persistence: + enabled: true + accessModes: + - ReadWriteOnce + size: 1Gi + annotations: {} + +environment: + LOG_LEVEL: info + ADMIN_PASSWORD: adminpass + +# domains for webdav addresses (currently owncloud only) +domains: + - name: owncloud.local + ADDRESS: https://owncloud.local/owncloud + OAUTH_CLIENT_ID: ABC + OAUTH_CLIENT_SECRET: XYZ + +postgresql: + image: + tag: 14.1.0 + service: + port: "5432" + fullnameOverride: postgresql + postgresqlDatabase: "describo" + postgresqlUsername: "admin" + postgresqlPassword: "admin" + +global: + describo: + domain: "" diff --git a/rds/base/charts/layer0_helper_describo_token_updater/.helmignore b/rds/base/charts/layer0_helper_describo_token_updater/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/.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/rds/base/charts/layer0_helper_describo_token_updater/Chart.lock b/rds/base/charts/layer0_helper_describo_token_updater/Chart.lock new file mode 100644 index 0000000..0b92850 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:907f03fdcae7108b8137782291baf28aad5dff00fe221ee3bb3bebd8d1101c9c +generated: "2023-02-07T10:30:53.980764318+01:00" diff --git a/rds/base/charts/layer0_helper_describo_token_updater/Chart.yaml b/rds/base/charts/layer0_helper_describo_token_updater/Chart.yaml new file mode 100644 index 0000000..ab3a0d5 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer0-helper-describo-token-updater +version: 0.2.1 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - describo + - describo-online + - updater + - layer3-token-storage +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer0-helper-describo-token-updater-common + diff --git a/rds/base/charts/layer0_helper_describo_token_updater/charts/common-0.1.2.tgz b/rds/base/charts/layer0_helper_describo_token_updater/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer0_helper_describo_token_updater/templates/_helpers.tpl b/rds/base/charts/layer0_helper_describo_token_updater/templates/_helpers.tpl new file mode 100644 index 0000000..3c91063 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer0_helper_describo_token_updater.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "layer0_helper_describo_token_updater.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer0_helper_describo_token_updater.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer0_helper_describo_token_updater.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer0_helper_describo_token_updater.labels" -}} +app.kubernetes.io/name: {{ include "layer0_helper_describo_token_updater.name" . }} +helm.sh/chart: {{ include "layer0_helper_describo_token_updater.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer0_helper_describo_token_updater.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer0_helper_describo_token_updater.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer0_helper_describo_token_updater/templates/configmap.yaml b/rds/base/charts/layer0_helper_describo_token_updater/templates/configmap.yaml new file mode 100644 index 0000000..19e35e5 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: describohelperconfig + namespace: {{ .Release.Namespace }} +data: + {{- with (mustMergeOverwrite (.Values.global | default dict) .Values.environment) }} + REDIS_HELPER_HOST: {{ .REDIS_HELPER_HOST | default "redis" | quote }} + REDIS_HELPER_PORT: {{ .REDIS_HELPER_PORT | default "6379" | quote }} + REDIS_CHANNEL: {{ .REDIS_CHANNEL | default "TokenStorage_Refresh_Token" | quote }} + {{- end }} + DESCRIBO_API_ENDPOINT: {{ .Values.environment.DESCRIBO_API_ENDPOINT | quote }} + DESCRIBO_API_SECRET: {{ .Values.global.describo.api_secret }} diff --git a/rds/base/charts/layer0_helper_describo_token_updater/templates/deployment.yaml b/rds/base/charts/layer0_helper_describo_token_updater/templates/deployment.yaml new file mode 100644 index 0000000..2e5d16e --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/templates/deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer0_helper_describo_token_updater.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer0_helper_describo_token_updater.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer0_helper_describo_token_updater.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer0_helper_describo_token_updater.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer0_helper_describo_token_updater.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: describohelperconfig + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer0_helper_describo_token_updater/values.yaml b/rds/base/charts/layer0_helper_describo_token_updater/values.yaml new file mode 100644 index 0000000..af62849 --- /dev/null +++ b/rds/base/charts/layer0_helper_describo_token_updater/values.yaml @@ -0,0 +1,44 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/port_helper_describo_token_updater + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: service + research-data-services.org/layer: layer0 + +fullnameOverride: layer0-helper-describo-token-updater + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + DESCRIBO_API_ENDPOINT: http://layer0-describo:80/api/session/application + +global: + describo: + api_secret: "" diff --git a/rds/base/charts/layer0_web/.helmignore b/rds/base/charts/layer0_web/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer0_web/.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/rds/base/charts/layer0_web/Chart.lock b/rds/base/charts/layer0_web/Chart.lock new file mode 100644 index 0000000..3e310b8 --- /dev/null +++ b/rds/base/charts/layer0_web/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:9f1061b59aef21bbca3bc05796009e900a75e94f1b37107e92b756f2c0a6e1d7 +generated: "2023-02-07T10:30:54.433292998+01:00" diff --git a/rds/base/charts/layer0_web/Chart.yaml b/rds/base/charts/layer0_web/Chart.yaml new file mode 100644 index 0000000..254ecf8 --- /dev/null +++ b/rds/base/charts/layer0_web/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer0-web +version: 0.3.3 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer0-web-common + diff --git a/rds/base/charts/layer0_web/charts/common-0.1.2.tgz b/rds/base/charts/layer0_web/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer0_web/templates/NOTES.txt b/rds/base/charts/layer0_web/templates/NOTES.txt new file mode 100644 index 0000000..afe8df8 --- /dev/null +++ b/rds/base/charts/layer0_web/templates/NOTES.txt @@ -0,0 +1,21 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "layer0_web.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "layer0_web.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "layer0_web.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "layer0_web.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:80 +{{- end }} diff --git a/rds/base/charts/layer0_web/templates/_helpers.tpl b/rds/base/charts/layer0_web/templates/_helpers.tpl new file mode 100644 index 0000000..d24cdf2 --- /dev/null +++ b/rds/base/charts/layer0_web/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer0_web.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). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "layer0_web.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer0_web.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer0_web.labels" -}} +app.kubernetes.io/name: {{ include "layer0_web.name" . }} +helm.sh/chart: {{ include "layer0_web.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer0_web.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer0_web.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer0_web/templates/configmap.yaml b/rds/base/charts/layer0_web/templates/configmap.yaml new file mode 100644 index 0000000..44a05c6 --- /dev/null +++ b/rds/base/charts/layer0_web/templates/configmap.yaml @@ -0,0 +1,49 @@ +{{- $domains := .Values.domains -}} +{{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} +{{- end -}} + +{{- if not $domains -}} + {{- if .Values.environment.ADRESS -}} + {{- $name := dict "name" (.Values.environment.ADRESS | trimPrefix "https://" | trimPrefix "http://") -}} + {{- $domains = (list (merge .Values.environment $name)) -}} + {{- else -}} + {{- $domains = list (merge .Values.environment) -}} + {{- end -}} +{{- end -}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: webconfig + namespace: {{ .Release.Namespace }} +data: + EMBED_MODE: "{{ .Values.environment.EMBED_MODE }}" + FLASK_ORIGINS: {{ (append (append .Values.environment.FLASK_ORIGINS (printf "https://%s" .Values.global.rds.domain)) (printf "http://%s" .Values.global.rds.domain)) | toJson | squote }} + SECRET_KEY: "{{ .Values.environment.SECRET_KEY }}" + DESCRIBO_API_ENDPOINT: "{{ .Values.environment.DESCRIBO_API_ENDPOINT }}" + DESCRIBO_API_SECRET: {{ .Values.global.describo.api_secret | quote }} + VUE_APP_DESCRIBO_URL: https://{{ .Values.global.describo.domain }}/application + VUE_APP_FRONTENDHOST: https://{{ .Values.global.rds.domain }} + VUE_APP_SOCKETIO_HOST: https://{{ .Values.global.rds.domain }} + SOCKETIO_HOST: https://{{ .Values.global.rds.domain }} + SOCKETIO_PATH: "{{ .Values.environment.SOCKETIO_PATH }}" + VUE_APP_BASE_URL: "{{ .Values.environment.VUE_APP_BASE_URL }}" + {{- with (mustMergeOverwrite (.Values.global | default dict) .Values.environment) }} + REDIS_HELPER_HOST: {{ .REDIS_HELPER_HOST | default "redis-helper" | quote }} + REDIS_HELPER_PORT: {{ .REDIS_HELPER_PORT | default "6379" | quote }} + REDIS_HOST: {{ .REDIS_HOST | default "redis" | quote }} + REDIS_PORT: {{ .REDIS_PORT | default "6379" | quote }} + {{- end }} + PROMETHEUS_MULTIPROC_DIR: "/tmp" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: domainsconfig + namespace: {{ .Release.Namespace }} +data: + domains.json: |- +{{- $domains | toJson | nindent 4 }} diff --git a/rds/base/charts/layer0_web/templates/deployment.yaml b/rds/base/charts/layer0_web/templates/deployment.yaml new file mode 100644 index 0000000..0ef5e2d --- /dev/null +++ b/rds/base/charts/layer0_web/templates/deployment.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer0_web.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer0_web.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer0_web.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer0_web.labels" . | indent 8 }} + spec: + volumes: + - name: domainsconfig + configMap: + name: domainsconfig + items: + - key: domains.json + path: domains.json + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer0_web.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + volumeMounts: + - name: domainsconfig + mountPath: /srv/domains.json + subPath: domains.json + readOnly: true +{{- if.Values.global.domains }} + env: + {{- range $domain := .Values.global.domains }} + {{- $name := $domain.name -}} + {{- $upper_name := regexReplaceAll "\\W+" $name "_" | upper -}} + {{- $lower_name := regexReplaceAll "\\W+" $name "-" | lower -}} + {{- $client_id := printf "%s_%s" $upper_name "OAUTH_CLIENT_ID" }} + {{- $client_secret := printf "%s_%s" $upper_name "OAUTH_CLIENT_SECRET" }} + - name: {{ $client_id }} + valueFrom: + secretKeyRef: + name: layer1-port-owncloud-{{ $lower_name }} + key: oauth-client-id + - name: {{ $client_secret }} + valueFrom: + secretKeyRef: + name: layer1-port-owncloud-{{ $lower_name }} + key: oauth-client-secret + {{- end }} +{{- end }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: webconfig + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + - name: metrics + containerPort: 9999 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: metrics + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: metrics + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer0_web/templates/ingress.yaml b/rds/base/charts/layer0_web/templates/ingress.yaml new file mode 100644 index 0000000..913be76 --- /dev/null +++ b/rds/base/charts/layer0_web/templates/ingress.yaml @@ -0,0 +1,28 @@ +{{- $fullName := include "layer0_web.fullname" . -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: +{{ include "layer0_web.labels" . | indent 4 }} + annotations: +{{- include "common.ingressAnnotations" . | nindent 4 }} +spec: + {{- if (include "common.tlsSecretName" .) }} + tls: + - hosts: + - {{ .Values.global.rds.domain }} + secretName: {{ include "common.tlsSecretName" . }} + {{- end }} + rules: + - host: {{ .Values.global.rds.domain }} + http: + paths: + - path: {{ .Values.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + # number: 80 + name: http \ No newline at end of file diff --git a/rds/base/charts/layer0_web/templates/service.yaml b/rds/base/charts/layer0_web/templates/service.yaml new file mode 100644 index 0000000..eea2890 --- /dev/null +++ b/rds/base/charts/layer0_web/templates/service.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: +{{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer0_web.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer0_web.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + - port: 9999 + targetPort: metrics + protocol: TCP + name: metrics + selector: + app.kubernetes.io/name: {{ include "layer0_web.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer0_web/templates/tests/test-connection.yaml b/rds/base/charts/layer0_web/templates/tests/test-connection.yaml new file mode 100644 index 0000000..cc5462a --- /dev/null +++ b/rds/base/charts/layer0_web/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer0_web.fullname" . }}-test-research" + labels: +{{ include "layer0_web.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer0_web.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer0_web/values.yaml b/rds/base/charts/layer0_web/values.yaml new file mode 100644 index 0000000..226979a --- /dev/null +++ b/rds/base/charts/layer0_web/values.yaml @@ -0,0 +1,92 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/rds_web + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: rds-ingress + research-data-services.org/layer: layer0 + +fullnameOverride: layer0-web + +service: + type: ClusterIP + port: 80 + targetPort: 80 + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9999" + +ingress: + path: / + annotations: + nginx.org/server-snippets: | + location /socket.io/ { + proxy_http_version 1.1; + proxy_redirect off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://layer0-web/socket.io/; + } +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + EMBED_MODE: true + FLASK_ORIGINS: + - "http://localhost:8080" + - "http://localhost:8085" + - "http://localhost:8000" + - "http://localhost:9100" + SECRET_KEY: 1234 + DESCRIBO_API_ENDPOINT: http://layer0-describo/api/session/application + #SOCKETIO_HOST: https:// + SOCKETIO_PATH: /socket.io/ + VUE_APP_BASE_URL: / + +global: + rds: + domain: hey + describo: + domain: hej + api_secret: asd + +# domains: +# - name: owncloud.local # have to be equal to the second part of cloudID in owncloud +# ADDRESS: https://owncloud.local/owncloud +# OAUTH_CLIENT_ID: ABC +# OAUTH_CLIENT_SECRET: XYZ +# # filter settings for services for this domain. This is very usable, if you want to connect a single RDS instance to multiple installations. +# # So you can show some specific services only for some ownclouds and show other services to others. Domainname in only and except have to be the same as in domains +# filters: +# # example! +# only: # only this services will be shown to users of this domain +# - "layer1-port-zenodo" +# except: # all other services will be shown to users of this domain +# - "layer1-port-openscienceframework" +# # if only and except are used at the same time, the system will filter for only first and then for except. So except should be a subset of only, otherwise it is doing nothing. diff --git a/rds/base/charts/layer1_port_openscienceframework/.helmignore b/rds/base/charts/layer1_port_openscienceframework/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/.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/rds/base/charts/layer1_port_openscienceframework/Chart.lock b/rds/base/charts/layer1_port_openscienceframework/Chart.lock new file mode 100644 index 0000000..9fd77bc --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:7aaa9dc5d2b77fe20b6c86434b0c1b5ec1755a923fca94ab95b35f07eec006e8 +generated: "2023-02-07T10:30:54.931573465+01:00" diff --git a/rds/base/charts/layer1_port_openscienceframework/Chart.yaml b/rds/base/charts/layer1_port_openscienceframework/Chart.yaml new file mode 100644 index 0000000..2df521a --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer1-port-openscienceframework +version: 0.2.3 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - openscienceframework +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer1-port-openscienceframewor-common + + diff --git a/rds/base/charts/layer1_port_openscienceframework/charts/common-0.1.2.tgz b/rds/base/charts/layer1_port_openscienceframework/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer1_port_openscienceframework/templates/_helpers.tpl b/rds/base/charts/layer1_port_openscienceframework/templates/_helpers.tpl new file mode 100644 index 0000000..9ba1fb6 --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer1_port_openscienceframework.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "layer1_port_openscienceframework.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer1_port_openscienceframework.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer1_port_openscienceframework.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer1_port_openscienceframework.labels" -}} +app.kubernetes.io/name: {{ include "layer1_port_openscienceframework.name" . }} +helm.sh/chart: {{ include "layer1_port_openscienceframework.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer1_port_openscienceframework.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer1_port_openscienceframework.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer1_port_openscienceframework/templates/configmap.yaml b/rds/base/charts/layer1_port_openscienceframework/templates/configmap.yaml new file mode 100644 index 0000000..550a8c5 --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/templates/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: portosfconfig + namespace: {{ .Release.Namespace }} +data: + OPENSCIENCEFRAMEWORK_ADDRESS: {{ .Values.environment.ADDRESS | quote }} + OPENSCIENCEFRAMEWORK_API_ADDRESS: {{ .Values.environment.API_ADDRESS | quote }} + OPENSCIENCEFRAMEWORK_DISPLAYNAME: {{ .Values.environment.DISPLAYNAME | quote }} + OPENSCIENCEFRAMEWORK_INFO_URL: {{ .Values.environment.INFO_URL | quote }} + OPENSCIENCEFRAMEWORK_HELP_URL: {{ .Values.environment.HELP_URL | quote }} + OPENSCIENCEFRAMEWORK_ICON: {{ .Values.environment.ICON | quote }} + OPENSCIENCEFRAMEWORK_METADATA_PROFILE: {{ .Values.environment.METADATA_PROFILE | quote }} + OPENSCIENCEFRAMEWORK_PROJECT_LINK_TEMPLATE: {{ .Values.environment.PROJECT_LINK_TEMPLATE | quote }} + diff --git a/rds/base/charts/layer1_port_openscienceframework/templates/deployment.yaml b/rds/base/charts/layer1_port_openscienceframework/templates/deployment.yaml new file mode 100644 index 0000000..1f37869 --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/templates/deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer1_port_openscienceframework.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_openscienceframework.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer1_port_openscienceframework.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer1_port_openscienceframework.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer1_port_openscienceframework.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: OPENSCIENCEFRAMEWORK_OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: osf-client + key: osf-client-id + - name: OPENSCIENCEFRAMEWORK_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: osf-client + key: osf-client-secret + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: portosfconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer1_port_openscienceframework/templates/service.yaml b/rds/base/charts/layer1_port_openscienceframework/templates/service.yaml new file mode 100644 index 0000000..61b3ffc --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer1_port_openscienceframework.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_openscienceframework.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer1_port_openscienceframework.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer1_port_openscienceframework/templates/tests/test-connection.yaml b/rds/base/charts/layer1_port_openscienceframework/templates/tests/test-connection.yaml new file mode 100644 index 0000000..f82564f --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer1_port_openscienceframework.fullname" . }}-test-research" + labels: +{{ include "layer1_port_openscienceframework.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer1_port_openscienceframework.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer1_port_openscienceframework/values.yaml b/rds/base/charts/layer1_port_openscienceframework/values.yaml new file mode 100644 index 0000000..93427b3 --- /dev/null +++ b/rds/base/charts/layer1_port_openscienceframework/values.yaml @@ -0,0 +1,60 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/port_openscienceframework + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: connector + research-data-services.org/layer: layer1 + +fullnameOverride: layer1-port-openscienceframework + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +domain: localhost +ingress: + tls: + secretName: sciebords-tls-public + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + ADDRESS: https://accounts.test.osf.io + API_ADDRESS: https://api.test.osf.io/v2 + OAUTH_CLIENT_ID: "" + OAUTH_CLIENT_SECRET: "" + DISPLAYNAME: "" + INFO_URL: "" + HELP_URL: "" + ICON: "" + METADATA_PROFILE: "" + PROJECT_LINK_TEMPLATE: "" diff --git a/rds/base/charts/layer1_port_owncloud/.helmignore b/rds/base/charts/layer1_port_owncloud/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/.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/rds/base/charts/layer1_port_owncloud/Chart.lock b/rds/base/charts/layer1_port_owncloud/Chart.lock new file mode 100644 index 0000000..dd11068 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:f9388dc66957a72d1d42857c8c87b9e4c2c81fb5fcdc5829b366219fa64c77bd +generated: "2023-02-07T10:30:55.413462484+01:00" diff --git a/rds/base/charts/layer1_port_owncloud/Chart.yaml b/rds/base/charts/layer1_port_owncloud/Chart.yaml new file mode 100644 index 0000000..64c53b3 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer1-port-owncloud +version: 0.3.3 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer1-port-owncloud-common + diff --git a/rds/base/charts/layer1_port_owncloud/charts/common-0.1.2.tgz b/rds/base/charts/layer1_port_owncloud/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer1_port_owncloud/templates/_helpers.tpl b/rds/base/charts/layer1_port_owncloud/templates/_helpers.tpl new file mode 100644 index 0000000..7febb7e --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/templates/_helpers.tpl @@ -0,0 +1,71 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer1_port_owncloud.name" -}} +{{- printf "%s-%s" (default .Chart.Name .Values.nameOverride) (.name | replace "." "-" | replace ":" "-") -}} +{{- end -}} + +{{/* +Format the name of the image as a dictionary. +*/}} +{{- define "layer1_port_owncloud.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer1_port_owncloud.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- printf "%s-%s" (.Values.fullnameOverride | trunc 63 | trimSuffix "-") (.name | replace "." "-" | replace ":" "-") -}} +{{- 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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer1_port_owncloud.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer1_port_owncloud.labels" -}} +app.kubernetes.io/name: {{ include "layer1_port_owncloud.name" . }} +helm.sh/chart: {{ include "layer1_port_owncloud.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + +{{- define "layer1_port_owncloud.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer1_port_owncloud.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/layer1_port_owncloud/templates/configmap.yaml b/rds/base/charts/layer1_port_owncloud/templates/configmap.yaml new file mode 100644 index 0000000..3d4b717 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/templates/configmap.yaml @@ -0,0 +1,35 @@ +{{- $domains := .Values.domains -}} +{{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} +{{- end -}} + +{{- if not $domains -}} +{{- $name := (dict "name" (.Values.environment.ADDRESS | replace "https://" "" | replace "http://" "")) -}} +{{- $domains = (list (merge .Values.environment $name)) -}} +{{- end -}} + +{{- range $domains }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: portowncloudconfig-{{ .name | replace "." "-" | replace ":" "-" }} + namespace: {{ $.Values.global.namespace.name | default $.Release.Namespace }} +data: + OWNCLOUD_OAUTH_CLIENT_ID: {{ .OAUTH_CLIENT_ID | quote }} + OWNCLOUD_INSTALLATION_URL: {{ .ADDRESS | quote }} + OWNCLOUD_OAUTH_CLIENT_SECRET: {{ .OAUTH_CLIENT_SECRET | quote }} + OWNCLOUD_DISPLAYNAME: {{ .DISPLAYNAME | quote }} + OWNCLOUD_INFO_URL: {{ .INFO_URL | quote }} + OWNCLOUD_HELP_URL: {{ .HELP_URL | quote }} + OWNCLOUD_ICON: {{ .ICON | quote }} + SERVICENAME: {{ .name | replace "." "-" | replace ":" "-" }} + {{ if not .INTERNAL_ADDRESS }} + OWNCLOUD_INTERNAL_INSTALLATION_URL: {{ .ADDRESS | quote }} + {{ end }} + {{ if .INTERNAL_ADDRESS }} + OWNCLOUD_INTERNAL_INSTALLATION_URL: {{ .INTERNAL_ADDRESS | quote }} + {{ end }} +{{- end }} diff --git a/rds/base/charts/layer1_port_owncloud/templates/deployment.yaml b/rds/base/charts/layer1_port_owncloud/templates/deployment.yaml new file mode 100644 index 0000000..bcaa8af --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/templates/deployment.yaml @@ -0,0 +1,88 @@ +{{- $domains := .Values.domains -}} +{{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} +{{- end -}} + +{{- if not $domains -}} + {{- $name := (dict "name" (.Values.environment.ADDRESS | trimPrefix "https://" | trimPrefix "http://")) -}} + {{- $domains = (list (merge .Values.environment $name)) -}} +{{- end -}} + +{{- range $domains }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer1_port_owncloud.fullname" (mergeOverwrite $ .) }} + namespace: {{ $.Values.global.namespace.name | default $.Release.Namespace }} + labels: +{{ include "layer1_port_owncloud.labels" (mergeOverwrite $ .) | indent 4 }} +spec: + replicas: {{ $.Values.replicaCount }} + selector: + matchLabels: +{{ include "layer1_port_owncloud.labels" (mergeOverwrite $ .) | indent 6 }} + template: + metadata: + labels: +{{ include "layer1_port_owncloud.labels" (mergeOverwrite $ .) | indent 8 }} + spec: + {{- with $.Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ $.Chart.Name }} + image: {{ template "layer1_port_owncloud.image" $ }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + env: + - name: OWNCLOUD_OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: layer1-port-owncloud-{{ .name | replace "." "-" | replace ":" "-" }} + key: "oauth-client-id" + - name: OWNCLOUD_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: layer1-port-owncloud-{{ .name | replace "." "-" | replace ":" "-" }} + key: "oauth-client-secret" + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: portowncloudconfig-{{ .name | replace "." "-" | replace ":" "-" }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml $.Values.resources | nindent 12 }} + {{- with $.Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $.Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $.Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/layer1_port_owncloud/templates/service.yaml b/rds/base/charts/layer1_port_owncloud/templates/service.yaml new file mode 100644 index 0000000..2429e74 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/templates/service.yaml @@ -0,0 +1,36 @@ +{{- $domains := .Values.domains -}} +{{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} +{{- end -}} + +{{- if not $domains -}} +{{- $name := (dict "name" (.Values.environment.ADDRESS | replace "https://" "" | replace "http://" "")) -}} +{{- $domains = (list (merge .Values.environment $name)) -}} +{{- end -}} + +{{- range $domains }} +--- +apiVersion: v1 +kind: Service +metadata: + {{- with $.Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer1_port_owncloud.fullname" (mergeOverwrite $ .) }} + namespace: {{ $.Values.global.namespace.name | default $.Release.Namespace }} + labels: +{{ include "layer1_port_owncloud.labels" (mergeOverwrite $ .) | indent 4 }} +spec: + type: {{ $.Values.service.type }} + ports: + - port: {{ $.Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer1_port_owncloud.name" (mergeOverwrite $ .) }} + app.kubernetes.io/instance: {{ $.Release.Name }} +{{- end }} diff --git a/rds/base/charts/layer1_port_owncloud/templates/tests/test-connection.yaml b/rds/base/charts/layer1_port_owncloud/templates/tests/test-connection.yaml new file mode 100644 index 0000000..be51921 --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/templates/tests/test-connection.yaml @@ -0,0 +1,30 @@ +{{- $domains := .Values.domains -}} +{{- if .Values.global }} + {{- if .Values.global.domains }} + {{- $domains = .Values.global.domains -}} + {{- end -}} +{{- end -}} + +{{- if not $domains -}} + {{- $name := (dict "name" (.Values.environment.ADDRESS | trimPrefix "https://" | trimPrefix "http://")) -}} + {{- $domains = (list (merge .Values.environment $name)) -}} +{{- end -}} + +{{- range $domains }} +--- +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer1_port_owncloud.fullname" (mergeOverwrite $ .) }}-test-research" + labels: +{{ include "layer1_port_owncloud.labels" (mergeOverwrite $ .) | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer1_port_owncloud.fullname" (mergeOverwrite $ .) }}:{{ $.Values.service.port }}'] + restartPolicy: Never +{{- end -}} diff --git a/rds/base/charts/layer1_port_owncloud/values.yaml b/rds/base/charts/layer1_port_owncloud/values.yaml new file mode 100644 index 0000000..eecd97f --- /dev/null +++ b/rds/base/charts/layer1_port_owncloud/values.yaml @@ -0,0 +1,60 @@ +# Default values for layer1_port_owncloud. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/port_owncloud + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: connector + research-data-services.org/layer: layer1 + +nameOverride: "" +fullnameOverride: "layer1-port-owncloud" + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +domain: localhost +ingress: + tls: + secretName: sciebords-tls-public + +environment: + ADDRESS: "https://test-adress.de" + DISPLAYNAME: "" + INFO_URL: "" + HELP_URL: "" + ICON: "" + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +global: + namespace: + name: "test-namespace" diff --git a/rds/base/charts/layer1_port_reva/.helmignore b/rds/base/charts/layer1_port_reva/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/.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/rds/base/charts/layer1_port_reva/Chart.lock b/rds/base/charts/layer1_port_reva/Chart.lock new file mode 100644 index 0000000..c8bb132 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:c45fe9bebe36f65d76dc5793030e0ad762dc46d290d98837e2a85cc6e6329914 +generated: "2023-02-07T10:30:55.892945969+01:00" diff --git a/rds/base/charts/layer1_port_reva/Chart.yaml b/rds/base/charts/layer1_port_reva/Chart.yaml new file mode 100644 index 0000000..8059850 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer1-port-reva +version: 0.2.0 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - reva +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer1-port-reva-common + diff --git a/rds/base/charts/layer1_port_reva/charts/common-0.1.2.tgz b/rds/base/charts/layer1_port_reva/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer1_port_reva/templates/_helpers.tpl b/rds/base/charts/layer1_port_reva/templates/_helpers.tpl new file mode 100644 index 0000000..8893977 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer1_port_reva.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer1_port_reva.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer1_port_reva.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer1_port_reva.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer1_port_reva.labels" -}} +app.kubernetes.io/name: {{ include "layer1_port_reva.name" . }} +helm.sh/chart: {{ include "layer1_port_reva.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer1_port_reva.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer1_port_reva.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer1_port_reva/templates/configmap.yaml b/rds/base/charts/layer1_port_reva/templates/configmap.yaml new file mode 100644 index 0000000..a7fd8c3 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/templates/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: portrevaconfig + namespace: {{ .Release.Namespace }} +data: + RDS_REVA_HOST: {{ .Values.environment.RDS_REVA_HOST | quote }} + RDS_REVA_USER: {{ .Values.environment.RDS_REVA_USER | quote }} + RDS_REVA_PASSWORD: {{ .Values.environment.RDS_REVA_PASSWORD | quote }} \ No newline at end of file diff --git a/rds/base/charts/layer1_port_reva/templates/deployment.yaml b/rds/base/charts/layer1_port_reva/templates/deployment.yaml new file mode 100644 index 0000000..083cc9b --- /dev/null +++ b/rds/base/charts/layer1_port_reva/templates/deployment.yaml @@ -0,0 +1,66 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer1_port_reva.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_reva.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer1_port_reva.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer1_port_reva.labels" . | indent 8 }} + spec: + initContainers: + - name: init-tokenstorage + image: busybox:1.28 + command: ['sh', '-c', "until nslookup layer3-token-storage.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for layer3-token-storage; sleep 2; done"] + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer1_port_reva.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: portrevaconfig + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer1_port_reva/templates/service.yaml b/rds/base/charts/layer1_port_reva/templates/service.yaml new file mode 100644 index 0000000..d18e43a --- /dev/null +++ b/rds/base/charts/layer1_port_reva/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer1_port_reva.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_reva.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer1_port_reva.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer1_port_reva/templates/tests/test-connection.yaml b/rds/base/charts/layer1_port_reva/templates/tests/test-connection.yaml new file mode 100644 index 0000000..7666e8b --- /dev/null +++ b/rds/base/charts/layer1_port_reva/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer1_port_reva.fullname" . }}-test-research" + labels: +{{ include "layer1_port_reva.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer1_port_reva.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer1_port_reva/values.yaml b/rds/base/charts/layer1_port_reva/values.yaml new file mode 100644 index 0000000..8723c62 --- /dev/null +++ b/rds/base/charts/layer1_port_reva/values.yaml @@ -0,0 +1,52 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: omnivox/port-reva + tag: latest + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: connector + research-data-services.org/layer: layer1 + +fullnameOverride: layer1-port-reva + +service: + type: ClusterIP + port: 80 + targetPort: 80 + annotations: + prometheus.io/scrape: "true" + +domain: localhost +ingress: + tls: + secretName: sciebords-tls-public + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + RDS_REVA_HOST: sciencemesh-test.uni-muenster.de:9600 + RDS_REVA_USER: "" + RDS_REVA_PASSWORD: "" diff --git a/rds/base/charts/layer1_port_zenodo/.helmignore b/rds/base/charts/layer1_port_zenodo/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/.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/rds/base/charts/layer1_port_zenodo/Chart.lock b/rds/base/charts/layer1_port_zenodo/Chart.lock new file mode 100644 index 0000000..b3a17db --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:d1d50002fef4797c2a48d9d32c63033f562681492a57c98311330067b5359984 +generated: "2023-02-07T10:30:56.387781762+01:00" diff --git a/rds/base/charts/layer1_port_zenodo/Chart.yaml b/rds/base/charts/layer1_port_zenodo/Chart.yaml new file mode 100644 index 0000000..e04b01f --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer1-port-zenodo +version: 0.2.2 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer1-port-zenodo-common + diff --git a/rds/base/charts/layer1_port_zenodo/charts/common-0.1.2.tgz b/rds/base/charts/layer1_port_zenodo/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer1_port_zenodo/templates/_helpers.tpl b/rds/base/charts/layer1_port_zenodo/templates/_helpers.tpl new file mode 100644 index 0000000..0e5fdbc --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer1_port_zenodo.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer1_port_zenodo.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer1_port_zenodo.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer1_port_zenodo.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer1_port_zenodo.labels" -}} +app.kubernetes.io/name: {{ include "layer1_port_zenodo.name" . }} +helm.sh/chart: {{ include "layer1_port_zenodo.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer1_port_zenodo.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer1_port_zenodo.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer1_port_zenodo/templates/configmap.yaml b/rds/base/charts/layer1_port_zenodo/templates/configmap.yaml new file mode 100644 index 0000000..abc5518 --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: portzenodoconfig + namespace: {{ .Release.Namespace }} +data: + ZENODO_ADDRESS: {{ .Values.environment.ADDRESS | quote }} + ZENODO_DISPLAYNAME: {{ .Values.environment.DISPLAYNAME | quote }} + ZENODO_INFO_URL: {{ .Values.environment.INFO_URL | quote }} + ZENODO_HELP_URL: {{ .Values.environment.HELP_URL | quote }} + ZENODO_ICON: {{ .Values.environment.ICON | quote }} + ZENODO_METADATA_PROFILE: {{ .Values.environment.METADATA_PROFILE | quote }} + ZENODO_PROJECT_LINK_TEMPLATE: {{ .Values.environment.PROJECT_LINK_TEMPLATE | quote }} diff --git a/rds/base/charts/layer1_port_zenodo/templates/deployment.yaml b/rds/base/charts/layer1_port_zenodo/templates/deployment.yaml new file mode 100644 index 0000000..4462eac --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/templates/deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer1_port_zenodo.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_zenodo.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer1_port_zenodo.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer1_port_zenodo.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer1_port_zenodo.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: ZENODO_OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: zenodo-client + key: zenodo-client-id + - name: ZENODO_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: zenodo-client + key: zenodo-client-secret + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: portzenodoconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer1_port_zenodo/templates/service.yaml b/rds/base/charts/layer1_port_zenodo/templates/service.yaml new file mode 100644 index 0000000..fa50a8d --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer1_port_zenodo.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer1_port_zenodo.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer1_port_zenodo.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer1_port_zenodo/templates/tests/test-connection.yaml b/rds/base/charts/layer1_port_zenodo/templates/tests/test-connection.yaml new file mode 100644 index 0000000..88e42e0 --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer1_port_zenodo.fullname" . }}-test-research" + labels: +{{ include "layer1_port_zenodo.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer1_port_zenodo.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer1_port_zenodo/values.yaml b/rds/base/charts/layer1_port_zenodo/values.yaml new file mode 100644 index 0000000..fc01509 --- /dev/null +++ b/rds/base/charts/layer1_port_zenodo/values.yaml @@ -0,0 +1,60 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/port_zenodo + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: connector + research-data-services.org/layer: layer1 + +fullnameOverride: layer1-port-zenodo + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +domain: localhost +ingress: + tls: + secretName: sciebords-tls-public + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + ADDRESS: https://sandbox.zenodo.org + OAUTH_CLIENT_ID: "" + OAUTH_CLIENT_SECRET: "" + DISPLAYNAME: "" + INFO_URL: "" + HELP_URL: "" + ICON: "" + METADATA_PROFILE: "" + PROJECT_LINK_TEMPLATE: "" diff --git a/rds/base/charts/layer2_exporter_service/.helmignore b/rds/base/charts/layer2_exporter_service/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/.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/rds/base/charts/layer2_exporter_service/Chart.lock b/rds/base/charts/layer2_exporter_service/Chart.lock new file mode 100644 index 0000000..3b6a514 --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:a6c0de64f7566cd8e27dbc5e3fcf34a3df54d6333bff2e6cb61b8c172bddc240 +generated: "2023-02-07T10:30:56.892693816+01:00" diff --git a/rds/base/charts/layer2_exporter_service/Chart.yaml b/rds/base/charts/layer2_exporter_service/Chart.yaml new file mode 100644 index 0000000..0ea4bcc --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer2-exporter-service +version: 0.2.3 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer2-exporter-service-common + diff --git a/rds/base/charts/layer2_exporter_service/charts/common-0.1.2.tgz b/rds/base/charts/layer2_exporter_service/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer2_exporter_service/templates/_helpers.tpl b/rds/base/charts/layer2_exporter_service/templates/_helpers.tpl new file mode 100644 index 0000000..c4e308f --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer2_exporter_service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer2_exporter_service.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer2_exporter_service.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer2_exporter_service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer2_exporter_service.labels" -}} +app.kubernetes.io/name: {{ include "layer2_exporter_service.name" . }} +helm.sh/chart: {{ include "layer2_exporter_service.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + +{{- define "layer2_exporter_service.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain }} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain }} +{{- else }}localhost{{- end -}} +{{- end -}} + +{{- define "layer2_exporter_service.secretName" -}} +{{- if .Values.global }} +{{- .Values.global.ingress.tls.secretName }} +{{- else }} +{{- .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer2_exporter_service/templates/configmap.yaml b/rds/base/charts/layer2_exporter_service/templates/configmap.yaml new file mode 100644 index 0000000..afdec7a --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: serviceexporterconfig + namespace: {{ .Release.Namespace }} +data: + \ No newline at end of file diff --git a/rds/base/charts/layer2_exporter_service/templates/deployment.yaml b/rds/base/charts/layer2_exporter_service/templates/deployment.yaml new file mode 100644 index 0000000..9c20f82 --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/templates/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer2_exporter_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_exporter_service.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer2_exporter_service.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer2_exporter_service.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer2_exporter_service.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: serviceexporterconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer2_exporter_service/templates/service.yaml b/rds/base/charts/layer2_exporter_service/templates/service.yaml new file mode 100644 index 0000000..086f645 --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer2_exporter_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_exporter_service.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer2_exporter_service.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer2_exporter_service/templates/tests/test-connection.yaml b/rds/base/charts/layer2_exporter_service/templates/tests/test-connection.yaml new file mode 100644 index 0000000..23a384f --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer2_exporter_service.fullname" . }}-test-research" + labels: +{{ include "layer2_exporter_service.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer2_exporter_service.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer2_exporter_service/values.yaml b/rds/base/charts/layer2_exporter_service/values.yaml new file mode 100644 index 0000000..529a0d1 --- /dev/null +++ b/rds/base/charts/layer2_exporter_service/values.yaml @@ -0,0 +1,43 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/use_case_exporter + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: service + research-data-services.org/layer: layer2 + +fullnameOverride: layer2-exporter-service + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/rds/base/charts/layer2_metadata_service/Chart.lock b/rds/base/charts/layer2_metadata_service/Chart.lock new file mode 100644 index 0000000..ddcb7d3 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:318af01b93c9de85b7a5c9ebd15321e6eea000ce4d817337cfb8b083a1f6e92e +generated: "2023-02-07T10:30:57.413843137+01:00" diff --git a/rds/base/charts/layer2_metadata_service/Chart.yaml b/rds/base/charts/layer2_metadata_service/Chart.yaml new file mode 100644 index 0000000..c8a61b3 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer2-metadata-service +version: 0.2.3 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer2-metadata-service-common + diff --git a/rds/base/charts/layer2_metadata_service/charts/common-0.1.2.tgz b/rds/base/charts/layer2_metadata_service/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer2_metadata_service/templates/_helpers.tpl b/rds/base/charts/layer2_metadata_service/templates/_helpers.tpl new file mode 100644 index 0000000..6c0d03f --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer2_metadata_service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer2_metadata_service.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer2_metadata_service.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer2_metadata_service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer2_metadata_service.labels" -}} +app.kubernetes.io/name: {{ include "layer2_metadata_service.name" . }} +helm.sh/chart: {{ include "layer2_metadata_service.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + +{{- define "layer2_metadata_service.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer2_metadata_service.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer2_metadata_service/templates/configmap.yaml b/rds/base/charts/layer2_metadata_service/templates/configmap.yaml new file mode 100644 index 0000000..43cce4b --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/templates/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: servicemetadataconfig + namespace: {{ .Release.Namespace }} +data: {} \ No newline at end of file diff --git a/rds/base/charts/layer2_metadata_service/templates/deployment.yaml b/rds/base/charts/layer2_metadata_service/templates/deployment.yaml new file mode 100644 index 0000000..7ce1e79 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/templates/deployment.yaml @@ -0,0 +1,62 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer2_metadata_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_metadata_service.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer2_metadata_service.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer2_metadata_service.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer2_metadata_service.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: servicemetadataconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer2_metadata_service/templates/service.yaml b/rds/base/charts/layer2_metadata_service/templates/service.yaml new file mode 100644 index 0000000..d6d37c1 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer2_metadata_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_metadata_service.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer2_metadata_service.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer2_metadata_service/templates/tests/test-connection.yaml b/rds/base/charts/layer2_metadata_service/templates/tests/test-connection.yaml new file mode 100644 index 0000000..479f2e2 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer2_metadata_service.fullname" . }}-test-research" + labels: +{{ include "layer2_metadata_service.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer2_metadata_service.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer2_metadata_service/values.yaml b/rds/base/charts/layer2_metadata_service/values.yaml new file mode 100644 index 0000000..928e4f5 --- /dev/null +++ b/rds/base/charts/layer2_metadata_service/values.yaml @@ -0,0 +1,43 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/use_case_metadata + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: service + research-data-services.org/layer: layer2 + +fullnameOverride: layer2-metadata-service + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/rds/base/charts/layer2_port_service/.helmignore b/rds/base/charts/layer2_port_service/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer2_port_service/.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/rds/base/charts/layer2_port_service/Chart.lock b/rds/base/charts/layer2_port_service/Chart.lock new file mode 100644 index 0000000..1f085fb --- /dev/null +++ b/rds/base/charts/layer2_port_service/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:dca3d3ef6fede10aab2defdabecdff2205a5bd66c7b36d1441f98635d4b84e37 +generated: "2023-02-07T10:30:57.939050904+01:00" diff --git a/rds/base/charts/layer2_port_service/Chart.yaml b/rds/base/charts/layer2_port_service/Chart.yaml new file mode 100644 index 0000000..4a2139d --- /dev/null +++ b/rds/base/charts/layer2_port_service/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: layer2-port-service +version: 0.2.5 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer2-port-service-common + diff --git a/rds/base/charts/layer2_port_service/charts/common-0.1.2.tgz b/rds/base/charts/layer2_port_service/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer2_port_service/templates/_helpers.tpl b/rds/base/charts/layer2_port_service/templates/_helpers.tpl new file mode 100644 index 0000000..64f2ee6 --- /dev/null +++ b/rds/base/charts/layer2_port_service/templates/_helpers.tpl @@ -0,0 +1,71 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer2_port_service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer2_port_service.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer2_port_service.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer2_port_service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer2_port_service.labels" -}} +app.kubernetes.io/name: {{ include "layer2_port_service.name" . }} +helm.sh/chart: {{ include "layer2_port_service.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + + + +{{- define "layer2_port_service.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer2_port_service.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer2_port_service/templates/configmap.yaml b/rds/base/charts/layer2_port_service/templates/configmap.yaml new file mode 100644 index 0000000..9261ff7 --- /dev/null +++ b/rds/base/charts/layer2_port_service/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: serviceportconfig + namespace: {{ .Release.Namespace }} +data: + IGNORE_PROJECTS: {{ .Values.environment.IGNORE_PROJECTS | quote }} \ No newline at end of file diff --git a/rds/base/charts/layer2_port_service/templates/deployment.yaml b/rds/base/charts/layer2_port_service/templates/deployment.yaml new file mode 100644 index 0000000..5220825 --- /dev/null +++ b/rds/base/charts/layer2_port_service/templates/deployment.yaml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer2_port_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_port_service.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer2_port_service.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer2_port_service.labels" . | indent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer2_port_service.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: mservice + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: serviceportconfig + {{- if .Values.environment.TOKENSERVICE_STATE_SECRET }} + env: + - name: TOKENSERVICE_STATE_SECRET + value: {{ .Values.environment.TOKENSERVICE_STATE_SECRET }} + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer2_port_service/templates/service.yaml b/rds/base/charts/layer2_port_service/templates/service.yaml new file mode 100644 index 0000000..b2d67a6 --- /dev/null +++ b/rds/base/charts/layer2_port_service/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer2_port_service.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer2_port_service.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer2_port_service.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer2_port_service/templates/tests/test-connection.yaml b/rds/base/charts/layer2_port_service/templates/tests/test-connection.yaml new file mode 100644 index 0000000..7c12901 --- /dev/null +++ b/rds/base/charts/layer2_port_service/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer2_port_service.fullname" . }}-test-research" + labels: +{{ include "layer2_port_service.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer2_port_service.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer2_port_service/values.yaml b/rds/base/charts/layer2_port_service/values.yaml new file mode 100644 index 0000000..b61b2a5 --- /dev/null +++ b/rds/base/charts/layer2_port_service/values.yaml @@ -0,0 +1,47 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/use_case_port_service + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: service + research-data-services.org/layer: layer2 + +fullnameOverride: layer2-port-service + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +environment: + IGNORE_PROJECTS: "True" + TOKENSERVICE_STATE_SECRET: "" + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/rds/base/charts/layer3_research_manager/Chart.lock b/rds/base/charts/layer3_research_manager/Chart.lock new file mode 100644 index 0000000..00336e4 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:96a625dec9b5cc24195264a79968fd43a4a7199bfd0b4e22c994e9e48736a6c2 +generated: "2023-02-07T10:30:58.462751504+01:00" diff --git a/rds/base/charts/layer3_research_manager/Chart.yaml b/rds/base/charts/layer3_research_manager/Chart.yaml new file mode 100644 index 0000000..76d0512 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: The project manager to manage projects within RDS +name: layer3-research-manager +version: 0.3.4 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer3-research-manager-common + diff --git a/rds/base/charts/layer3_research_manager/charts/common-0.1.2.tgz b/rds/base/charts/layer3_research_manager/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer3_research_manager/templates/_helpers.tpl b/rds/base/charts/layer3_research_manager/templates/_helpers.tpl new file mode 100644 index 0000000..92f9220 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer3_research_manager.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer3_research_manager.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer3_research_manager.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer3_research_manager.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer3_research_manager.labels" -}} +app.kubernetes.io/name: {{ include "layer3_research_manager.name" . }} +helm.sh/chart: {{ include "layer3_research_manager.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + +{{- define "layer3_research_manager.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer3_research_manager.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer3_research_manager/templates/configmap.yaml b/rds/base/charts/layer3_research_manager/templates/configmap.yaml new file mode 100644 index 0000000..b2d4cd2 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/templates/configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: layer3researchconfig + namespace: {{ .Release.Namespace }} +data: + REDIS_HOST: {{ .Values.global.REDIS_HOST | quote }} + REDIS_PORT: {{ .Values.global.REDIS_PORT | quote }} \ No newline at end of file diff --git a/rds/base/charts/layer3_research_manager/templates/deployment.yaml b/rds/base/charts/layer3_research_manager/templates/deployment.yaml new file mode 100644 index 0000000..dbf1a54 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/templates/deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer3_research_manager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer3_research_manager.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer3_research_manager.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer3_research_manager.labels" . | indent 8 }} + spec: + initContainers: + - name: deploy + image: redis + command: [ "sh" ] + args: [ "-c", 'while [ "$(redis-cli -h $REDIS_PORT_6379_TCP_ADDR ping)" != "PONG" ]; do sleep 2; done' ] + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer3_research_manager.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: layer3researchconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer3_research_manager/templates/service.yaml b/rds/base/charts/layer3_research_manager/templates/service.yaml new file mode 100644 index 0000000..5f979f8 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer3_research_manager.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer3_research_manager.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer3_research_manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer3_research_manager/templates/tests/test-connection.yaml b/rds/base/charts/layer3_research_manager/templates/tests/test-connection.yaml new file mode 100644 index 0000000..fbd51ee --- /dev/null +++ b/rds/base/charts/layer3_research_manager/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer3_research_manager.fullname" . }}-test-research" + labels: +{{ include "layer3_research_manager.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer3_research_manager.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer3_research_manager/values.yaml b/rds/base/charts/layer3_research_manager/values.yaml new file mode 100644 index 0000000..feef760 --- /dev/null +++ b/rds/base/charts/layer3_research_manager/values.yaml @@ -0,0 +1,52 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/central_service_research_manager + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: core + research-data-services.org/layer: layer3 + +fullnameOverride: layer3-research-manager + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + REDIS_HOST: redis + REDIS_PORT: 6379 + IN_MEMORY_AS_FAILOVER: "False" + +global: + REDIS_HOST: + REDIS_PORT: diff --git a/rds/base/charts/layer3_token_storage/.helmignore b/rds/base/charts/layer3_token_storage/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/.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/rds/base/charts/layer3_token_storage/Chart.lock b/rds/base/charts/layer3_token_storage/Chart.lock new file mode 100644 index 0000000..70e2be6 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../common + version: 0.1.2 +digest: sha256:d1c8c0cbbca95a7e19dbef5c25f6422bf2c632f5972f682e44d4f70bb8ca4b74 +generated: "2023-02-07T10:30:58.971766018+01:00" diff --git a/rds/base/charts/layer3_token_storage/Chart.yaml b/rds/base/charts/layer3_token_storage/Chart.yaml new file mode 100644 index 0000000..cac8de2 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +appVersion: "1.0" +description: The storage for tokens and passwords for services. +name: layer3-token-storage +version: 0.3.0 +home: https://www.research-data-services.org/ +type: application +keywords: + - research + - data + - services + - zenodo +maintainers: + - email: peter.heiss@uni-muenster.de + name: Heiss +sources: + - https://github.com/Sciebo-RDS/Sciebo-RDS +icon: https://www.research-data-services.org/img/sciebo.png +dependencies: + - name: common + version: ^0.1.0 + repository: file://../common + alias: layer3-token-storage-common + diff --git a/rds/base/charts/layer3_token_storage/charts/common-0.1.2.tgz b/rds/base/charts/layer3_token_storage/charts/common-0.1.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2374cbbdc57c70ed09a87d63002412f6af231972 GIT binary patch literal 995 zcmV<9104JxiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI&yZ{s!-&Y8brUL^r`jn|Z%#4%tXhecALMbT~%7Xf-&(9&4q zMj{DvbpS^L5 zS@pYO7J_!D4hVJyYy(>Vmb2EB)_h~7q8WIn&OdeIQ|GnR+RJ!SC0_g3@aC8NH7k-ty?#gQj ztB_qK*s0i+Tcb3ZIcB+|AJE45agO%EO2U`;Z8-U>g+%%nk)=<4HI}k#Eh%*q3@uVo z4z*=;8)mVJp4SC=3(jt75n&JDH7#C?*#&k@eD$3D7c}HoEvOb4FEZGJ%YJP3yXxBO zx;Dy}Lbu&%yhc84-M2db*Y`i3V4BHTX^XEPKIJS)l??sG`vt#%=>@g zl7+kL2}II5#$!o-`CO&X`1CjHm(Plk#D4LphHkF>t)r<3?{fn)nWAGo?j8*5@u|3H zJx*Eit*wk%?+axQtNvD|M?sGd_2rj?h2+vmWR`2l^+| Rp8)^>|No_G#=QU*008lp+kXH6 literal 0 HcmV?d00001 diff --git a/rds/base/charts/layer3_token_storage/templates/_helpers.tpl b/rds/base/charts/layer3_token_storage/templates/_helpers.tpl new file mode 100644 index 0000000..149641a --- /dev/null +++ b/rds/base/charts/layer3_token_storage/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "layer3_token_storage.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + + +{{- define "layer3_token_storage.image" -}} +{{ include "common.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- 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 "layer3_token_storage.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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "layer3_token_storage.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "layer3_token_storage.labels" -}} +app.kubernetes.io/name: {{ include "layer3_token_storage.name" . }} +helm.sh/chart: {{ include "layer3_token_storage.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.labels }} +{{ toYaml .Values.labels }} +{{- end -}} +{{- end -}} + +{{- define "layer3_token_storage.domain" -}} +{{- if .Values.global }} +{{- .Values.global.domain -}} +{{- else if hasKey .Values "domain" }} +{{- .Values.domain -}} +{{- else }}"localhost"{{- end -}} +{{- end -}} + +{{- define "layer3_token_storage.secretName" -}} +{{- if .Values.global}} +{{ .Values.global.ingress.tls.secretName }} +{{- else }} +{{ .Values.ingress.tls.secretName }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/rds/base/charts/layer3_token_storage/templates/configmap.yaml b/rds/base/charts/layer3_token_storage/templates/configmap.yaml new file mode 100644 index 0000000..6b1f797 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: layer3tokenconfig + namespace: {{ .Release.Namespace }} +data: + {{- with (mustMergeOverwrite (.Values.global | default dict) .Values.environment) }} + REDIS_HELPER_HOST: {{ .REDIS_HELPER_HOST | default "redis" | quote }} + REDIS_HELPER_PORT: {{ .REDIS_HELPER_PORT | default "6379" | quote }} + REDIS_HOST: {{ .REDIS_HOST | default "redis" | quote }} + REDIS_PORT: {{ .REDIS_PORT | default "6379" | quote }} + REDIS_CHANNEL: {{ .REDIS_CHANNEL | default "TokenStorage_Refresh_Token" | quote }} + {{- end }} + use_inmemory_as_fallover: {{ .Values.environment.IN_MEMORY_AS_FAILOVER | quote }} \ No newline at end of file diff --git a/rds/base/charts/layer3_token_storage/templates/deployment.yaml b/rds/base/charts/layer3_token_storage/templates/deployment.yaml new file mode 100644 index 0000000..2f7055a --- /dev/null +++ b/rds/base/charts/layer3_token_storage/templates/deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "layer3_token_storage.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer3_token_storage.labels" . | indent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: +{{ include "layer3_token_storage.labels" . | indent 6 }} + template: + metadata: + labels: +{{ include "layer3_token_storage.labels" . | indent 8 }} + spec: + initContainers: + - name: deploy + image: redis + command: [ "sh" ] + args: [ "-c", 'while [ "$(redis-cli -h $REDIS_PORT_6379_TCP_ADDR ping)" != "PONG" ]; do sleep 2; done' ] + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: {{ template "layer3_token_storage.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + - configMapRef: + name: proxy + - configMapRef: + name: globalenvvar + - configMapRef: + name: layer3tokenconfig + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + readinessProbe: + httpGet: + path: /metrics + port: http + periodSeconds: 10 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/rds/base/charts/layer3_token_storage/templates/service.yaml b/rds/base/charts/layer3_token_storage/templates/service.yaml new file mode 100644 index 0000000..5e7daa6 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + {{- with .Values.service.annotations }} + annotations: + {{ toYaml . | indent 4 }} + {{- end }} + name: {{ include "layer3_token_storage.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: +{{ include "layer3_token_storage.labels" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app.kubernetes.io/name: {{ include "layer3_token_storage.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/rds/base/charts/layer3_token_storage/templates/tests/test-connection.yaml b/rds/base/charts/layer3_token_storage/templates/tests/test-connection.yaml new file mode 100644 index 0000000..91830d0 --- /dev/null +++ b/rds/base/charts/layer3_token_storage/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "layer3_token_storage.fullname" . }}-test-research" + labels: +{{ include "layer3_token_storage.labels" . | indent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "layer3_token_storage.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/rds/base/charts/layer3_token_storage/values.yaml b/rds/base/charts/layer3_token_storage/values.yaml new file mode 100644 index 0000000..6fc2d0f --- /dev/null +++ b/rds/base/charts/layer3_token_storage/values.yaml @@ -0,0 +1,53 @@ +# Default values for layer3_token_storage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + registry: zivgitlab.wwu.io + repository: sciebo-rds/sciebo-rds/central_service_token_storage + tag: release + pullPolicy: Always + +labels: + app.kubernetes.io/component: research-data-services.org + app.kubernetes.io/part-of: core + research-data-services.org/layer: layer3 + +fullnameOverride: layer3-token-storage + +service: + type: ClusterIP + port: 80 + targetPort: 8080 + annotations: + prometheus.io/scrape: "true" + +domain: localhost +ingress: + tls: + secretName: sciebords-tls-public + +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 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +environment: + # disable IN Memory, when redis is not available. Service will be exited, when in memory not enabled. + IN_MEMORY_AS_FAILOVER: "False" + REDIS_CHANNEL: "TokenStorage_Refresh_Token" diff --git a/rds/base/charts/postgresql/.helmignore b/rds/base/charts/postgresql/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/rds/base/charts/postgresql/.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/rds/base/charts/postgresql/Chart.lock b/rds/base/charts/postgresql/Chart.lock new file mode 100644 index 0000000..dcd041c --- /dev/null +++ b/rds/base/charts/postgresql/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://charts/common + version: 1.10.3 +digest: sha256:82aaf79ebedf82345360ed9d2271ced861bce18bf02165041364808bc5c23c3f +generated: "2023-02-07T10:30:59.478748345+01:00" diff --git a/rds/base/charts/postgresql/Chart.yaml b/rds/base/charts/postgresql/Chart.yaml new file mode 100644 index 0000000..b692733 --- /dev/null +++ b/rds/base/charts/postgresql/Chart.yaml @@ -0,0 +1,30 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 11.14.0 +dependencies: +- name: common + repository: file://charts/common + version: 1.x.x + alias: postgresql-common +description: Chart for PostgreSQL, an object-relational database management system + (ORDBMS) with an emphasis on extensibility and on standards-compliance. +home: https://github.com/bitnami/charts/tree/master/bitnami/postgresql +icon: https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-220x234.png +keywords: +- postgresql +- postgres +- database +- sql +- replication +- cluster +maintainers: +- email: containers@bitnami.com + name: Bitnami +- email: cedric@desaintmartin.fr + name: desaintmartin +name: postgresql +sources: +- https://github.com/bitnami/bitnami-docker-postgresql +- https://www.postgresql.org/ +version: 10.14.3 diff --git a/rds/base/charts/postgresql/README.md b/rds/base/charts/postgresql/README.md new file mode 100644 index 0000000..0f04032 --- /dev/null +++ b/rds/base/charts/postgresql/README.md @@ -0,0 +1,816 @@ +# PostgreSQL + +[PostgreSQL](https://www.postgresql.org/) is an object-relational database management system (ORDBMS) with an emphasis on extensibility and on standards-compliance. + +For HA, please see [this repo](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha) + +## TL;DR + +```console +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/postgresql +``` + +## Introduction + +This chart bootstraps a [PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) deployment on a [Kubernetes](https://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.1.0 +- PV provisioner support in the underlying infrastructure + +## Installing the Chart +To install the chart with the release name `my-release`: + +```console +$ helm install my-release bitnami/postgresql +``` + +The command deploys PostgreSQL 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: + +```console +$ helm delete my-release +``` + +The command removes all the Kubernetes components but PVC's associated with the chart and deletes the release. + +To delete the PVC's associated with `my-release`: + +```console +$ kubectl delete pvc -l release=my-release +``` + +> **Note**: Deleting the PVC's will delete postgresql data as well. Please be cautious before doing it. + +## Parameters + +### Global parameters + +| Name | Description | Value | +| --------------------------------------- | ------------------------------------------------------------------------------------ | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | +| `global.postgresql.postgresqlDatabase` | PostgreSQL database (overrides `postgresqlDatabase`) | `""` | +| `global.postgresql.postgresqlUsername` | PostgreSQL username (overrides `postgresqlUsername`) | `""` | +| `global.postgresql.existingSecret` | Name of existing secret to use for PostgreSQL passwords (overrides `existingSecret`) | `""` | +| `global.postgresql.postgresqlPassword` | PostgreSQL admin password (overrides `postgresqlPassword`) | `""` | +| `global.postgresql.servicePort` | PostgreSQL port (overrides `service.port` | `""` | +| `global.postgresql.replicationPassword` | Replication user password (overrides `replication.password`) | `""` | + + +### Common parameters + +| Name | Description | Value | +| ------------------------ | -------------------------------------------------------------------------------------------- | -------------- | +| `nameOverride` | String to partially override common.names.fullname template (will maintain the release name) | `""` | +| `fullnameOverride` | String to fully override common.names.fullname template | `""` | +| `extraDeploy` | Array of extra objects to deploy with the release (evaluated as a template) | `[]` | +| `commonLabels` | Add labels to all the deployed resources | `{}` | +| `commonAnnotations` | Add annotations to all the deployed resources | `{}` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the deployment | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the deployment | `["infinity"]` | + + +### PostgreSQL parameters + +| Name | Description | Value | +| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `image.registry` | PostgreSQL image registry | `docker.io` | +| `image.repository` | PostgreSQL image repository | `bitnami/postgresql` | +| `image.tag` | PostgreSQL image tag (immutable tags are recommended) | `11.14.0-debian-10-r17` | +| `image.pullPolicy` | PostgreSQL image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify image pull secrets | `[]` | +| `image.debug` | Specify if debug values should be set | `false` | +| `volumePermissions.enabled` | Enable init container that changes volume permissions in the data directory (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 repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag (immutable tags are recommended) | `10-debian-10-r265` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Init container volume-permissions image pull secrets | `[]` | +| `volumePermissions.securityContext.runAsUser` | User ID for the init container | `0` | +| `schedulerName` | Use an alternate scheduler, e.g. "stork". | `""` | +| `lifecycleHooks` | for the PostgreSQL container to automate configuration before or after startup | `{}` | +| `securityContext.enabled` | Enable security context | `true` | +| `securityContext.fsGroup` | Group ID for the pod | `1001` | +| `containerSecurityContext.enabled` | Enable container security context | `true` | +| `containerSecurityContext.runAsUser` | User ID for the container | `1001` | +| `serviceAccount.enabled` | Enable service account (Note: Service Account will only be automatically created if `serviceAccount.name` is not set) | `false` | +| `serviceAccount.name` | Name of an already existing service account. Setting this value disables the automatic service account creation | `""` | +| `serviceAccount.autoMount` | Auto-mount the service account token in the pod | `false` | +| `psp.create` | Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later | `false` | +| `rbac.create` | Create Role and RoleBinding (required for PSP to work) | `false` | +| `replication.enabled` | Enable replication | `false` | +| `replication.user` | Replication user | `repl_user` | +| `replication.password` | Replication user password | `repl_password` | +| `replication.readReplicas` | Number of read replicas replicas | `1` | +| `replication.synchronousCommit` | Set synchronous commit mode. Allowed values: `on`, `remote_apply`, `remote_write`, `local` and `off` | `off` | +| `replication.numSynchronousReplicas` | Number of replicas that will have synchronous replication. Note: Cannot be greater than `replication.readReplicas`. | `0` | +| `replication.applicationName` | Cluster application name. Useful for advanced replication settings | `my_application` | +| `replication.singleService` | Create one service connecting to all read-replicas | `true` | +| `replication.uniqueServices` | Create a unique service for each independent read-replica | `false` | +| `postgresqlPostgresPassword` | PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`, in which case`postgres` is the admin username) | `""` | +| `postgresqlUsername` | PostgreSQL user (has superuser privileges if username is `postgres`) | `postgres` | +| `postgresqlPassword` | PostgreSQL user password | `""` | +| `existingSecret` | Name of existing secret to use for PostgreSQL passwords | `""` | +| `usePasswordFile` | Mount PostgreSQL secret as a file instead of passing environment variable | `false` | +| `postgresqlDatabase` | PostgreSQL database | `""` | +| `postgresqlDataDir` | PostgreSQL data dir folder | `/bitnami/postgresql/data` | +| `extraEnv` | An array to add extra environment variables | `[]` | +| `extraEnvVarsCM` | Name of a Config Map containing extra environment variables | `""` | +| `postgresqlInitdbArgs` | PostgreSQL initdb extra arguments | `""` | +| `postgresqlInitdbWalDir` | Specify a custom location for the PostgreSQL transaction log | `""` | +| `postgresqlConfiguration` | PostgreSQL configuration | `{}` | +| `postgresqlExtendedConf` | Extended Runtime Config Parameters (appended to main or default configuration) | `{}` | +| `primaryAsStandBy.enabled` | Whether to enable current cluster's primary as standby server of another cluster or not | `false` | +| `primaryAsStandBy.primaryHost` | The Host of replication primary in the other cluster | `""` | +| `primaryAsStandBy.primaryPort` | The Port of replication primary in the other cluster | `""` | +| `pgHbaConfiguration` | PostgreSQL client authentication configuration | `""` | +| `configurationConfigMap` | ConfigMap with PostgreSQL configuration | `""` | +| `extendedConfConfigMap` | ConfigMap with PostgreSQL extended configuration | `""` | +| `initdbScripts` | Dictionary of initdb scripts | `{}` | +| `initdbScriptsConfigMap` | ConfigMap with scripts to be run at first boot | `""` | +| `initdbScriptsSecret` | Secret with scripts to be run at first boot (in case it contains sensitive information) | `""` | +| `initdbUser` | Specify the PostgreSQL username to execute the initdb scripts | `""` | +| `initdbPassword` | Specify the PostgreSQL password to execute the initdb scripts | `""` | +| `containerPorts.postgresql` | PostgreSQL container port | `5432` | +| `audit.logHostname` | Log client hostnames | `false` | +| `audit.logConnections` | Add client log-in operations to the log file | `false` | +| `audit.logDisconnections` | Add client log-outs operations to the log file | `false` | +| `audit.pgAuditLog` | Add operations to log using the pgAudit extension | `""` | +| `audit.pgAuditLogCatalog` | Log catalog using pgAudit | `off` | +| `audit.clientMinMessages` | Message log level to share with the user | `error` | +| `audit.logLinePrefix` | Template for log line prefix (default if not set) | `""` | +| `audit.logTimezone` | Timezone for the log timestamps | `""` | +| `postgresqlSharedPreloadLibraries` | Shared preload libraries (comma-separated list) | `pgaudit` | +| `postgresqlMaxConnections` | Maximum total connections | `""` | +| `postgresqlPostgresConnectionLimit` | Maximum connections for the postgres user | `""` | +| `postgresqlDbUserConnectionLimit` | Maximum connections for the non-admin user | `""` | +| `postgresqlTcpKeepalivesInterval` | TCP keepalives interval | `""` | +| `postgresqlTcpKeepalivesIdle` | TCP keepalives idle | `""` | +| `postgresqlTcpKeepalivesCount` | TCP keepalives count | `""` | +| `postgresqlStatementTimeout` | Statement timeout | `""` | +| `postgresqlPghbaRemoveFilters` | Comma-separated list of patterns to remove from the pg_hba.conf file | `""` | +| `terminationGracePeriodSeconds` | Seconds the pod needs to terminate gracefully | `""` | +| `ldap.enabled` | Enable LDAP support | `false` | +| `ldap.url` | LDAP URL beginning in the form `ldap[s]://host[:port]/basedn` | `""` | +| `ldap.server` | IP address or name of the LDAP server. | `""` | +| `ldap.port` | Port number on the LDAP server to connect to | `""` | +| `ldap.prefix` | String to prepend to the user name when forming the DN to bind | `""` | +| `ldap.suffix` | String to append to the user name when forming the DN to bind | `""` | +| `ldap.baseDN` | Root DN to begin the search for the user in | `""` | +| `ldap.bindDN` | DN of user to bind to LDAP | `""` | +| `ldap.bind_password` | Password for the user to bind to LDAP | `""` | +| `ldap.search_attr` | Attribute to match against the user name in the search | `""` | +| `ldap.search_filter` | The search filter to use when doing search+bind authentication | `""` | +| `ldap.scheme` | Set to `ldaps` to use LDAPS | `""` | +| `ldap.tls` | Set to `1` to use TLS encryption | `""` | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.clusterIP` | Static clusterIP or None for headless services | `""` | +| `service.port` | PostgreSQL port | `5432` | +| `service.nodePort` | Specify the nodePort value for the LoadBalancer and NodePort service types | `""` | +| `service.annotations` | Annotations for PostgreSQL service | `{}` | +| `service.loadBalancerIP` | Load balancer IP if service type is `LoadBalancer` | `""` | +| `service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | +| `service.loadBalancerSourceRanges` | Addresses that are allowed when service is LoadBalancer | `[]` | +| `shmVolume.enabled` | Enable emptyDir volume for /dev/shm for primary and read replica(s) Pod(s) | `true` | +| `shmVolume.chmod.enabled` | Set to `true` to `chmod 777 /dev/shm` on a initContainer (ignored if `volumePermissions.enabled` is `false`) | `true` | +| `shmVolume.sizeLimit` | Set this to enable a size limit on the shm tmpfs. Note that the size of the tmpfs counts against container's memory limit | `""` | +| `persistence.enabled` | Enable persistence using PVC | `true` | +| `persistence.existingClaim` | Provide an existing `PersistentVolumeClaim`, the value is evaluated as a template. | `""` | +| `persistence.mountPath` | The path the volume will be mounted at, useful when using different | `/bitnami/postgresql` | +| `persistence.subPath` | The subdirectory of the volume to mount to | `""` | +| `persistence.storageClass` | PVC Storage Class for PostgreSQL volume | `""` | +| `persistence.accessModes` | PVC Access Mode for PostgreSQL volume | `["ReadWriteOnce"]` | +| `persistence.size` | PVC Storage Request for PostgreSQL volume | `8Gi` | +| `persistence.annotations` | Annotations for the PVC | `{}` | +| `persistence.selector` | Selector to match an existing Persistent Volume (this value is evaluated as a template) | `{}` | +| `updateStrategy.type` | updateStrategy for PostgreSQL StatefulSet and its reads StatefulSets | `RollingUpdate` | +| `primary.podAffinityPreset` | PostgreSQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.podAntiAffinityPreset` | PostgreSQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `primary.nodeAffinityPreset.type` | PostgreSQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `primary.nodeAffinityPreset.key` | PostgreSQL primary node label key to match Ignored if `primary.affinity` is set. | `""` | +| `primary.nodeAffinityPreset.values` | PostgreSQL primary node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `primary.affinity` | Affinity for PostgreSQL primary pods assignment | `{}` | +| `primary.nodeSelector` | Node labels for PostgreSQL primary pods assignment | `{}` | +| `primary.tolerations` | Tolerations for PostgreSQL primary pods assignment | `[]` | +| `primary.extraPodSpec` | Optionally specify extra PodSpec | `{}` | +| `primary.labels` | Map of labels to add to the statefulset (postgresql primary) | `{}` | +| `primary.annotations` | Annotations for PostgreSQL primary pods | `{}` | +| `primary.podLabels` | Map of labels to add to the pods (postgresql primary) | `{}` | +| `primary.podAnnotations` | Map of annotations to add to the pods (postgresql primary) | `{}` | +| `primary.priorityClassName` | Priority Class to use for each pod (postgresql primary) | `""` | +| `primary.extraInitContainers` | Extra init containers to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumeMounts` | Extra volume mounts to add to the pods (postgresql primary) | `[]` | +| `primary.extraVolumes` | Extra volumes to add to the pods (postgresql primary) | `[]` | +| `primary.sidecars` | Extra containers to the pod | `[]` | +| `primary.service.type` | Allows using a different service type for primary | `""` | +| `primary.service.nodePort` | Allows using a different nodePort for primary | `""` | +| `primary.service.clusterIP` | Allows using a different clusterIP for primary | `""` | +| `readReplicas.podAffinityPreset` | PostgreSQL read only pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.podAntiAffinityPreset` | PostgreSQL read only pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `readReplicas.nodeAffinityPreset.type` | PostgreSQL read only node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `readReplicas.nodeAffinityPreset.key` | PostgreSQL read only node label key to match Ignored if `primary.affinity` is set. | `""` | +| `readReplicas.nodeAffinityPreset.values` | PostgreSQL read only node label values to match. Ignored if `primary.affinity` is set. | `[]` | +| `readReplicas.affinity` | Affinity for PostgreSQL read only pods assignment | `{}` | +| `readReplicas.nodeSelector` | Node labels for PostgreSQL read only pods assignment | `{}` | +| `readReplicas.tolerations` | Tolerations for PostgreSQL read only pods assignment | `[]` | +| `readReplicas.topologySpreadConstraints` | Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template | `[]` | +| `readReplicas.extraPodSpec` | Optionally specify extra PodSpec | `{}` | +| `readReplicas.labels` | Map of labels to add to the statefulsets (postgresql readReplicas) | `{}` | +| `readReplicas.annotations` | Annotations for PostgreSQL read only pods | `{}` | +| `readReplicas.podLabels` | Map of labels to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.podAnnotations` | Map of annotations to add to the pods (postgresql readReplicas) | `{}` | +| `readReplicas.priorityClassName` | Priority Class to use for each pod (postgresql readReplicas) | `""` | +| `readReplicas.extraInitContainers` | Extra init containers to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumeMounts` | Extra volume mounts to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.extraVolumes` | Extra volumes to add to the pods (postgresql readReplicas) | `[]` | +| `readReplicas.sidecars` | Extra containers to the pod | `[]` | +| `readReplicas.service.type` | Allows using a different service type for readReplicas | `""` | +| `readReplicas.service.nodePort` | Allows using a different nodePort for readReplicas | `""` | +| `readReplicas.service.clusterIP` | Allows using a different clusterIP for readReplicas | `""` | +| `readReplicas.persistence.enabled` | Whether to enable PostgreSQL read replicas replicas persistence | `true` | +| `readReplicas.resources` | CPU/Memory resource requests/limits override for readReplicass. Will fallback to `values.resources` if not defined. | `{}` | +| `resources.requests` | The requested resources for the container | `{}` | +| `networkPolicy.enabled` | Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.explicitNamespacesSelector` | A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed | `{}` | +| `startupProbe.enabled` | Enable startupProbe | `false` | +| `startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `30` | +| `startupProbe.periodSeconds` | Period seconds for startupProbe | `15` | +| `startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `5` | +| `startupProbe.failureThreshold` | Failure threshold for startupProbe | `10` | +| `startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `livenessProbe.enabled` | Enable livenessProbe | `true` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `30` | +| `livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `6` | +| `livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `readinessProbe.enabled` | Enable readinessProbe | `true` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `5` | +| `readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `6` | +| `readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `customStartupProbe` | Override default startup probe | `{}` | +| `customLivenessProbe` | Override default liveness probe | `{}` | +| `customReadinessProbe` | Override default readiness probe | `{}` | +| `tls.enabled` | Enable TLS traffic support | `false` | +| `tls.autoGenerated` | Generate automatically self-signed TLS certificates | `false` | +| `tls.preferServerCiphers` | Whether to use the server's TLS cipher preferences rather than the client's | `true` | +| `tls.certificatesSecret` | Name of an existing secret that contains the certificates | `""` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename | `""` | +| `tls.crlFilename` | File containing a Certificate Revocation List | `""` | +| `metrics.enabled` | Start a prometheus exporter | `false` | +| `metrics.resources` | Prometheus exporter container resources | `{}` | +| `metrics.service.type` | Kubernetes Service type | `ClusterIP` | +| `metrics.service.annotations` | Additional annotations for metrics exporter pod | `{}` | +| `metrics.service.loadBalancerIP` | loadBalancerIP if redis metrics service type is `LoadBalancer` | `""` | +| `metrics.serviceMonitor.enabled` | Set this to `true` to create ServiceMonitor for Prometheus operator | `false` | +| `metrics.serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.namespace` | Optional namespace in which to create ServiceMonitor | `""` | +| `metrics.serviceMonitor.interval` | Scrape interval. If not set, the Prometheus default scrape interval is used | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Scrape timeout. If not set, the Prometheus default scrape timeout is used | `""` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `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 | `""` | +| `metrics.prometheusRule.rules` | Create specified [Rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) | `[]` | +| `metrics.image.registry` | PostgreSQL Exporter image registry | `docker.io` | +| `metrics.image.repository` | PostgreSQL Exporter image repository | `bitnami/postgres-exporter` | +| `metrics.image.tag` | PostgreSQL Exporter image tag (immutable tags are recommended) | `0.10.0-debian-10-r133` | +| `metrics.image.pullPolicy` | PostgreSQL Exporter image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify image pull secrets | `[]` | +| `metrics.customMetrics` | Define additional custom metrics | `{}` | +| `metrics.extraEnvVars` | Extra environment variables to add to postgres-exporter | `[]` | +| `metrics.securityContext.enabled` | Enable security context for metrics | `false` | +| `metrics.securityContext.runAsUser` | User ID for the container for metrics | `1001` | +| `metrics.livenessProbe.enabled` | Enable livenessProbe | `true` | +| `metrics.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `5` | +| `metrics.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `10` | +| `metrics.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `metrics.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `6` | +| `metrics.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `metrics.readinessProbe.enabled` | Enable readinessProbe | `true` | +| `metrics.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `metrics.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `metrics.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `5` | +| `metrics.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `6` | +| `metrics.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | + + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +$ helm install my-release \ + --set postgresqlPassword=secretpassword,postgresqlDatabase=my-database \ + bitnami/postgresql +``` + +The above command sets the PostgreSQL `postgres` account password to `secretpassword`. Additionally it creates a database named `my-database`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +$ helm install my-release -f values.yaml bitnami/postgresql +``` + +> **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. + +### Customizing primary and read replica services in a replicated configuration + +At the top level, there is a service object which defines the services for both primary and readReplicas. For deeper customization, there are service objects for both the primary and read types individually. This allows you to override the values in the top level service object so that the primary and read can be of different service types and with different clusterIPs / nodePorts. Also in the case you want the primary and read to be of type nodePort, you will need to set the nodePorts to different values to prevent a collision. The values that are deeper in the primary.service or readReplicas.service objects will take precedence over the top level service object. + +### Use a different PostgreSQL version + +To modify the application version used in this chart, specify a different version of the image using the `image.tag` parameter and/or a different repository using the `image.repository` parameter. Refer to the [chart documentation for more information on these parameters and how to use them with images from a private registry](https://docs.bitnami.com/kubernetes/infrastructure/postgresql/configuration/change-image-version/). + +### postgresql.conf / pg_hba.conf files as configMap + +This helm chart also supports to customize the whole configuration file. + +Add your custom file to "files/postgresql.conf" in your working directory. This file will be mounted as configMap to the containers and it will be used for configuring the PostgreSQL server. + +Alternatively, you can add additional PostgreSQL configuration parameters using the `postgresqlExtendedConf` parameter as a dict, using camelCase, e.g. {"sharedBuffers": "500MB"}. Alternatively, to replace the entire default configuration use `postgresqlConfiguration`. + +In addition to these options, you can also set an external ConfigMap with all the configuration files. This is done by setting the `configurationConfigMap` parameter. Note that this will override the two previous options. + +### Allow settings to be loaded from files other than the default `postgresql.conf` + +If you don't want to provide the whole PostgreSQL configuration file and only specify certain parameters, you can add your extended `.conf` files to "files/conf.d/" in your working directory. +Those files will be mounted as configMap to the containers adding/overwriting the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +Alternatively, you can also set an external ConfigMap with all the extra configuration files. This is done by setting the `extendedConfConfigMap` parameter. Note that this will override the previous option. + +### Initialize a fresh instance + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, they must be located inside the chart folder `files/docker-entrypoint-initdb.d` so they can be consumed as a ConfigMap. + +Alternatively, you can specify custom scripts using the `initdbScripts` parameter as dict. + +In addition to these options, 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 two previous options. If your initialization scripts contain sensitive information such as credentials or passwords, you can use the `initdbScriptsSecret` parameter. + +The allowed extensions are `.sh`, `.sql` and `.sql.gz`. + +### 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 an existing secret that contains the certificates. No defaults. +- `tls.certFilename`: Certificate filename. No defaults. +- `tls.certKeyFilename`: Certificate key filename. No defaults. + +For example: + +* First, create the secret with the cetificates files: + + ```console + kubectl create secret generic certificates-tls-secret --from-file=./cert.crt --from-file=./cert.key --from-file=./ca.crt + ``` + +* Then, use the following parameters: + + ```console + volumePermissions.enabled=true + tls.enabled=true + tls.certificatesSecret="certificates-tls-secret" + tls.certFilename="cert.crt" + tls.certKeyFilename="cert.key" + ``` + + > Note TLS and VolumePermissions: PostgreSQL requires certain permissions on sensitive files (such as certificate keys) to start up. Due to an on-going [issue](https://github.com/kubernetes/kubernetes/issues/57923) regarding kubernetes permissions and the use of `containerSecurityContext.runAsUser`, you must enable `volumePermissions` to ensure everything works as expected. + +### Sidecars + +If you need additional containers to run within the same pod as PostgreSQL (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 +# For the PostgreSQL primary +primary: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +# For the PostgreSQL replicas +readReplicas: + sidecars: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +### Metrics + +The chart optionally can start a metrics exporter for [prometheus](https://prometheus.io). The metrics endpoint (port 9187) is not exposed and it is expected that the metrics are collected from inside the k8s 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). + +The exporter allows to create custom metrics from additional SQL queries. See the Chart's `values.yaml` for an example and consult the [exporters documentation](https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file) for more details. + +### Use of global variables + +In more complex scenarios, we may have the following tree of dependencies + +``` + +--------------+ + | | + +------------+ Chart 1 +-----------+ + | | | | + | --------+------+ | + | | | + | | | + | | | + | | | + v v v ++-------+------+ +--------+------+ +--------+------+ +| | | | | | +| PostgreSQL | | Sub-chart 1 | | Sub-chart 2 | +| | | | | | ++--------------+ +---------------+ +---------------+ +``` + +The three charts below depend on the parent chart Chart 1. However, subcharts 1 and 2 may need to connect to PostgreSQL as well. In order to do so, subcharts 1 and 2 need to know the PostgreSQL credentials, so one option for deploying could be deploy Chart 1 with the following parameters: + +``` +postgresql.postgresqlPassword=testtest +subchart1.postgresql.postgresqlPassword=testtest +subchart2.postgresql.postgresqlPassword=testtest +postgresql.postgresqlDatabase=db1 +subchart1.postgresql.postgresqlDatabase=db1 +subchart2.postgresql.postgresqlDatabase=db1 +``` + +If the number of dependent sub-charts increases, installing the chart with parameters can become increasingly difficult. An alternative would be to set the credentials using global variables as follows: + +``` +global.postgresql.postgresqlPassword=testtest +global.postgresql.postgresqlDatabase=db1 +``` + +This way, the credentials will be available in all of the subcharts. + +## Persistence + +The [Bitnami PostgreSQL](https://github.com/bitnami/bitnami-docker-postgresql) image stores the PostgreSQL data and configurations at the `/bitnami/postgresql` path of the container. + +Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. +See the [Parameters](#parameters) section to configure the PVC or to disable persistence. + +If you already have data in it, you will fail to sync to standby nodes for all commits, details can refer to [code](https://github.com/bitnami/bitnami-docker-postgresql/blob/8725fe1d7d30ebe8d9a16e9175d05f7ad9260c93/9.6/debian-9/rootfs/libpostgresql.sh#L518-L556). If you need to use those data, please covert them to sql and import after `helm install` finished. + +## NetworkPolicy + +To enable network policy for PostgreSQL, 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: + +```console +$ kubectl annotate namespace default "net.beta.kubernetes.io/network-policy={\"ingress\":{\"isolation\":\"DefaultDeny\"}}" +``` + +With NetworkPolicy enabled, traffic will be limited to just port 5432. + +For more precise policy, set `networkPolicy.allowExternal=false`. This will only allow pods with the generated client label to connect to PostgreSQL. +This label will be displayed in the output of a successful install. + +## Differences between Bitnami PostgreSQL image and [Docker Official](https://hub.docker.com/_/postgres) image + +- The Docker Official PostgreSQL image does not support replication. If you pass any replication environment variable, this would be ignored. The only environment variables supported by the Docker Official image are POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD, POSTGRES_INITDB_ARGS, POSTGRES_INITDB_WALDIR and PGDATA. All the remaining environment variables are specific to the Bitnami PostgreSQL image. +- The Bitnami PostgreSQL image is non-root by default. This requires that you run the pod with `securityContext` and updates the permissions of the volume with an `initContainer`. A key benefit of this configuration is that the pod follows security best practices and is prepared to run on Kubernetes distributions with hard security constraints like OpenShift. +- For OpenShift, one may either define the runAsUser and fsGroup accordingly, or try this more dynamic option: volumePermissions.securityContext.runAsUser="auto",securityContext.enabled=false,containerSecurityContext.enabled=false,shmVolume.chmod.enabled=false + +### Deploy chart using Docker Official PostgreSQL Image + +From chart version 4.0.0, it is possible to use this chart with the Docker Official PostgreSQL image. +Besides specifying the new Docker repository and tag, it is important to modify the PostgreSQL data directory and volume mount point. Basically, the PostgreSQL data dir cannot be the mount point directly, it has to be a subdirectory. + +``` +image.repository=postgres +image.tag=10.6 +postgresqlDataDir=/data/pgdata +persistence.mountPath=/data/ +``` + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` paremeter(s). Find more infomation 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 + +It's necessary to specify the existing passwords while performing an upgrade to ensure the secrets are not updated with invalid randomly generated passwords. Remember to specify the existing values of the `postgresqlPassword` and `replication.password` parameters when upgrading the chart: + +```bash +$ helm upgrade my-release bitnami/postgresql \ + --set postgresqlPassword=[POSTGRESQL_PASSWORD] \ + --set replication.password=[REPLICATION_PASSWORD] +``` + +> Note: you need to substitute the placeholders _[POSTGRESQL_PASSWORD]_, and _[REPLICATION_PASSWORD]_ with the values obtained from instructions 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 Chart. + +**Considerations when upgrading to this version** + +- 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/ + +#### Breaking changes + +- The term `master` has been replaced with `primary` and `slave` with `readReplicas` throughout the chart. Role names have changed from `master` and `slave` to `primary` and `read`. + +To upgrade to `10.0.0`, it should be done reusing the PVCs used to hold the PostgreSQL data on your previous release. To do so, follow the instructions below (the following example assumes that the release name is `postgresql`): + +> NOTE: Please, create a backup of your database before running any of those actions. + +Obtain the credentials and the names of the PVCs used to hold the PostgreSQL data on your current release: + +```console +$ export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +$ export POSTGRESQL_PVC=$(kubectl get pvc -l app.kubernetes.io/instance=postgresql,role=master -o jsonpath="{.items[0].metadata.name}") +``` + +Delete the PostgreSQL statefulset. Notice the option `--cascade=false`: + +```console +$ kubectl delete statefulsets.apps postgresql-postgresql --cascade=false +``` + +Now the upgrade works: + +```console +$ helm upgrade postgresql bitnami/postgresql --set postgresqlPassword=$POSTGRESQL_PASSWORD --set persistence.existingClaim=$POSTGRESQL_PVC +``` + +You will have to delete the existing PostgreSQL pod and the new statefulset is going to create a new one + +```console +$ kubectl delete pod postgresql-postgresql-0 +``` + +Finally, you should see the lines below in PostgreSQL container logs: + +```console +$ kubectl logs $(kubectl get pods -l app.kubernetes.io/instance=postgresql,app.kubernetes.io/name=postgresql,role=primary -o jsonpath="{.items[0].metadata.name}") +... +postgresql 08:05:12.59 INFO ==> Deploying PostgreSQL with persisted data... +... +``` + +### To 9.0.0 + +In this version the chart was adapted to follow the Helm label best practices, see [PR 3021](https://github.com/bitnami/charts/pull/3021). That means the backward compatibility is not guarantee when upgrading the chart to this major version. + +As a workaround, you can delete the existing statefulset (using the `--cascade=false` flag pods are not deleted) before upgrade the chart. For example, this can be a valid workflow: + +- Deploy an old version (8.X.X) + +```console +$ helm install postgresql bitnami/postgresql --version 8.10.14 +``` + +- Old version is up and running + +```console +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 1 2020-08-04 13:39:54.783480286 +0000 UTC deployed postgresql-8.10.14 11.8.0 + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 76s +``` + +- The upgrade to the latest one (9.X.X) is going to fail + +```console +$ helm upgrade postgresql bitnami/postgresql +Error: UPGRADE FAILED: cannot patch "postgresql-postgresql" with kind StatefulSet: StatefulSet.apps "postgresql-postgresql" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden +``` + +- Delete the statefulset + +```console +$ kubectl delete statefulsets.apps --cascade=false postgresql-postgresql +statefulset.apps "postgresql-postgresql" deleted +``` + +- Now the upgrade works + +```console +$ helm upgrade postgresql bitnami/postgresql +$ helm ls +NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION +postgresql default 3 2020-08-04 13:42:08.020385884 +0000 UTC deployed postgresql-9.1.2 11.8.0 +``` + +- We can kill the existing pod and the new statefulset is going to create a new one: + +```console +$ kubectl delete pod postgresql-postgresql-0 +pod "postgresql-postgresql-0" deleted + +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +postgresql-postgresql-0 1/1 Running 0 19s +``` + +Please, note that without the `--cascade=false` both objects (statefulset and pod) are going to be removed and both objects will be deployed again with the `helm upgrade` command + +### To 8.0.0 + +Prefixes the port names with their protocols to comply with Istio conventions. + +If you depend on the port names in your setup, make sure to update them to reflect this change. + +### To 7.1.0 + +Adds support for LDAP configuration. + +### To 7.0.0 + +Helm performs a lookup for the object based on its group (apps), version (v1), and kind (Deployment). Also known as its GroupVersionKind, or GVK. Changing the GVK is considered a compatibility breaker from Kubernetes' point of view, so you cannot "upgrade" those objects to the new GVK in-place. Earlier versions of Helm 3 did not perform the lookup correctly which has since been fixed to match the spec. + +In https://github.com/helm/charts/pull/17281 the `apiVersion` of the statefulset resources was updated to `apps/v1` in tune with the api's deprecated, resulting in compatibility breakage. + +This major version bump signifies this change. + +### To 6.5.7 + +In this version, the chart will use PostgreSQL with the Postgis extension included. The version used with Postgresql version 10, 11 and 12 is Postgis 2.5. It has been compiled with the following dependencies: + +- protobuf +- protobuf-c +- json-c +- geos +- proj + +### To 5.0.0 + +In this version, the **chart is using PostgreSQL 11 instead of PostgreSQL 10**. You can find the main difference and notable changes in the following links: [https://www.postgresql.org/about/news/1894/](https://www.postgresql.org/about/news/1894/) and [https://www.postgresql.org/about/featurematrix/](https://www.postgresql.org/about/featurematrix/). + +For major releases of PostgreSQL, the internal data storage format is subject to change, thus complicating upgrades, you can see some errors like the following one in the logs: + +```console +Welcome to the Bitnami postgresql container +Subscribe to project updates by watching https://github.com/bitnami/bitnami-docker-postgresql +Submit issues and feature requests at https://github.com/bitnami/bitnami-docker-postgresql/issues +Send us your feedback at containers@bitnami.com + +INFO ==> ** Starting PostgreSQL setup ** +NFO ==> Validating settings in POSTGRESQL_* env vars.. +INFO ==> Initializing PostgreSQL database... +INFO ==> postgresql.conf file not detected. Generating it... +INFO ==> pg_hba.conf file not detected. Generating it... +INFO ==> Deploying PostgreSQL with persisted data... +INFO ==> Configuring replication parameters +INFO ==> Loading custom scripts... +INFO ==> Enabling remote connections +INFO ==> Stopping PostgreSQL... +INFO ==> ** PostgreSQL setup finished! ** + +INFO ==> ** Starting PostgreSQL ** + [1] FATAL: database files are incompatible with server + [1] DETAIL: The data directory was initialized by PostgreSQL version 10, which is not compatible with this version 11.3. +``` + +In this case, you should migrate the data from the old chart to the new one following an approach similar to that described in [this section](https://www.postgresql.org/docs/current/upgrading.html#UPGRADING-VIA-PGDUMPALL) from the official documentation. Basically, create a database dump in the old chart, move and restore it in the new one. + +### To 4.0.0 + +This chart will use by default the Bitnami PostgreSQL container starting from version `10.7.0-r68`. This version moves the initialization logic from node.js to bash. This new version of the chart requires setting the `POSTGRES_PASSWORD` in the slaves as well, in order to properly configure the `pg_hba.conf` file. Users from previous versions of the chart are advised to upgrade immediately. + +IMPORTANT: If you do not want to upgrade the chart version then make sure you use the `10.7.0-r68` version of the container. Otherwise, you will get this error + +``` +The POSTGRESQL_PASSWORD environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development +``` + +### To 3.0.0 + +This releases make it possible to specify different nodeSelector, affinity and tolerations for master and slave pods. +It also fixes an issue with `postgresql.master.fullname` helper template not obeying fullnameOverride. + +#### Breaking changes + +- `affinty` has been renamed to `master.affinity` and `slave.affinity`. +- `tolerations` has been renamed to `master.tolerations` and `slave.tolerations`. +- `nodeSelector` has been renamed to `master.nodeSelector` and `slave.nodeSelector`. + +### To 2.0.0 + +In order to upgrade from the `0.X.X` branch to `1.X.X`, you should follow the below steps: + +- Obtain the service name (`SERVICE_NAME`) and password (`OLD_PASSWORD`) of the existing postgresql chart. You can find the instructions to obtain the password in the NOTES.txt, the service name can be obtained by running + +```console +$ kubectl get svc +``` + +- Install (not upgrade) the new version + +```console +$ helm repo update +$ helm install my-release bitnami/postgresql +``` + +- Connect to the new pod (you can obtain the name by running `kubectl get pods`): + +```console +$ kubectl exec -it NAME bash +``` + +- Once logged in, create a dump file from the previous database using `pg_dump`, for that we should connect to the previous postgresql chart: + +```console +$ pg_dump -h SERVICE_NAME -U postgres DATABASE_NAME > /tmp/backup.sql +``` + +After run above command you should be prompted for a password, this password is the previous chart password (`OLD_PASSWORD`). +This operation could take some time depending on the database size. + +- Once you have the backup file, you can restore it with a command like the one below: + +```console +$ psql -U postgres DATABASE_NAME < /tmp/backup.sql +``` + +In this case, you are accessing to the local postgresql, so the password should be the new one (you can find it in NOTES.txt). + +If you want to restore the database and the database schema does not exist, it is necessary to first follow the steps described below. + +```console +$ psql -U postgres +postgres=# drop database DATABASE_NAME; +postgres=# create database DATABASE_NAME; +postgres=# create user USER_NAME; +postgres=# alter role USER_NAME with password 'BITNAMI_USER_PASSWORD'; +postgres=# grant all privileges on database DATABASE_NAME to USER_NAME; +postgres=# alter database DATABASE_NAME owner to USER_NAME; +``` + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rds/base/charts/postgresql/charts/common-1.10.3.tgz b/rds/base/charts/postgresql/charts/common-1.10.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d5aeb7f569975b47fe5bb078b8f667faaedd6078 GIT binary patch literal 13331 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%0AEU37OKglxJ8x~z)4#%1y@;;ww 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 literal 0 HcmV?d00001 diff --git a/rds/base/charts/postgresql/charts/common/.helmignore b/rds/base/charts/postgresql/charts/common/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/postgresql/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/rds/base/charts/postgresql/charts/common/Chart.yaml b/rds/base/charts/postgresql/charts/common/Chart.yaml new file mode 100644 index 0000000..cf934aa --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.10.0 +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 +- https://www.bitnami.com/ +type: library +version: 1.10.3 diff --git a/rds/base/charts/postgresql/charts/common/README.md b/rds/base/charts/postgresql/charts/common/README.md new file mode 100644 index 0000000..cbbc31d --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/README.md @@ -0,0 +1,328 @@ +# 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.1.0 + +## 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.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.cronjob.apiVersion` | Return the appropriate apiVersion for cronjob. | `.` Chart context | +| `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 | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.policy.apiVersion` | Return the appropriate apiVersion for podsecuritypolicy. | `.` Chart context | +| `common.capabilities.networkPolicy.apiVersion` | Return the appropriate apiVersion for networkpolicy. | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` 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 (deprecated: use common.images.renderPullSecrets instead) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | +| `common.images.renderPullSecrets` | Return the proper Docker Image Registry Secret Names (evaluates values as templates) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|-------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | +| `common.ingress.supportsPathType` | Prints "true" if the pathType field is supported | `.` Chart context | +| `common.ingress.supportsIngressClassname` | Prints "true" if the ingressClassname field is supported | `.` Chart context | + +### 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 Input | +|-------------------------|------------------------------------------------------------|-------------------| +| `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. | +| `common.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `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" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "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" "subchart" "subchart" "context" $` secret, field and subchart 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 (evaluated as templates). + +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/rds/base/charts/postgresql/charts/common/templates/_affinities.tpl b/rds/base/charts/postgresql/charts/common/templates/_affinities.tpl new file mode 100644 index 0000000..189ea40 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_affinities.tpl @@ -0,0 +1,102 @@ +{{/* 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 }} + - {{ . | quote }} + {{- 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 }} + - {{ . | quote }} + {{- 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" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + 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/rds/base/charts/postgresql/charts/common/templates/_capabilities.tpl b/rds/base/charts/postgresql/charts/common/templates/_capabilities.tpl new file mode 100644 index 0000000..b94212b --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_capabilities.tpl @@ -0,0 +1,128 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for poddisruptionbudget. +*/}} +{{- define "common.capabilities.policy.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "common.capabilities.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for cronjob. +*/}} +{{- define "common.capabilities.cronjob.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "batch/v1beta1" -}} +{{- else -}} +{{- print "batch/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- 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" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_errors.tpl b/rds/base/charts/postgresql/charts/common/templates/_errors.tpl new file mode 100644 index 0000000..a79cc2e --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* 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 upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_images.tpl b/rds/base/charts/postgresql/charts/common/templates/_images.tpl new file mode 100644 index 0000000..42ffbc7 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_images.tpl @@ -0,0 +1,75 @@ +{{/* 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 -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) +{{ 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 -}} + +{{/* +Return the proper Docker Image Registry Secret Names evaluating values as templates +{{ include "common.images.renderPullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $) }} +*/}} +{{- define "common.images.renderPullSecrets" -}} + {{- $pullSecrets := list }} + {{- $context := .context }} + + {{- if $context.Values.global }} + {{- range $context.Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_ingress.tpl b/rds/base/charts/postgresql/charts/common/templates/_ingress.tpl new file mode 100644 index 0000000..f905f20 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_ingress.tpl @@ -0,0 +1,55 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "common.ingress.supportsIngressClassname" . }} +*/}} +{{- define "common.ingress.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_labels.tpl b/rds/base/charts/postgresql/charts/common/templates/_labels.tpl new file mode 100644 index 0000000..252066c --- /dev/null +++ b/rds/base/charts/postgresql/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/rds/base/charts/postgresql/charts/common/templates/_names.tpl b/rds/base/charts/postgresql/charts/common/templates/_names.tpl new file mode 100644 index 0000000..cf03231 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_names.tpl @@ -0,0 +1,52 @@ +{{/* 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 -}} + +{{/* +Create a default fully qualified dependency 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. +Usage: +{{ include "common.names.dependency.fullname" (dict "chartName" "dependency-chart-name" "chartValues" .Values.dependency-chart "context" $) }} +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_secrets.tpl b/rds/base/charts/postgresql/charts/common/templates/_secrets.tpl new file mode 100644 index 0000000..60b84a7 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_secrets.tpl @@ -0,0 +1,129 @@ +{{/* 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" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- 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 -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- if index $secret.data .key }} + {{- $password = index $secret.data .key }} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_storage.tpl b/rds/base/charts/postgresql/charts/common/templates/_storage.tpl new file mode 100644 index 0000000..60e2a84 --- /dev/null +++ b/rds/base/charts/postgresql/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/rds/base/charts/postgresql/charts/common/templates/_tplvalues.tpl b/rds/base/charts/postgresql/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000..2db1668 --- /dev/null +++ b/rds/base/charts/postgresql/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/rds/base/charts/postgresql/charts/common/templates/_utils.tpl b/rds/base/charts/postgresql/charts/common/templates/_utils.tpl new file mode 100644 index 0000000..ea083a2 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* 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 | quote }} {{ .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 -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/_warnings.tpl b/rds/base/charts/postgresql/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000..ae10fa4 --- /dev/null +++ b/rds/base/charts/postgresql/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/rds/base/charts/postgresql/charts/common/templates/validations/_cassandra.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000..ded1ae3 --- /dev/null +++ b/rds/base/charts/postgresql/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 (or (not $existingSecret) (eq $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/rds/base/charts/postgresql/charts/common/templates/validations/_mariadb.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000..b6906ff --- /dev/null +++ b/rds/base/charts/postgresql/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 (or (not $existingSecret) (eq $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/rds/base/charts/postgresql/charts/common/templates/validations/_mongodb.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000..a071ea4 --- /dev/null +++ b/rds/base/charts/postgresql/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 (or (not $existingSecret) (eq $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/rds/base/charts/postgresql/charts/common/templates/validations/_postgresql.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000..164ec0d --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* 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 (or (not $existingSecret) (eq $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/rds/base/charts/postgresql/charts/common/templates/validations/_redis.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000..5d72959 --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* 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" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "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 -}} + +{{/* +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 -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/templates/validations/_validations.tpl b/rds/base/charts/postgresql/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000..9a814cf --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* 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" "subchart" "subchart" "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" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- 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=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/charts/common/values.yaml b/rds/base/charts/postgresql/charts/common/values.yaml new file mode 100644 index 0000000..f2df68e --- /dev/null +++ b/rds/base/charts/postgresql/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/rds/base/charts/postgresql/ci/commonAnnotations.yaml b/rds/base/charts/postgresql/ci/commonAnnotations.yaml new file mode 100644 index 0000000..97e18a4 --- /dev/null +++ b/rds/base/charts/postgresql/ci/commonAnnotations.yaml @@ -0,0 +1,3 @@ +commonAnnotations: + helm.sh/hook: "\"pre-install, pre-upgrade\"" + helm.sh/hook-weight: "-1" diff --git a/rds/base/charts/postgresql/ci/default-values.yaml b/rds/base/charts/postgresql/ci/default-values.yaml new file mode 100644 index 0000000..fc2ba60 --- /dev/null +++ b/rds/base/charts/postgresql/ci/default-values.yaml @@ -0,0 +1 @@ +# Leave this file empty to ensure that CI runs builds against the default configuration in values.yaml. diff --git a/rds/base/charts/postgresql/ci/shmvolume-disabled-values.yaml b/rds/base/charts/postgresql/ci/shmvolume-disabled-values.yaml new file mode 100644 index 0000000..347d3b4 --- /dev/null +++ b/rds/base/charts/postgresql/ci/shmvolume-disabled-values.yaml @@ -0,0 +1,2 @@ +shmVolume: + enabled: false diff --git a/rds/base/charts/postgresql/files/README.md b/rds/base/charts/postgresql/files/README.md new file mode 100644 index 0000000..1813a2f --- /dev/null +++ b/rds/base/charts/postgresql/files/README.md @@ -0,0 +1 @@ +Copy here your postgresql.conf and/or pg_hba.conf files to use it as a config map. diff --git a/rds/base/charts/postgresql/files/conf.d/README.md b/rds/base/charts/postgresql/files/conf.d/README.md new file mode 100644 index 0000000..184c187 --- /dev/null +++ b/rds/base/charts/postgresql/files/conf.d/README.md @@ -0,0 +1,4 @@ +If you don't want to provide the whole configuration file and only specify certain parameters, you can copy here your extended `.conf` files. +These files will be injected as a config maps and add/overwrite the default configuration using the `include_dir` directive that allows settings to be loaded from files other than the default `postgresql.conf`. + +More info in the [bitnami-docker-postgresql README](https://github.com/bitnami/bitnami-docker-postgresql#configuration-file). diff --git a/rds/base/charts/postgresql/files/docker-entrypoint-initdb.d/README.md b/rds/base/charts/postgresql/files/docker-entrypoint-initdb.d/README.md new file mode 100644 index 0000000..cba3809 --- /dev/null +++ b/rds/base/charts/postgresql/files/docker-entrypoint-initdb.d/README.md @@ -0,0 +1,3 @@ +You can copy here your custom `.sh`, `.sql` or `.sql.gz` file so they are executed during the first boot of the image. + +More info in the [bitnami-docker-postgresql](https://github.com/bitnami/bitnami-docker-postgresql#initializing-a-new-instance) repository. \ No newline at end of file diff --git a/rds/base/charts/postgresql/templates/NOTES.txt b/rds/base/charts/postgresql/templates/NOTES.txt new file mode 100644 index 0000000..f35ebc5 --- /dev/null +++ b/rds/base/charts/postgresql/templates/NOTES.txt @@ -0,0 +1,89 @@ +CHART NAME: {{ .Chart.Name }} +CHART VERSION: {{ .Chart.Version }} +APP VERSION: {{ .Chart.AppVersion }} + +** Please be patient while the chart is being deployed ** + +{{- if .Values.diagnosticMode.enabled }} +The chart has been deployed in diagnostic mode. All probes have been disabled and the command has been overwritten with: + + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 4 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 4 }} + +Get the list of pods by executing: + + kubectl get pods --namespace {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} + +Access the pod you want to debug by executing + + kubectl exec --namespace {{ .Release.Namespace }} -ti -- bash + +In order to replicate the container startup scripts execute this command: + + /opt/bitnami/scripts/postgresql/entrypoint.sh /opt/bitnami/scripts/postgresql/run.sh + +{{- else }} + +PostgreSQL can be accessed via port {{ template "postgresql.servicePort" . }} on the following DNS names from within your cluster: + + {{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local - Read/Write connection +{{- if .Values.replication.enabled }} +{{- if .Values.replication.singleService }} + {{ template "common.names.fullname" . }}-read.{{ .Release.Namespace }}.svc.cluster.local - Read only connection +{{- end }} +{{- if .Values.replication.uniqueServices }} +{{- $replicaCount := .Values.replication.readReplicas | int }} +{{- $root := . }} +{{- range $i, $e := until $replicaCount }} + {{ template "common.names.fullname" $root }}-read-{{ $i }}.{{ $root.Release.Namespace }}.svc.cluster.local - Read only connection to replica {{ $i }} +{{- end }} +{{- end }} +{{- end }} + +{{- if not (eq (include "postgresql.username" .) "postgres") }} + +To get the password for "postgres" run: + + export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-postgres-password}" | base64 --decode) +{{- end }} + +To get the password for "{{ template "postgresql.username" . }}" run: + + export POSTGRES_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "postgresql.secretName" . }} -o jsonpath="{.data.postgresql-password}" | base64 --decode) + +To connect to your database run the following command: + + kubectl run {{ template "common.names.fullname" . }}-client --rm --tty -i --restart='Never' --namespace {{ .Release.Namespace }} --image {{ template "postgresql.image" . }} --env="PGPASSWORD=$POSTGRES_PASSWORD" {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} + --labels="{{ template "common.names.fullname" . }}-client=true" {{- end }} --command -- psql --host {{ template "common.names.fullname" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.servicePort" . }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label {{ template "common.names.fullname" . }}-client=true" will be able to connect to this PostgreSQL cluster. +{{- end }} + +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 {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "common.names.fullname" . }}) + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $NODE_IP --port $NODE_PORT -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- 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 {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host $SERVICE_IP --port {{ template "postgresql.servicePort" . }} -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} + +{{- else if contains "ClusterIP" .Values.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "common.names.fullname" . }} {{ template "postgresql.servicePort" . }}:{{ template "postgresql.servicePort" . }} & + {{ if (include "postgresql.password" . ) }}PGPASSWORD="$POSTGRES_PASSWORD" {{ end }}psql --host 127.0.0.1 -U {{ .Values.postgresqlUsername }} -d {{- if .Values.postgresqlDatabase }} {{ .Values.postgresqlDatabase }}{{- else }} postgres{{- end }} -p {{ template "postgresql.servicePort" . }} + +{{- end }} +{{- end }} + +{{- include "postgresql.validateValues" . -}} +{{- include "common.warnings.rollingTag" .Values.image -}} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} diff --git a/rds/base/charts/postgresql/templates/_helpers.tpl b/rds/base/charts/postgresql/templates/_helpers.tpl new file mode 100644 index 0000000..16e4456 --- /dev/null +++ b/rds/base/charts/postgresql/templates/_helpers.tpl @@ -0,0 +1,361 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "postgresql.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 "postgresql.primary.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- $fullname := default (printf "%s-%s" .Release.Name $name) .Values.fullnameOverride -}} +{{- if .Values.replication.enabled -}} +{{- printf "%s-%s" $fullname "primary" | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $fullname | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper PostgreSQL image name +*/}} +{{- define "postgresql.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper PostgreSQL metrics image name +*/}} +{{- define "postgresql.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 "postgresql.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "postgresql.imagePullSecrets" -}} +{{ include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image .Values.volumePermissions.image) "global" .Values.global) }} +{{- end -}} + +{{/* +Returns the available value for certain key in an existing secret (if it exists), +otherwise it generates a random value. +*/}} +{{- define "getValueFromSecret" }} +{{- $len := (default 16 .Length) | int -}} +{{- $obj := (lookup "v1" "Secret" .Namespace .Name).data -}} +{{- if $obj }} +{{- index $obj .Key | b64dec -}} +{{- else -}} +{{- randAlphaNum $len -}} +{{- end -}} +{{- end }} + +{{/* +Return PostgreSQL postgres user password +*/}} +{{- define "postgresql.postgres.password" -}} +{{- if .Values.global.postgresql.postgresqlPostgresPassword }} + {{- .Values.global.postgresql.postgresqlPostgresPassword -}} +{{- else if .Values.postgresqlPostgresPassword -}} + {{- .Values.postgresqlPostgresPassword -}} +{{- else -}} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "postgresql-postgres-password") -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL password +*/}} +{{- define "postgresql.password" -}} +{{- if .Values.global.postgresql.postgresqlPassword }} + {{- .Values.global.postgresql.postgresqlPassword -}} +{{- else if .Values.postgresqlPassword -}} + {{- .Values.postgresqlPassword -}} +{{- else -}} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "postgresql-password") -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication password +*/}} +{{- define "postgresql.replication.password" -}} +{{- if .Values.global.postgresql.replicationPassword }} + {{- .Values.global.postgresql.replicationPassword -}} +{{- else if .Values.replication.password -}} + {{- .Values.replication.password -}} +{{- else -}} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "postgresql-replication-password") -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL username +*/}} +{{- define "postgresql.username" -}} +{{- if .Values.global.postgresql.postgresqlUsername }} + {{- .Values.global.postgresql.postgresqlUsername -}} +{{- else -}} + {{- .Values.postgresqlUsername -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL replication username +*/}} +{{- define "postgresql.replication.username" -}} +{{- if .Values.global.postgresql.replicationUser }} + {{- .Values.global.postgresql.replicationUser -}} +{{- else -}} + {{- .Values.replication.user -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL port +*/}} +{{- define "postgresql.servicePort" -}} +{{- if .Values.global.postgresql.servicePort }} + {{- .Values.global.postgresql.servicePort -}} +{{- else -}} + {{- .Values.service.port -}} +{{- end -}} +{{- end -}} + +{{/* +Return PostgreSQL created database +*/}} +{{- define "postgresql.database" -}} +{{- if .Values.global.postgresql.postgresqlDatabase }} + {{- .Values.global.postgresql.postgresqlDatabase -}} +{{- else if .Values.postgresqlDatabase -}} + {{- .Values.postgresqlDatabase -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "postgresql.secretName" -}} +{{- if .Values.global.postgresql.existingSecret }} + {{- printf "%s" (tpl .Values.global.postgresql.existingSecret $) -}} +{{- else if .Values.existingSecret -}} + {{- printf "%s" (tpl .Values.existingSecret $) -}} +{{- else -}} + {{- printf "%s" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if we should use an existingSecret. +*/}} +{{- define "postgresql.useExistingSecret" -}} +{{- if or .Values.global.postgresql.existingSecret .Values.existingSecret -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a secret object should be created +*/}} +{{- define "postgresql.createSecret" -}} +{{- if not (include "postgresql.useExistingSecret" .) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the configuration ConfigMap name. +*/}} +{{- define "postgresql.configurationCM" -}} +{{- if .Values.configurationConfigMap -}} +{{- printf "%s" (tpl .Values.configurationConfigMap $) -}} +{{- else -}} +{{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the extended configuration ConfigMap name. +*/}} +{{- define "postgresql.extendedConfigurationCM" -}} +{{- if .Values.extendedConfConfigMap -}} +{{- printf "%s" (tpl .Values.extendedConfConfigMap $) -}} +{{- else -}} +{{- printf "%s-extended-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap should be mounted with PostgreSQL configuration +*/}} +{{- define "postgresql.mountConfigurationCM" -}} +{{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts ConfigMap name. +*/}} +{{- define "postgresql.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} +{{- printf "%s" (tpl .Values.initdbScriptsConfigMap $) -}} +{{- else -}} +{{- printf "%s-init-scripts" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the initialization scripts Secret name. +*/}} +{{- define "postgresql.initdbScriptsSecret" -}} +{{- printf "%s" (tpl .Values.initdbScriptsSecret $) -}} +{{- end -}} + +{{/* +Get the metrics ConfigMap name. +*/}} +{{- define "postgresql.metricsCM" -}} +{{- printf "%s-metrics" (include "common.names.fullname" .) -}} +{{- end -}} + +{{/* +Get the readiness probe command +*/}} +{{- define "postgresql.readinessProbeCommand" -}} +- | +{{- if (include "postgresql.database" .) }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if .Values.tls.enabled }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} +{{- else }} + exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if .Values.tls.enabled }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} +{{- end }} +{{- if contains "bitnami/" .Values.image.repository }} + [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "postgresql.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "postgresql.validateValues.ldapConfigurationMethod" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.psp" .) -}} +{{- $messages := append $messages (include "postgresql.validateValues.tls" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If ldap.url is used then you don't need the other settings for ldap +*/}} +{{- define "postgresql.validateValues.ldapConfigurationMethod" -}} +{{- if and .Values.ldap.enabled (and (not (empty .Values.ldap.url)) (not (empty .Values.ldap.server))) }} +postgresql: ldap.url, ldap.server + You cannot set both `ldap.url` and `ldap.server` at the same time. + Please provide a unique way to configure LDAP. + More info at https://www.postgresql.org/docs/current/auth-ldap.html +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql - If PSP is enabled RBAC should be enabled too +*/}} +{{- define "postgresql.validateValues.psp" -}} +{{- if and .Values.psp.create (not .Values.rbac.create) }} +postgresql: psp.create, rbac.create + RBAC should be enabled if PSP is enabled in order for PSP to work. + More info at https://kubernetes.io/docs/concepts/policy/pod-security-policy/#authorizing-policies +{{- end -}} +{{- end -}} + +{{/* +Validate values of Postgresql TLS - When TLS is enabled, so must be VolumePermissions +*/}} +{{- define "postgresql.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.volumePermissions.enabled) }} +postgresql: tls.enabled, volumePermissions.enabled + When TLS is enabled you must enable volumePermissions as well to ensure certificates files have + the right permissions. +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "postgresql.tlsCert" -}} +{{- if .Values.tls.autoGenerated }} + {{- printf "/opt/bitnami/postgresql/certs/tls.crt" -}} +{{- else -}} + {{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "postgresql.tlsCertKey" -}} +{{- if .Values.tls.autoGenerated }} + {{- printf "/opt/bitnami/postgresql/certs/tls.key" -}} +{{- else -}} +{{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/postgresql/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "postgresql.tlsCACert" -}} +{{- if .Values.tls.autoGenerated }} + {{- printf "/opt/bitnami/postgresql/certs/ca.crt" -}} +{{- else -}} + {{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.certCAFilename -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the CRL file. +*/}} +{{- define "postgresql.tlsCRL" -}} +{{- if .Values.tls.crlFilename -}} +{{- printf "/opt/bitnami/postgresql/certs/%s" .Values.tls.crlFilename -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a TLS credentials secret object should be created +*/}} +{{- define "postgresql.createTlsSecret" -}} +{{- if and .Values.tls.autoGenerated (not .Values.tls.certificatesSecret) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "postgresql.tlsSecretName" -}} +{{- if .Values.tls.autoGenerated }} + {{- printf "%s-crt" (include "common.names.fullname" .) -}} +{{- else -}} + {{ required "A secret containing TLS certificates is required when TLS is enabled" .Values.tls.certificatesSecret }} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/postgresql/templates/configmap.yaml b/rds/base/charts/postgresql/templates/configmap.yaml new file mode 100644 index 0000000..df8f763 --- /dev/null +++ b/rds/base/charts/postgresql/templates/configmap.yaml @@ -0,0 +1,34 @@ +{{ if and (or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration) (not .Values.configurationConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-configuration + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{- if (.Files.Glob "files/postgresql.conf") }} +{{ (.Files.Glob "files/postgresql.conf").AsConfig | indent 2 }} +{{- else if .Values.postgresqlConfiguration }} + postgresql.conf: | +{{- range $key, $value := default dict .Values.postgresqlConfiguration }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- if (.Files.Glob "files/pg_hba.conf") }} +{{ (.Files.Glob "files/pg_hba.conf").AsConfig | indent 2 }} +{{- else if .Values.pgHbaConfiguration }} + pg_hba.conf: | +{{ .Values.pgHbaConfiguration | indent 4 }} +{{- end }} +{{ end }} diff --git a/rds/base/charts/postgresql/templates/extended-config-configmap.yaml b/rds/base/charts/postgresql/templates/extended-config-configmap.yaml new file mode 100644 index 0000000..abbbf85 --- /dev/null +++ b/rds/base/charts/postgresql/templates/extended-config-configmap.yaml @@ -0,0 +1,29 @@ +{{- if and (or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf) (not .Values.extendedConfConfigMap)}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-extended-configuration + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{- with .Files.Glob "files/conf.d/*.conf" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{ with .Values.postgresqlExtendedConf }} + override.conf: | +{{- range $key, $value := . }} + {{- if kindIs "string" $value }} + {{ $key | snakecase }} = '{{ $value }}' + {{- else }} + {{ $key | snakecase }} = {{ $value }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/extra-list.yaml b/rds/base/charts/postgresql/templates/extra-list.yaml new file mode 100644 index 0000000..9ac65f9 --- /dev/null +++ b/rds/base/charts/postgresql/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/initialization-configmap.yaml b/rds/base/charts/postgresql/templates/initialization-configmap.yaml new file mode 100644 index 0000000..3e546fe --- /dev/null +++ b/rds/base/charts/postgresql/templates/initialization-configmap.yaml @@ -0,0 +1,26 @@ +{{- if and (or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScripts) (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-init-scripts + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.sql.gz" }} +binaryData: +{{- range $path, $bytes := . }} + {{ base $path }}: {{ $.Files.Get $path | b64enc | quote }} +{{- end }} +{{- end }} +data: +{{- with .Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql}" }} +{{ .AsConfig | indent 2 }} +{{- end }} +{{- include "common.tplvalues.render" (dict "value" .Values.initdbScripts "context" .) | nindent 2 }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/metrics-configmap.yaml b/rds/base/charts/postgresql/templates/metrics-configmap.yaml new file mode 100644 index 0000000..b711197 --- /dev/null +++ b/rds/base/charts/postgresql/templates/metrics-configmap.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "postgresql.metricsCM" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + custom-metrics.yaml: {{ toYaml .Values.metrics.customMetrics | quote }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/metrics-svc.yaml b/rds/base/charts/postgresql/templates/metrics-svc.yaml new file mode 100644 index 0000000..203aab2 --- /dev/null +++ b/rds/base/charts/postgresql/templates/metrics-svc.yaml @@ -0,0 +1,29 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- toYaml .Values.metrics.service.annotations | nindent 4 }} +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: http-metrics + port: 9187 + targetPort: http-metrics + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary +{{- end }} diff --git a/rds/base/charts/postgresql/templates/networkpolicy.yaml b/rds/base/charts/postgresql/templates/networkpolicy.yaml new file mode 100644 index 0000000..400351e --- /dev/null +++ b/rds/base/charts/postgresql/templates/networkpolicy.yaml @@ -0,0 +1,42 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + ingress: + # Allow inbound connections + - ports: + - port: {{ template "postgresql.servicePort" . }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.explicitNamespacesSelector }} + namespaceSelector: +{{ toYaml .Values.networkPolicy.explicitNamespacesSelector | indent 12 }} + {{- end }} + - podSelector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 14 }} + role: read + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes + - ports: + - port: 9187 + {{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/podsecuritypolicy.yaml b/rds/base/charts/postgresql/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000..0eefb3b --- /dev/null +++ b/rds/base/charts/postgresql/templates/podsecuritypolicy.yaml @@ -0,0 +1,42 @@ +{{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- if and $pspAvailable .Values.psp.create }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + privileged: false + volumes: + - 'configMap' + - 'secret' + - 'persistentVolumeClaim' + - 'emptyDir' + - 'projected' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'RunAsAny' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/rds/base/charts/postgresql/templates/prometheusrule.yaml b/rds/base/charts/postgresql/templates/prometheusrule.yaml new file mode 100644 index 0000000..1eff223 --- /dev/null +++ b/rds/base/charts/postgresql/templates/prometheusrule.yaml @@ -0,0 +1,28 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} +{{- if .Values.metrics.prometheusRule.namespace }} + namespace: {{ .Values.metrics.prometheusRule.namespace }} +{{- else }} + namespace: {{ .Release.Namespace }} +{{- end }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.metrics.prometheusRule.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: +{{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "postgresql.name" $ }} + rules: {{ tpl (toYaml .) $ | nindent 8 }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/role.yaml b/rds/base/charts/postgresql/templates/role.yaml new file mode 100644 index 0000000..1366eda --- /dev/null +++ b/rds/base/charts/postgresql/templates/role.yaml @@ -0,0 +1,24 @@ +{{- if .Values.rbac.create }} +kind: Role +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +rules: + {{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} + {{- if and $pspAvailable .Values.psp.create }} + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + verbs: ["use"] + resourceNames: + - {{ template "common.names.fullname" . }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/rolebinding.yaml b/rds/base/charts/postgresql/templates/rolebinding.yaml new file mode 100644 index 0000000..988cb73 --- /dev/null +++ b/rds/base/charts/postgresql/templates/rolebinding.yaml @@ -0,0 +1,23 @@ +{{- if .Values.rbac.create }} +kind: RoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: Role + name: {{ template "common.names.fullname" . }} + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/secrets.yaml b/rds/base/charts/postgresql/templates/secrets.yaml new file mode 100644 index 0000000..d73bf2f --- /dev/null +++ b/rds/base/charts/postgresql/templates/secrets.yaml @@ -0,0 +1,27 @@ +{{- if (include "postgresql.createSecret" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +type: Opaque +data: + {{- if not (eq (include "postgresql.username" .) "postgres") }} + postgresql-postgres-password: {{ include "postgresql.postgres.password" . | b64enc | quote }} + {{- end }} + postgresql-password: {{ include "postgresql.password" . | b64enc | quote }} + {{- if .Values.replication.enabled }} + postgresql-replication-password: {{ include "postgresql.replication.password" . | b64enc | quote }} + {{- end }} + {{- if (and .Values.ldap.enabled .Values.ldap.bind_password)}} + postgresql-ldap-password: {{ .Values.ldap.bind_password | b64enc | quote }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/postgresql/templates/serviceaccount.yaml b/rds/base/charts/postgresql/templates/serviceaccount.yaml new file mode 100644 index 0000000..8e951b8 --- /dev/null +++ b/rds/base/charts/postgresql/templates/serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if and (.Values.serviceAccount.enabled) (not .Values.serviceAccount.name) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + name: {{ template "common.names.fullname" . }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/servicemonitor.yaml b/rds/base/charts/postgresql/templates/servicemonitor.yaml new file mode 100644 index 0000000..60efc80 --- /dev/null +++ b/rds/base/charts/postgresql/templates/servicemonitor.yaml @@ -0,0 +1,44 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "common.names.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + 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 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "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 }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.relabelings "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.metricRelabelings "context" $) | nindent 8 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/statefulset-readreplicas.yaml b/rds/base/charts/postgresql/templates/statefulset-readreplicas.yaml new file mode 100644 index 0000000..ad2a06a --- /dev/null +++ b/rds/base/charts/postgresql/templates/statefulset-readreplicas.yaml @@ -0,0 +1,430 @@ +{{- if .Values.replication.enabled }} +{{- $readReplicasResources := coalesce .Values.readReplicas.resources .Values.resources -}} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: "{{ template "common.names.fullname" . }}-read" + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: read + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.readReplicas.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.labels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.readReplicas.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: {{ .Values.replication.readReplicas }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: read + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: read + role: read + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.podLabels "context" $) | nindent 8 }} + {{- end }} +{{- with .Values.readReplicas.podAnnotations }} + annotations: +{{ toYaml . | indent 8 }} +{{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.readReplicas.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAffinityPreset "component" "read" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.readReplicas.podAntiAffinityPreset "component" "read" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.readReplicas.nodeAffinityPreset.type "key" .Values.readReplicas.nodeAffinityPreset.key "values" .Values.readReplicas.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.readReplicas.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.serviceAccount.autoMount }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name}} + {{- end }} + {{- if or .Values.readReplicas.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- 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 }} + volumeMounts: + {{ if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.readReplicas.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.readReplicas.priorityClassName }} + priorityClassName: {{ .Values.readReplicas.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if $readReplicasResources }} + resources: {{- toYaml $readReplicasResources | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + - name: POSTGRESQL_PORT_NUMBER + value: {{ .Values.containerPorts.postgresql | quote }} + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + - name: POSTGRES_REPLICATION_MODE + value: "slave" + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + - name: POSTGRES_MASTER_HOST + value: {{ template "common.names.fullname" . }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ include "postgresql.servicePort" . | quote }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ .Values.containerPorts.postgresql }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- end }} + 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: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + 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 }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{ end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.readReplicas.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} +{{- if .Values.readReplicas.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.sidecars "context" $ ) | nindent 8 }} +{{- end }} + volumes: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ template "postgresql.tlsSecretName" . }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 1Gi + {{- end }} + {{- if or (not .Values.persistence.enabled) (not .Values.readReplicas.persistence.enabled) }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.readReplicas.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.readReplicas.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.readReplicas.extraPodSpec }} + {{- include "common.tplvalues.render" (dict "value" .Values.readReplicas.extraPodSpec "context" $) | nindent 6 }} + {{- end }} + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} +{{- if and .Values.persistence.enabled .Values.readReplicas.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + 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) }} + + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/statefulset.yaml b/rds/base/charts/postgresql/templates/statefulset.yaml new file mode 100644 index 0000000..49afa54 --- /dev/null +++ b/rds/base/charts/postgresql/templates/statefulset.yaml @@ -0,0 +1,636 @@ +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ template "postgresql.primary.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.primary.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.labels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- with .Values.primary.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + serviceName: {{ template "common.names.fullname" . }}-headless + replicas: 1 + updateStrategy: + type: {{ .Values.updateStrategy.type }} + {{- if (eq "Recreate" .Values.updateStrategy.type) }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: + {{- include "common.labels.matchLabels" . | nindent 6 }} + role: primary + template: + metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 8 }} + role: primary + app.kubernetes.io/component: primary + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.primary.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.podLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- with .Values.primary.podAnnotations }} + annotations: {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if .Values.schedulerName }} + schedulerName: "{{ .Values.schedulerName }}" + {{- end }} +{{- include "postgresql.imagePullSecrets" . | indent 6 }} + {{- if .Values.primary.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.primary.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAffinityPreset "component" "primary" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.primary.podAntiAffinityPreset "component" "primary" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.primary.nodeAffinityPreset.type "key" .Values.primary.nodeAffinityPreset.key "values" .Values.primary.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.primary.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.primary.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.primary.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.primary.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if .Values.securityContext.enabled }} + securityContext: {{- omit .Values.securityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.serviceAccount.autoMount }} + {{- if .Values.serviceAccount.enabled }} + serviceAccountName: {{ default (include "common.names.fullname" . ) .Values.serviceAccount.name }} + {{- end }} + {{- if or .Values.primary.extraInitContainers (and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled))) }} + initContainers: + {{- if and .Values.volumePermissions.enabled (or .Values.persistence.enabled (and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled) .Values.tls.enabled) }} + - name: init-chmod-data + image: {{ template "postgresql.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + command: + - /bin/sh + - -cx + - | + {{- if .Values.persistence.enabled }} + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown `id -u`:`id -G | cut -d " " -f2` {{ .Values.persistence.mountPath }} + {{- else }} + chown {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} {{ .Values.persistence.mountPath }} + {{- end }} + mkdir -p {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + chmod 700 {{ .Values.persistence.mountPath }}/data {{- if (include "postgresql.mountConfigurationCM" .) }} {{ .Values.persistence.mountPath }}/conf {{- end }} + find {{ .Values.persistence.mountPath }} -mindepth 1 -maxdepth 1 {{- if not (include "postgresql.mountConfigurationCM" .) }} -not -name "conf" {{- end }} -not -name ".snapshot" -not -name "lost+found" | \ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + xargs chown -R `id -u`:`id -G | cut -d " " -f2` + {{- else }} + xargs chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} + {{- end }} + {{- end }} + {{- if and .Values.shmVolume.enabled .Values.shmVolume.chmod.enabled }} + chmod -R 777 /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + cp /tmp/certs/* /opt/bitnami/postgresql/certs/ + {{- if eq ( toString ( .Values.volumePermissions.securityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` /opt/bitnami/postgresql/certs/ + {{- else }} + chown -R {{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.securityContext.fsGroup }} /opt/bitnami/postgresql/certs/ + {{- end }} + chmod 600 {{ template "postgresql.tlsCertKey" . }} + {{- 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 }} + volumeMounts: + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + mountPath: /tmp/certs + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + {{- end }} + {{- end }} + {{- if .Values.primary.extraInitContainers }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.extraInitContainers "context" $ ) | nindent 8 }} + {{- end }} + {{- end }} + {{- if .Values.primary.priorityClassName }} + priorityClassName: {{ .Values.primary.priorityClassName }} + {{- end }} + containers: + - name: {{ template "common.names.fullname" . }} + image: {{ template "postgresql.image" . }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- end }} + {{- if .Values.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: POSTGRESQL_PORT_NUMBER + value: {{ .Values.containerPorts.postgresql | quote }} + - name: POSTGRESQL_VOLUME_DIR + value: "{{ .Values.persistence.mountPath }}" + {{- if .Values.postgresqlInitdbArgs }} + - name: POSTGRES_INITDB_ARGS + value: {{ .Values.postgresqlInitdbArgs | quote }} + {{- end }} + {{- if .Values.postgresqlInitdbWalDir }} + - name: POSTGRES_INITDB_WALDIR + value: {{ .Values.postgresqlInitdbWalDir | quote }} + {{- end }} + {{- if .Values.initdbUser }} + - name: POSTGRESQL_INITSCRIPTS_USERNAME + value: {{ .Values.initdbUser }} + {{- end }} + {{- if .Values.initdbPassword }} + - name: POSTGRESQL_INITSCRIPTS_PASSWORD + value: {{ .Values.initdbPassword }} + {{- end }} + {{- if .Values.persistence.mountPath }} + - name: PGDATA + value: {{ .Values.postgresqlDataDir | quote }} + {{- end }} + {{- if .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_MASTER_HOST + value: {{ .Values.primaryAsStandBy.primaryHost }} + - name: POSTGRES_MASTER_PORT_NUMBER + value: {{ .Values.primaryAsStandBy.primaryPort | quote }} + {{- end }} + {{- if or .Values.replication.enabled .Values.primaryAsStandBy.enabled }} + - name: POSTGRES_REPLICATION_MODE + {{- if .Values.primaryAsStandBy.enabled }} + value: "slave" + {{- else }} + value: "master" + {{- end }} + - name: POSTGRES_REPLICATION_USER + value: {{ include "postgresql.replication.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_REPLICATION_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-replication-password" + {{- else }} + - name: POSTGRES_REPLICATION_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-replication-password + {{- end }} + {{- if not (eq .Values.replication.synchronousCommit "off")}} + - name: POSTGRES_SYNCHRONOUS_COMMIT_MODE + value: {{ .Values.replication.synchronousCommit | quote }} + - name: POSTGRES_NUM_SYNCHRONOUS_REPLICAS + value: {{ .Values.replication.numSynchronousReplicas | quote }} + {{- end }} + - name: POSTGRES_CLUSTER_APP_NAME + value: {{ .Values.replication.applicationName }} + {{- end }} + {{- if not (eq (include "postgresql.username" .) "postgres") }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-postgres-password" + {{- else }} + - name: POSTGRES_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-postgres-password + {{- end }} + {{- end }} + - name: POSTGRES_USER + value: {{ include "postgresql.username" . | quote }} + {{- if .Values.usePasswordFile }} + - name: POSTGRES_PASSWORD_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + {{- if (include "postgresql.database" .) }} + - name: POSTGRES_DB + value: {{ (include "postgresql.database" .) | quote }} + {{- end }} + {{- if .Values.extraEnv }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnv "context" $) | nindent 12 }} + {{- end }} + - name: POSTGRESQL_ENABLE_LDAP + value: {{ ternary "yes" "no" .Values.ldap.enabled | quote }} + {{- if .Values.ldap.enabled }} + - name: POSTGRESQL_LDAP_SERVER + value: {{ .Values.ldap.server }} + - name: POSTGRESQL_LDAP_PORT + value: {{ .Values.ldap.port | quote }} + - name: POSTGRESQL_LDAP_SCHEME + value: {{ .Values.ldap.scheme }} + {{- if .Values.ldap.tls }} + - name: POSTGRESQL_LDAP_TLS + value: "1" + {{- end }} + - name: POSTGRESQL_LDAP_PREFIX + value: {{ .Values.ldap.prefix | quote }} + - name: POSTGRESQL_LDAP_SUFFIX + value: {{ .Values.ldap.suffix | quote }} + - name: POSTGRESQL_LDAP_BASE_DN + value: {{ .Values.ldap.baseDN }} + - name: POSTGRESQL_LDAP_BIND_DN + value: {{ .Values.ldap.bindDN }} + {{- if (not (empty .Values.ldap.bind_password)) }} + - name: POSTGRESQL_LDAP_BIND_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-ldap-password + {{- end}} + - name: POSTGRESQL_LDAP_SEARCH_ATTR + value: {{ .Values.ldap.search_attr }} + - name: POSTGRESQL_LDAP_SEARCH_FILTER + value: {{ .Values.ldap.search_filter }} + - name: POSTGRESQL_LDAP_URL + value: {{ .Values.ldap.url }} + {{- end}} + - name: POSTGRESQL_ENABLE_TLS + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: POSTGRESQL_TLS_PREFER_SERVER_CIPHERS + value: {{ ternary "yes" "no" .Values.tls.preferServerCiphers | quote }} + - name: POSTGRESQL_TLS_CERT_FILE + value: {{ template "postgresql.tlsCert" . }} + - name: POSTGRESQL_TLS_KEY_FILE + value: {{ template "postgresql.tlsCertKey" . }} + {{- if .Values.tls.certCAFilename }} + - name: POSTGRESQL_TLS_CA_FILE + value: {{ template "postgresql.tlsCACert" . }} + {{- end }} + {{- if .Values.tls.crlFilename }} + - name: POSTGRESQL_TLS_CRL_FILE + value: {{ template "postgresql.tlsCRL" . }} + {{- end }} + {{- end }} + - name: POSTGRESQL_LOG_HOSTNAME + value: {{ .Values.audit.logHostname | quote }} + - name: POSTGRESQL_LOG_CONNECTIONS + value: {{ .Values.audit.logConnections | quote }} + - name: POSTGRESQL_LOG_DISCONNECTIONS + value: {{ .Values.audit.logDisconnections | quote }} + {{- if .Values.audit.logLinePrefix }} + - name: POSTGRESQL_LOG_LINE_PREFIX + value: {{ .Values.audit.logLinePrefix | quote }} + {{- end }} + {{- if .Values.audit.logTimezone }} + - name: POSTGRESQL_LOG_TIMEZONE + value: {{ .Values.audit.logTimezone | quote }} + {{- end }} + {{- if .Values.audit.pgAuditLog }} + - name: POSTGRESQL_PGAUDIT_LOG + value: {{ .Values.audit.pgAuditLog | quote }} + {{- end }} + - name: POSTGRESQL_PGAUDIT_LOG_CATALOG + value: {{ .Values.audit.pgAuditLogCatalog | quote }} + - name: POSTGRESQL_CLIENT_MIN_MESSAGES + value: {{ .Values.audit.clientMinMessages | quote }} + - name: POSTGRESQL_SHARED_PRELOAD_LIBRARIES + value: {{ .Values.postgresqlSharedPreloadLibraries | quote }} + {{- if .Values.postgresqlMaxConnections }} + - name: POSTGRESQL_MAX_CONNECTIONS + value: {{ .Values.postgresqlMaxConnections | quote }} + {{- end }} + {{- if .Values.postgresqlPostgresConnectionLimit }} + - name: POSTGRESQL_POSTGRES_CONNECTION_LIMIT + value: {{ .Values.postgresqlPostgresConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlDbUserConnectionLimit }} + - name: POSTGRESQL_USERNAME_CONNECTION_LIMIT + value: {{ .Values.postgresqlDbUserConnectionLimit | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesInterval }} + - name: POSTGRESQL_TCP_KEEPALIVES_INTERVAL + value: {{ .Values.postgresqlTcpKeepalivesInterval | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesIdle }} + - name: POSTGRESQL_TCP_KEEPALIVES_IDLE + value: {{ .Values.postgresqlTcpKeepalivesIdle | quote }} + {{- end }} + {{- if .Values.postgresqlStatementTimeout }} + - name: POSTGRESQL_STATEMENT_TIMEOUT + value: {{ .Values.postgresqlStatementTimeout | quote }} + {{- end }} + {{- if .Values.postgresqlTcpKeepalivesCount }} + - name: POSTGRESQL_TCP_KEEPALIVES_COUNT + value: {{ .Values.postgresqlTcpKeepalivesCount | quote }} + {{- end }} + {{- if .Values.postgresqlPghbaRemoveFilters }} + - name: POSTGRESQL_PGHBA_REMOVE_FILTERS + value: {{ .Values.postgresqlPghbaRemoveFilters | quote }} + {{- end }} + {{- if .Values.extraEnvVarsCM }} + envFrom: + - configMapRef: + name: {{ tpl .Values.extraEnvVarsCM . }} + {{- end }} + ports: + - name: tcp-postgresql + containerPort: {{ .Values.containerPorts.postgresql }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- end }} + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + successThreshold: {{ .Values.startupProbe.successThreshold }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + {{- else if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + exec: + command: + - /bin/sh + - -c + {{- if (include "postgresql.database" .) }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} -d "dbname={{ include "postgresql.database" . }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}{{- end }}" -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- else }} + - exec pg_isready -U {{ include "postgresql.username" . | quote }} {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} -d "sslcert={{ include "postgresql.tlsCert" . }} sslkey={{ include "postgresql.tlsCertKey" . }}"{{- end }} -h 127.0.0.1 -p {{ .Values.containerPorts.postgresql }} + {{- end }} + 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: + - /bin/sh + - -c + - -e + {{- include "postgresql.readinessProbeCommand" . | nindent 16 }} + 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 }} + {{- end }} + volumeMounts: + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d/ + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + mountPath: /docker-entrypoint-initdb.d/secret + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + mountPath: /bitnami/postgresql/conf/conf.d/ + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + mountPath: /dev/shm + {{- end }} + {{- if .Values.persistence.enabled }} + - name: data + mountPath: {{ .Values.persistence.mountPath }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap }} + - name: postgresql-config + mountPath: /bitnami/postgresql/conf + {{- end }} + {{- if .Values.primary.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} +{{- if .Values.primary.sidecars }} +{{- include "common.tplvalues.render" ( dict "value" .Values.primary.sidecars "context" $ ) | nindent 8 }} +{{- end }} +{{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "postgresql.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.securityContext.enabled }} + securityContext: {{- omit .Values.metrics.securityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + {{- $database := required "In order to enable metrics you need to specify a database (.Values.postgresqlDatabase or .Values.global.postgresql.postgresqlDatabase)" (include "postgresql.database" .) }} + {{- $sslmode := ternary "require" "disable" .Values.tls.enabled }} + {{- if and .Values.tls.enabled .Values.tls.certCAFilename }} + - name: DATA_SOURCE_NAME + value: {{ printf "host=127.0.0.1 port=%d user=%s sslmode=%s sslcert=%s sslkey=%s" (int (include "postgresql.servicePort" .)) (include "postgresql.username" .) $sslmode (include "postgresql.tlsCert" .) (include "postgresql.tlsCertKey" .) }} + {{- else }} + - name: DATA_SOURCE_URI + value: {{ printf "127.0.0.1:%d/%s?sslmode=%s" (int (include "postgresql.servicePort" .)) $database $sslmode }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: DATA_SOURCE_PASS_FILE + value: "/opt/bitnami/postgresql/secrets/postgresql-password" + {{- else }} + - name: DATA_SOURCE_PASS + valueFrom: + secretKeyRef: + name: {{ template "postgresql.secretName" . }} + key: postgresql-password + {{- end }} + - name: DATA_SOURCE_USER + value: {{ template "postgresql.username" . }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: {{ .Values.metrics.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.metrics.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.metrics.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.metrics.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.metrics.readinessProbe.failureThreshold }} + {{- end }} + {{- end }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: postgresql-password + mountPath: /opt/bitnami/postgresql/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: postgresql-certificates + mountPath: /opt/bitnami/postgresql/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.customMetrics }} + - name: custom-metrics + mountPath: /conf + readOnly: true + args: ["--extend.query-path", "/conf/custom-metrics.yaml"] + {{- end }} + ports: + - name: http-metrics + containerPort: 9187 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} +{{- end }} + volumes: + {{- if or (.Files.Glob "files/postgresql.conf") (.Files.Glob "files/pg_hba.conf") .Values.postgresqlConfiguration .Values.pgHbaConfiguration .Values.configurationConfigMap}} + - name: postgresql-config + configMap: + name: {{ template "postgresql.configurationCM" . }} + {{- end }} + {{- if or (.Files.Glob "files/conf.d/*.conf") .Values.postgresqlExtendedConf .Values.extendedConfConfigMap }} + - name: postgresql-extended-config + configMap: + name: {{ template "postgresql.extendedConfigurationCM" . }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: postgresql-password + secret: + secretName: {{ template "postgresql.secretName" . }} + {{- end }} + {{- if or (.Files.Glob "files/docker-entrypoint-initdb.d/*.{sh,sql,sql.gz}") .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ template "postgresql.initdbScriptsCM" . }} + {{- end }} + {{- if .Values.initdbScriptsSecret }} + - name: custom-init-scripts-secret + secret: + secretName: {{ template "postgresql.initdbScriptsSecret" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: raw-certificates + secret: + secretName: {{ template "postgresql.tlsSecretName" . }} + - name: postgresql-certificates + emptyDir: {} + {{- end }} + {{- if .Values.primary.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.primary.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.customMetrics }} + - name: custom-metrics + configMap: + name: {{ template "postgresql.metricsCM" . }} + {{- end }} + {{- if .Values.shmVolume.enabled }} + - name: dshm + emptyDir: + medium: Memory +{{- with .Values.shmVolume.sizeLimit }} + sizeLimit: {{ . }} +{{- end }} + {{- end }} +{{- if and .Values.persistence.enabled .Values.persistence.existingClaim }} + - name: data + persistentVolumeClaim: +{{- with .Values.persistence.existingClaim }} + claimName: {{ tpl . $ }} +{{- end }} +{{- else if not .Values.persistence.enabled }} + - name: data + emptyDir: {} + {{- if .Values.primary.extraPodSpec }} + {{- include "common.tplvalues.render" (dict "value" .Values.primary.extraPodSpec "context" $) | nindent 6 }} + {{- end }} +{{- else if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + 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) }} + {{- if .Values.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.persistence.selector "context" $) | nindent 10 }} + {{- end -}} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/svc-headless.yaml b/rds/base/charts/postgresql/templates/svc-headless.yaml new file mode 100644 index 0000000..fbbfd40 --- /dev/null +++ b/rds/base/charts/postgresql/templates/svc-headless.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-headless + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + # Use this annotation in addition to the actual publishNotReadyAddresses + # field below because the annotation will stop being respected soon but the + # field is broken in some versions of Kubernetes: + # https://github.com/kubernetes/kubernetes/issues/58662 + service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" + namespace: {{ .Release.Namespace }} +spec: + type: ClusterIP + clusterIP: None + # We want all pods in the StatefulSet to have their addresses published for + # the sake of the other Postgresql pods even before they're ready, since they + # have to be able to talk to each other in order to become ready. + publishNotReadyAddresses: true + ports: + - name: tcp-postgresql + port: {{ template "postgresql.servicePort" . }} + targetPort: tcp-postgresql + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/rds/base/charts/postgresql/templates/svc-read-set.yaml b/rds/base/charts/postgresql/templates/svc-read-set.yaml new file mode 100644 index 0000000..1808bd1 --- /dev/null +++ b/rds/base/charts/postgresql/templates/svc-read-set.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.replication.enabled .Values.replication.uniqueServices }} +{{- $serviceAnnotations := coalesce .Values.readReplicas.service.annotations .Values.service.annotations -}} + +{{- $fullName := include "common.names.fullname" . }} +{{- $replicaCount := .Values.replication.readReplicas | int }} +{{- $root := . }} + +{{- range $i, $e := until $replicaCount }} +{{- $targetPod := printf "%s-read-%d" (printf "%s" $fullName) $i }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }}-read-{{ $i }} + namespace: {{ .Release.Namespace }} + labels: + pod: {{ $targetPod }} + {{- include "common.labels.standard" $root | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + + {{- if $root.Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ $root.Release.Namespace }} +spec: + type: ClusterIP + ports: + - name: tcp-postgresql + port: {{ template "postgresql.servicePort" $root }} + targetPort: tcp-postgresql + selector: + {{- include "common.labels.matchLabels" $root | nindent 4 }} + role: read + statefulset.kubernetes.io/pod-name: {{ $targetPod }} + +{{- end }} +{{- end }} diff --git a/rds/base/charts/postgresql/templates/svc-read.yaml b/rds/base/charts/postgresql/templates/svc-read.yaml new file mode 100644 index 0000000..ed1005f --- /dev/null +++ b/rds/base/charts/postgresql/templates/svc-read.yaml @@ -0,0 +1,47 @@ +{{- if and .Values.replication.enabled .Values.replication.singleService }} +{{- $serviceAnnotations := coalesce .Values.readReplicas.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.readReplicas.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.readReplicas.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.readReplicas.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.readReplicas.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.readReplicas.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-read + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.servicePort" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: read +{{- end }} diff --git a/rds/base/charts/postgresql/templates/svc.yaml b/rds/base/charts/postgresql/templates/svc.yaml new file mode 100644 index 0000000..a47efb9 --- /dev/null +++ b/rds/base/charts/postgresql/templates/svc.yaml @@ -0,0 +1,45 @@ +{{- $serviceAnnotations := coalesce .Values.primary.service.annotations .Values.service.annotations -}} +{{- $serviceType := coalesce .Values.primary.service.type .Values.service.type -}} +{{- $serviceLoadBalancerIP := coalesce .Values.primary.service.loadBalancerIP .Values.service.loadBalancerIP -}} +{{- $serviceLoadBalancerSourceRanges := coalesce .Values.primary.service.loadBalancerSourceRanges .Values.service.loadBalancerSourceRanges -}} +{{- $serviceClusterIP := coalesce .Values.primary.service.clusterIP .Values.service.clusterIP -}} +{{- $serviceNodePort := coalesce .Values.primary.service.nodePort .Values.service.nodePort -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + labels: + {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $serviceAnnotations }} + {{- include "common.tplvalues.render" (dict "value" $serviceAnnotations "context" $) | nindent 4 }} + {{- end }} + namespace: {{ .Release.Namespace }} +spec: + type: {{ $serviceType }} + {{- if and $serviceLoadBalancerIP (eq $serviceType "LoadBalancer") }} + loadBalancerIP: {{ $serviceLoadBalancerIP }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq $serviceType "LoadBalancer") $serviceLoadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- include "common.tplvalues.render" (dict "value" $serviceLoadBalancerSourceRanges "context" $) | nindent 4 }} + {{- end }} + {{- if and (eq $serviceType "ClusterIP") $serviceClusterIP }} + clusterIP: {{ $serviceClusterIP }} + {{- end }} + ports: + - name: tcp-postgresql + port: {{ template "postgresql.servicePort" . }} + targetPort: tcp-postgresql + {{- if $serviceNodePort }} + nodePort: {{ $serviceNodePort }} + {{- end }} + selector: + {{- include "common.labels.matchLabels" . | nindent 4 }} + role: primary diff --git a/rds/base/charts/postgresql/templates/tls-secrets.yaml b/rds/base/charts/postgresql/templates/tls-secrets.yaml new file mode 100644 index 0000000..c1e9ef2 --- /dev/null +++ b/rds/base/charts/postgresql/templates/tls-secrets.yaml @@ -0,0 +1,26 @@ +{{- if (include "postgresql.createTlsSecret" . )}} +{{- $ca := genCA "postgresql-ca" 365 }} +{{- $fullname := include "common.names.fullname" . }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $headlessServiceName := printf "%s-headless" (include "common.names.fullname" .) }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $fullname $releaseNamespace $clusterDomain) (printf "*.%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-crt" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} diff --git a/rds/base/charts/postgresql/values.schema.json b/rds/base/charts/postgresql/values.schema.json new file mode 100644 index 0000000..66a2a9d --- /dev/null +++ b/rds/base/charts/postgresql/values.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "postgresqlUsername": { + "type": "string", + "title": "Admin user", + "form": true + }, + "postgresqlPassword": { + "type": "string", + "title": "Password", + "form": true + }, + "persistence": { + "type": "object", + "properties": { + "size": { + "type": "string", + "title": "Persistent Volume Size", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderUnit": "Gi" + } + } + }, + "resources": { + "type": "object", + "title": "Required Resources", + "description": "Configure resource requests", + "form": true, + "properties": { + "requests": { + "type": "object", + "properties": { + "memory": { + "type": "string", + "form": true, + "render": "slider", + "title": "Memory Request", + "sliderMin": 10, + "sliderMax": 2048, + "sliderUnit": "Mi" + }, + "cpu": { + "type": "string", + "form": true, + "render": "slider", + "title": "CPU Request", + "sliderMin": 10, + "sliderMax": 2000, + "sliderUnit": "m" + } + } + } + } + }, + "replication": { + "type": "object", + "form": true, + "title": "Replication Details", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable Replication", + "form": true + }, + "readReplicas": { + "type": "integer", + "title": "read Replicas", + "form": true, + "hidden": { + "value": false, + "path": "replication/enabled" + } + } + } + }, + "volumePermissions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Enable Init Containers", + "description": "Change the owner of the persist volume mountpoint to RunAsUser:fsGroup" + } + } + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "title": "Configure metrics exporter", + "form": true + } + } + } + } +} diff --git a/rds/base/charts/postgresql/values.yaml b/rds/base/charts/postgresql/values.yaml new file mode 100644 index 0000000..b6f6d5e --- /dev/null +++ b/rds/base/charts/postgresql/values.yaml @@ -0,0 +1,996 @@ +## @section Global parameters +## 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, imagePullSecrets and storageClass +## + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + ## @param global.postgresql.postgresqlDatabase PostgreSQL database (overrides `postgresqlDatabase`) + ## @param global.postgresql.postgresqlUsername PostgreSQL username (overrides `postgresqlUsername`) + ## @param global.postgresql.existingSecret Name of existing secret to use for PostgreSQL passwords (overrides `existingSecret`) + ## @param global.postgresql.postgresqlPassword PostgreSQL admin password (overrides `postgresqlPassword`) + ## @param global.postgresql.servicePort PostgreSQL port (overrides `service.port` + ## @param global.postgresql.replicationPassword Replication user password (overrides `replication.password`) + ## + postgresql: + postgresqlDatabase: "" + postgresqlUsername: "" + existingSecret: "" + postgresqlPassword: "" + servicePort: "" + replicationPassword: "" + +## @section Common parameters +## + +## @param nameOverride String to partially override common.names.fullname template (will maintain the release name) +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname template +## +fullnameOverride: "" +## @param extraDeploy Array of extra objects to deploy with the release (evaluated as a template) +## +extraDeploy: [] +## @param commonLabels Add labels to all the deployed resources +## +commonLabels: {} +## @param commonAnnotations Add annotations to all the deployed resources +## +commonAnnotations: {} + +## Enable diagnostic mode in the deployment +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the deployment + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the deployment + ## + args: + - infinity + +## @section PostgreSQL parameters +## + +## Bitnami PostgreSQL image version +## ref: https://hub.docker.com/r/bitnami/postgresql/tags/ +## @param image.registry PostgreSQL image registry +## @param image.repository PostgreSQL image repository +## @param image.tag PostgreSQL image tag (immutable tags are recommended) +## @param image.pullPolicy PostgreSQL image pull policy +## @param image.pullSecrets Specify image pull secrets +## @param image.debug Specify if debug values should be set +## +image: + registry: docker.io + repository: bitnami/postgresql + tag: 11.14.0-debian-10-r21 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://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: [] + ## Set to true if you would like to see extra information on logs + ## It turns BASH and/or NAMI debugging in the image + ## + debug: false +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes volume permissions in the data directory (for cases where the default k8s `runAsUser` and `fsUser` values do not work) + ## + enabled: false + ## @param volumePermissions.image.registry Init container volume-permissions image registry + ## @param volumePermissions.image.repository Init container volume-permissions image repository + ## @param volumePermissions.image.tag Init container volume-permissions image tag (immutable tags are recommended) + ## @param volumePermissions.image.pullPolicy Init container volume-permissions image pull policy + ## @param volumePermissions.image.pullSecrets Init container volume-permissions image pull secrets + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 10-debian-10-r299 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://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 Security Context + ## @param volumePermissions.securityContext.runAsUser User ID for the init container + ## Note: the chown of the data folder is done to securityContext.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 + ## pod securityContext.enabled=false and shmVolume.chmod.enabled=false + ## + securityContext: + runAsUser: 0 +## @param schedulerName Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" +## @param lifecycleHooks for the PostgreSQL container to automate configuration before or after startup +## +lifecycleHooks: {} +## Pod Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## @param securityContext.enabled Enable security context +## @param securityContext.fsGroup Group ID for the pod +## +securityContext: + enabled: true + fsGroup: 1001 +## Container Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +## @param containerSecurityContext.enabled Enable container security context +## @param containerSecurityContext.runAsUser User ID for the container +## +containerSecurityContext: + enabled: true + runAsUser: 1001 +## Pod Service Account +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ +## +serviceAccount: + ## @param serviceAccount.enabled Enable service account (Note: Service Account will only be automatically created if `serviceAccount.name` is not set) + ## + enabled: false + ## @param serviceAccount.name Name of an already existing service account. Setting this value disables the automatic service account creation + ## + name: "" + ## @param serviceAccount.autoMount Auto-mount the service account token in the pod + ## + autoMount: false +## Pod Security Policy +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## @param psp.create Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later +## +psp: + create: false +## Creates role for ServiceAccount +## Required for PSP +## @param rbac.create Create Role and RoleBinding (required for PSP to work) +## +rbac: + create: false +## @param replication.enabled Enable replication +## @param replication.user Replication user +## @param replication.password Replication user password +## @param replication.readReplicas Number of read replicas replicas +## @param replication.synchronousCommit Set synchronous commit mode. Allowed values: `on`, `remote_apply`, `remote_write`, `local` and `off` +## @param replication.numSynchronousReplicas Number of replicas that will have synchronous replication. Note: Cannot be greater than `replication.readReplicas`. +## @param replication.applicationName Cluster application name. Useful for advanced replication settings +## @param replication.singleService Create one service connecting to all read-replicas +## @param replication.uniqueServices Create a unique service for each independent read-replica +## +replication: + enabled: false + user: repl_user + password: repl_password + readReplicas: 1 + ## ref: https://www.postgresql.org/docs/9.6/runtime-config-wal.html#GUC-WAL-LEVEL + ## + synchronousCommit: "off" + ## NOTE: It cannot be > readReplicas + ## + numSynchronousReplicas: 0 + applicationName: my_application + singleService: true + uniqueServices: false +## @param postgresqlPostgresPassword PostgreSQL admin password (used when `postgresqlUsername` is not `postgres`, in which case`postgres` is the admin username) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-user-on-first-run (see note!) +## +postgresqlPostgresPassword: "" +## @param postgresqlUsername PostgreSQL user (has superuser privileges if username is `postgres`) +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +postgresqlUsername: postgres +## @param postgresqlPassword PostgreSQL user password +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#setting-the-root-password-on-first-run +## +postgresqlPassword: "" +## @param existingSecret Name of existing secret to use for PostgreSQL passwords +## The secret has to contain the keys postgresql-password which is the password for postgresqlUsername when it is +## different of postgres, postgresql-postgres-password which will override postgresqlPassword, +## postgresql-replication-password which will override replication.password and postgresql-ldap-password which will be +## used to authenticate on LDAP. The value is evaluated as a template. +## e.g: +## existingSecret: secret +## +existingSecret: "" +## @param usePasswordFile Mount PostgreSQL secret as a file instead of passing environment variable +## +usePasswordFile: false +## @param postgresqlDatabase PostgreSQL database +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md#creating-a-database-on-first-run +## +postgresqlDatabase: "" +## @param postgresqlDataDir PostgreSQL data dir folder +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +postgresqlDataDir: /bitnami/postgresql/data +## @param extraEnv An array to add extra environment variables +## For example: +## extraEnv: +## - name: FOO +## value: "bar" +## +extraEnv: [] +## @param extraEnvVarsCM Name of a Config Map containing extra environment variables +## +extraEnvVarsCM: "" +## @param postgresqlInitdbArgs PostgreSQL initdb extra arguments +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +postgresqlInitdbArgs: "" +## @param postgresqlInitdbWalDir Specify a custom location for the PostgreSQL transaction log +## ref: https://github.com/bitnami/bitnami-docker-postgresql/blob/master/README.md +## +postgresqlInitdbWalDir: "" +## @param postgresqlConfiguration PostgreSQL configuration +## Specify runtime configuration parameters as a dict, using camelCase, e.g. +## {"sharedBuffers": "500MB"} +## Alternatively, you can put your postgresql.conf under the files/ directory +## ref: https://www.postgresql.org/docs/current/static/runtime-config.html +## +postgresqlConfiguration: {} +## @param postgresqlExtendedConf Extended Runtime Config Parameters (appended to main or default configuration) +## Alternatively, you can put your *.conf under the files/conf.d/ directory +## https://github.com/bitnami/bitnami-docker-postgresql#allow-settings-to-be-loaded-from-files-other-than-the-default-postgresqlconf +## +postgresqlExtendedConf: {} +## Configure current cluster's primary server to be the standby server in other cluster. +## This will allow cross cluster replication and provide cross cluster high availability. +## You will need to configure pgHbaConfiguration if you want to enable this feature with local cluster replication enabled. +## @param primaryAsStandBy.enabled Whether to enable current cluster's primary as standby server of another cluster or not +## @param primaryAsStandBy.primaryHost The Host of replication primary in the other cluster +## @param primaryAsStandBy.primaryPort The Port of replication primary in the other cluster +## +primaryAsStandBy: + enabled: false + primaryHost: "" + primaryPort: "" +## @param pgHbaConfiguration PostgreSQL client authentication configuration +## Specify content for pg_hba.conf +## Default: do not create pg_hba.conf +## Alternatively, you can put your pg_hba.conf under the files/ directory +## pgHbaConfiguration: |- +## local all all trust +## host all all localhost trust +## host mydatabase mysuser 192.168.0.0/24 md5 +## +pgHbaConfiguration: "" +## @param configurationConfigMap ConfigMap with PostgreSQL configuration +## NOTE: This will override postgresqlConfiguration and pgHbaConfiguration +## +configurationConfigMap: "" +## @param extendedConfConfigMap ConfigMap with PostgreSQL extended configuration +## +extendedConfConfigMap: "" +## @param initdbScripts Dictionary of initdb scripts +## Specify dictionary of scripts to be run at first boot +## Alternatively, you can put your scripts under the files/docker-entrypoint-initdb.d directory +## e.g: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/sh +## echo "Do something." +## +initdbScripts: {} +## @param initdbScriptsConfigMap ConfigMap with scripts to be run at first boot +## NOTE: This will override initdbScripts +## +initdbScriptsConfigMap: "" +## @param initdbScriptsSecret Secret with scripts to be run at first boot (in case it contains sensitive information) +## NOTE: This can work along initdbScripts or initdbScriptsConfigMap +## +initdbScriptsSecret: "" +## @param initdbUser Specify the PostgreSQL username to execute the initdb scripts +## +initdbUser: "" +## @param initdbPassword Specify the PostgreSQL password to execute the initdb scripts +## +initdbPassword: "" + +## @param containerPorts.postgresql PostgreSQL container port +## +containerPorts: + postgresql: 5432 +## Audit settings +## https://github.com/bitnami/bitnami-docker-postgresql#auditing +## +audit: + ## @param audit.logHostname Log client hostnames + ## + logHostname: false + ## @param audit.logConnections Add client log-in operations to the log file + ## + logConnections: false + ## @param audit.logDisconnections Add client log-outs operations to the log file + ## + logDisconnections: false + ## @param audit.pgAuditLog Add operations to log using the pgAudit extension + ## + pgAuditLog: "" + ## @param audit.pgAuditLogCatalog Log catalog using pgAudit + ## + pgAuditLogCatalog: "off" + ## @param audit.clientMinMessages Message log level to share with the user + ## + clientMinMessages: error + ## @param audit.logLinePrefix Template for log line prefix (default if not set) + ## + logLinePrefix: "" + ## @param audit.logTimezone Timezone for the log timestamps + ## + logTimezone: "" +## @param postgresqlSharedPreloadLibraries Shared preload libraries (comma-separated list) +## +postgresqlSharedPreloadLibraries: "pgaudit" +## @param postgresqlMaxConnections Maximum total connections +## +postgresqlMaxConnections: "" +## @param postgresqlPostgresConnectionLimit Maximum connections for the postgres user +## +postgresqlPostgresConnectionLimit: "" +## @param postgresqlDbUserConnectionLimit Maximum connections for the non-admin user +## +postgresqlDbUserConnectionLimit: "" +## @param postgresqlTcpKeepalivesInterval TCP keepalives interval +## +postgresqlTcpKeepalivesInterval: "" +## @param postgresqlTcpKeepalivesIdle TCP keepalives idle +## +postgresqlTcpKeepalivesIdle: "" +## @param postgresqlTcpKeepalivesCount TCP keepalives count +## +postgresqlTcpKeepalivesCount: "" +## @param postgresqlStatementTimeout Statement timeout +## +postgresqlStatementTimeout: "" +## @param postgresqlPghbaRemoveFilters Comma-separated list of patterns to remove from the pg_hba.conf file +## Cannot be used with custom pg_hba.conf +## +postgresqlPghbaRemoveFilters: "" +## @param terminationGracePeriodSeconds Seconds the pod needs to terminate gracefully +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods +## e.g: +## terminationGracePeriodSeconds: 30 +## +terminationGracePeriodSeconds: "" +## LDAP configuration +## @param ldap.enabled Enable LDAP support +## @param ldap.url LDAP URL beginning in the form `ldap[s]://host[:port]/basedn` +## @param ldap.server IP address or name of the LDAP server. +## @param ldap.port Port number on the LDAP server to connect to +## @param ldap.prefix String to prepend to the user name when forming the DN to bind +## @param ldap.suffix String to append to the user name when forming the DN to bind +## @param ldap.baseDN Root DN to begin the search for the user in +## @param ldap.bindDN DN of user to bind to LDAP +## @param ldap.bind_password Password for the user to bind to LDAP +## @param ldap.search_attr Attribute to match against the user name in the search +## @param ldap.search_filter The search filter to use when doing search+bind authentication +## @param ldap.scheme Set to `ldaps` to use LDAPS +## @param ldap.tls Set to `1` to use TLS encryption +## +ldap: + enabled: false + url: "" + server: "" + port: "" + prefix: "" + suffix: "" + baseDN: "" + bindDN: "" + bind_password: "" + search_attr: "" + search_filter: "" + scheme: "" + tls: "" +## PostgreSQL service configuration +## +service: + ## @param service.type Kubernetes Service type + ## + type: ClusterIP + ## @param service.clusterIP Static clusterIP or None for headless services + ## e.g: + ## clusterIP: None + ## + clusterIP: "" + ## @param service.port PostgreSQL port + ## + port: 5432 + ## @param service.nodePort Specify the nodePort value for the LoadBalancer and NodePort service types + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePort: "" + ## @param service.annotations Annotations for PostgreSQL service + ## + annotations: {} + ## @param service.loadBalancerIP Load balancer IP if service type is `LoadBalancer` + ## Set the LoadBalancer service type to internal only + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param service.externalTrafficPolicy Enable client source IP preservation + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.loadBalancerSourceRanges Addresses that are allowed when service is LoadBalancer + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] +## Start primary and read(s) pod(s) without limitations on shm memory. +## By default docker and containerd (and possibly other container runtimes) +## limit `/dev/shm` to `64M` (see e.g. the +## [docker issue](https://github.com/docker-library/postgres/issues/416) and the +## [containerd issue](https://github.com/containerd/containerd/issues/3654), +## which could be not enough if PostgreSQL uses parallel workers heavily. +## +shmVolume: + ## @param shmVolume.enabled Enable emptyDir volume for /dev/shm for primary and read replica(s) Pod(s) + ## Set `shmVolume.enabled` to `true` to mount a new tmpfs volume to remove the above limitation. + ## + enabled: true + ## @param shmVolume.chmod.enabled Set to `true` to `chmod 777 /dev/shm` on a initContainer (ignored if `volumePermissions.enabled` is `false`) + ## + chmod: + enabled: true + ## @param shmVolume.sizeLimit Set this to enable a size limit on the shm tmpfs. Note that the size of the tmpfs counts against container's memory limit + ## e.g: + ## sizeLimit: 1Gi + ## + sizeLimit: "" +persistence: + ## @param persistence.enabled Enable persistence using PVC + ## + enabled: true + ## @param persistence.existingClaim Provide an existing `PersistentVolumeClaim`, the value is evaluated as a template. + ## If defined, PVC must be created manually before volume will be bound + ## The value is evaluated as a template, so, for example, the name can depend on .Release or .Chart + ## + existingClaim: "" + ## @param persistence.mountPath The path the volume will be mounted at, useful when using different + ## PostgreSQL images. + ## + mountPath: /bitnami/postgresql + ## @param persistence.subPath The subdirectory of the volume to mount to + ## Useful in dev environments and one PV for multiple services + ## + subPath: "" + ## @param persistence.storageClass PVC Storage Class for PostgreSQL volume + ## 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: "" + ## @param persistence.accessModes PVC Access Mode for PostgreSQL volume + ## + accessModes: + - ReadWriteOnce + ## @param persistence.size PVC Storage Request for PostgreSQL volume + ## + size: 8Gi + ## @param persistence.annotations Annotations for the PVC + ## + annotations: {} + ## @param persistence.selector Selector to match an existing Persistent Volume (this value is evaluated as a template) + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} +## @param updateStrategy.type updateStrategy for PostgreSQL StatefulSet and its reads StatefulSets +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate +## +## PostgreSQL Primary parameters +## +primary: + ## @param primary.podAffinityPreset PostgreSQL primary pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param primary.podAntiAffinityPreset PostgreSQL primary pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## PostgreSQL Primary node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param primary.nodeAffinityPreset.type PostgreSQL primary node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param primary.nodeAffinityPreset.key PostgreSQL primary node label key to match Ignored if `primary.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param primary.nodeAffinityPreset.values PostgreSQL primary node label values to match. Ignored if `primary.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param primary.affinity Affinity for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: primary.podAffinityPreset, primary.podAntiAffinityPreset, and primary.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param primary.nodeSelector Node labels for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param primary.tolerations Tolerations for PostgreSQL primary pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param primary.extraPodSpec Optionally specify extra PodSpec + ## + extraPodSpec: {} + ## @param primary.labels Map of labels to add to the statefulset (postgresql primary) + ## + labels: {} + ## @param primary.annotations Annotations for PostgreSQL primary pods + ## + annotations: {} + ## @param primary.podLabels Map of labels to add to the pods (postgresql primary) + ## + podLabels: {} + ## @param primary.podAnnotations Map of annotations to add to the pods (postgresql primary) + ## + podAnnotations: {} + ## @param primary.priorityClassName Priority Class to use for each pod (postgresql primary) + ## + priorityClassName: "" + ## @param primary.extraInitContainers Extra init containers to add to the pods (postgresql primary) + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + ## @param primary.extraVolumeMounts Extra volume mounts to add to the pods (postgresql primary) + ## + extraVolumeMounts: [] + ## @param primary.extraVolumes Extra volumes to add to the pods (postgresql primary) + ## + extraVolumes: [] + ## @param primary.sidecars Extra containers to the pod + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## Override the service configuration for primary + ## @param primary.service.type Allows using a different service type for primary + ## @param primary.service.nodePort Allows using a different nodePort for primary + ## @param primary.service.clusterIP Allows using a different clusterIP for primary + ## + service: + type: "" + nodePort: "" + clusterIP: "" +## PostgreSQL read only replica parameters +## +readReplicas: + ## @param readReplicas.podAffinityPreset PostgreSQL read only pod affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param readReplicas.podAntiAffinityPreset PostgreSQL read only pod anti-affinity preset. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## PostgreSQL read only node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param readReplicas.nodeAffinityPreset.type PostgreSQL read only node affinity preset type. Ignored if `primary.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param readReplicas.nodeAffinityPreset.key PostgreSQL read only node label key to match Ignored if `primary.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param readReplicas.nodeAffinityPreset.values PostgreSQL read only node label values to match. Ignored if `primary.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param readReplicas.affinity Affinity for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: readReplicas.podAffinityPreset, readReplicas.podAntiAffinityPreset, and readReplicas.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param readReplicas.nodeSelector Node labels for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param readReplicas.tolerations Tolerations for PostgreSQL read only pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param readReplicas.topologySpreadConstraints Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template + ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/#spread-constraints-for-pods + ## + topologySpreadConstraints: [] + ## @param readReplicas.extraPodSpec Optionally specify extra PodSpec + ## + extraPodSpec: {} + ## @param readReplicas.labels Map of labels to add to the statefulsets (postgresql readReplicas) + ## + labels: {} + ## @param readReplicas.annotations Annotations for PostgreSQL read only pods + ## + annotations: {} + ## @param readReplicas.podLabels Map of labels to add to the pods (postgresql readReplicas) + ## + podLabels: {} + ## @param readReplicas.podAnnotations Map of annotations to add to the pods (postgresql readReplicas) + ## + podAnnotations: {} + ## @param readReplicas.priorityClassName Priority Class to use for each pod (postgresql readReplicas) + ## + priorityClassName: "" + ## @param readReplicas.extraInitContainers Extra init containers to add to the pods (postgresql readReplicas) + ## Example + ## + ## extraInitContainers: + ## - name: do-something + ## image: busybox + ## command: ['do', 'something'] + ## + extraInitContainers: [] + ## @param readReplicas.extraVolumeMounts Extra volume mounts to add to the pods (postgresql readReplicas) + ## + extraVolumeMounts: [] + ## @param readReplicas.extraVolumes Extra volumes to add to the pods (postgresql readReplicas) + ## + extraVolumes: [] + ## @param readReplicas.sidecars Extra containers to the pod + ## + ## For example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## Override the service configuration for read + ## @param readReplicas.service.type Allows using a different service type for readReplicas + ## @param readReplicas.service.nodePort Allows using a different nodePort for readReplicas + ## @param readReplicas.service.clusterIP Allows using a different clusterIP for readReplicas + ## + service: + type: "" + nodePort: "" + clusterIP: "" + ## @param readReplicas.persistence.enabled Whether to enable PostgreSQL read replicas replicas persistence + ## + persistence: + enabled: true + ## @param readReplicas.resources CPU/Memory resource requests/limits override for readReplicass. Will fallback to `values.resources` if not defined. + ## + resources: {} +## Configure resource requests and limits +## ref: https://kubernetes.io/docs/user-guide/compute-resources/ +## @param resources.requests [object] The requested resources for the container +## +resources: + requests: + memory: 256Mi + cpu: 250m +networkPolicy: + ## @param networkPolicy.enabled Enable creation of NetworkPolicy resources. Only Ingress traffic is filtered for now. + ## + enabled: false + ## @param networkPolicy.allowExternal Don't require client label for connections + ## The Policy model to apply. When set to false, only pods with the correct + ## client label will have network access to the port PostgreSQL is listening + ## on. When true, PostgreSQL will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.explicitNamespacesSelector A Kubernetes LabelSelector to explicitly select namespaces from which ingress traffic could be allowed + ## If explicitNamespacesSelector is missing or set to {}, only client Pods that are in the networkPolicy's namespace + ## and that match other criteria, the ones that have the good label, can reach the DB. + ## But sometimes, we want the DB to be accessible to clients from other namespaces, in this case, we can use this + ## LabelSelector to select these namespaces, note that the networkPolicy's namespace should also be explicitly added. + ## + ## Example: + ## explicitNamespacesSelector: + ## matchLabels: + ## role: frontend + ## matchExpressions: + ## - {key: role, operator: In, values: [frontend]} + ## + explicitNamespacesSelector: {} +## Configure extra options for liveness probe +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param startupProbe.enabled Enable startupProbe +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 10 + successThreshold: 1 +## Configure extra options for liveness probe +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe +## +livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +## Configure extra options for readiness probe +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes +## @param readinessProbe.enabled Enable readinessProbe +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 +## @param customStartupProbe Override default startup probe +## +customStartupProbe: {} +## @param customLivenessProbe Override default liveness probe +## +customLivenessProbe: {} +## @param customReadinessProbe Override default readiness probe +## +customReadinessProbe: {} +## +## TLS configuration +## +tls: + ## @param tls.enabled Enable TLS traffic support + ## + enabled: false + ## @param tls.autoGenerated Generate automatically self-signed TLS certificates + ## + autoGenerated: false + ## @param tls.preferServerCiphers Whether to use the server's TLS cipher preferences rather than the client's + ## + preferServerCiphers: true + ## @param tls.certificatesSecret Name of an existing secret that contains the certificates + ## + certificatesSecret: "" + ## @param tls.certFilename Certificate filename + ## + certFilename: "" + ## @param tls.certKeyFilename Certificate key filename + ## + certKeyFilename: "" + ## @param tls.certCAFilename CA Certificate filename + ## If provided, PostgreSQL will authenticate TLS/SSL clients by requesting them a certificate + ## ref: https://www.postgresql.org/docs/9.6/auth-methods.html + ## + certCAFilename: "" + ## @param tls.crlFilename File containing a Certificate Revocation List + ## + crlFilename: "" +## Configure metrics exporter +## +metrics: + ## @param metrics.enabled Start a prometheus exporter + ## + enabled: false + ## @param metrics.resources Prometheus exporter container resources + ## + resources: {} + ## @param metrics.service.type Kubernetes Service type + ## @param metrics.service.annotations [object] Additional annotations for metrics exporter pod + ## @param metrics.service.loadBalancerIP loadBalancerIP if redis metrics service type is `LoadBalancer` + ## + service: + type: ClusterIP + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9187" + loadBalancerIP: "" + ## @param metrics.serviceMonitor.enabled Set this to `true` to create ServiceMonitor for Prometheus operator + ## @param metrics.serviceMonitor.additionalLabels Additional labels that can be used so ServiceMonitor will be discovered by Prometheus + ## @param metrics.serviceMonitor.namespace Optional namespace in which to create ServiceMonitor + ## @param metrics.serviceMonitor.interval Scrape interval. If not set, the Prometheus default scrape interval is used + ## @param metrics.serviceMonitor.scrapeTimeout Scrape timeout. If not set, the Prometheus default scrape timeout is used + ## @param metrics.serviceMonitor.relabelings RelabelConfigs to apply to samples before scraping + ## @param metrics.serviceMonitor.metricRelabelings MetricRelabelConfigs to apply to samples before ingestion + ## + serviceMonitor: + enabled: false + additionalLabels: {} + namespace: "" + interval: "" + scrapeTimeout: "" + relabelings: [] + metricRelabelings: [] + ## 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: + ## @param metrics.prometheusRule.enabled Set this to true to create prometheusRules for Prometheus operator + ## + enabled: false + ## @param metrics.prometheusRule.additionalLabels Additional labels that can be used so prometheusRules will be discovered by Prometheus + ## + additionalLabels: {} + ## @param metrics.prometheusRule.namespace namespace where prometheusRules resource should be created + ## + namespace: "" + ## @param metrics.prometheusRule.rules Create specified [Rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/) + ## Make sure to constraint the rules to the current postgresql service. + ## rules: + ## - alert: HugeReplicationLag + ## expr: pg_replication_lag{service="{{ template "common.names.fullname" . }}-metrics"} / 3600 > 1 + ## for: 1m + ## labels: + ## severity: critical + ## annotations: + ## description: replication for {{ template "common.names.fullname" . }} PostgreSQL is lagging by {{ "{{ $value }}" }} hour(s). + ## summary: PostgreSQL replication is lagging by {{ "{{ $value }}" }} hour(s). + ## + rules: [] + ## @param metrics.image.registry PostgreSQL Exporter image registry + ## @param metrics.image.repository PostgreSQL Exporter image repository + ## @param metrics.image.tag PostgreSQL Exporter image tag (immutable tags are recommended) + ## @param metrics.image.pullPolicy PostgreSQL Exporter image pull policy + ## @param metrics.image.pullSecrets Specify image pull secrets + ## + image: + registry: docker.io + repository: bitnami/postgres-exporter + tag: 0.10.0-debian-10-r166 + 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: [] + ## @param metrics.customMetrics Define additional custom metrics + ## ref: https://github.com/wrouesnel/postgres_exporter#adding-new-metrics-via-a-config-file + ## customMetrics: + ## pg_database: + ## query: "SELECT d.datname AS name, CASE WHEN pg_catalog.has_database_privilege(d.datname, 'CONNECT') THEN pg_catalog.pg_database_size(d.datname) ELSE 0 END AS size_bytes FROM pg_catalog.pg_database d where datname not in ('template0', 'template1', 'postgres')" + ## metrics: + ## - name: + ## usage: "LABEL" + ## description: "Name of the database" + ## - size_bytes: + ## usage: "GAUGE" + ## description: "Size of the database in bytes" + ## + customMetrics: {} + ## @param metrics.extraEnvVars Extra environment variables to add to postgres-exporter + ## see: https://github.com/wrouesnel/postgres_exporter#environment-variables + ## For example: + ## extraEnvVars: + ## - name: PG_EXPORTER_DISABLE_DEFAULT_METRICS + ## value: "true" + ## + extraEnvVars: [] + ## Pod Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## @param metrics.securityContext.enabled Enable security context for metrics + ## @param metrics.securityContext.runAsUser User ID for the container for metrics + ## + securityContext: + enabled: false + runAsUser: 1001 + ## Configure extra options for liveness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param metrics.livenessProbe.enabled Enable livenessProbe + ## @param metrics.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param metrics.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param metrics.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param metrics.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param metrics.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + ## Configure extra options for readiness probe + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param metrics.readinessProbe.enabled Enable readinessProbe + ## @param metrics.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param metrics.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param metrics.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param metrics.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param metrics.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 diff --git a/rds/base/charts/redis-cluster/.helmignore b/rds/base/charts/redis-cluster/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/rds/base/charts/redis-cluster/.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/rds/base/charts/redis-cluster/Chart.lock b/rds/base/charts/redis-cluster/Chart.lock new file mode 100644 index 0000000..cfa9923 --- /dev/null +++ b/rds/base/charts/redis-cluster/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://charts/common + version: 1.16.0 +digest: sha256:8cbf195631894434137a5801072c21fbcff43915de298ab2664725c5492fa7d0 +generated: "2023-02-07T10:31:00.441718631+01:00" diff --git a/rds/base/charts/redis-cluster/Chart.yaml b/rds/base/charts/redis-cluster/Chart.yaml new file mode 100644 index 0000000..0eda7ff --- /dev/null +++ b/rds/base/charts/redis-cluster/Chart.yaml @@ -0,0 +1,28 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 6.2.7 +dependencies: +- name: common + repository: file://charts/common + tags: + - bitnami-common + alias: redis-cluster-common + version: 1.x.x +description: Redis(R) is an open source, scalable, distributed in-memory cache for + applications. It can be used to store and serve data in the form of strings, hashes, + lists, sets and sorted sets. +home: https://github.com/bitnami/charts/tree/master/bitnami/redis-cluster +icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png +keywords: +- redis +- keyvalue +- database +maintainers: +- name: Bitnami + url: https://github.com/bitnami/charts +name: redis-cluster +sources: +- https://github.com/bitnami/bitnami-docker-redis +- http://redis.io/ +version: 7.6.4 diff --git a/rds/base/charts/redis-cluster/README.md b/rds/base/charts/redis-cluster/README.md new file mode 100644 index 0000000..f4d7a8d --- /dev/null +++ b/rds/base/charts/redis-cluster/README.md @@ -0,0 +1,682 @@ + + +# Bitnami package for Redis(R) Cluster + +Redis(R) is an open source, scalable, distributed in-memory cache for applications. It can be used to store and serve data in the form of strings, hashes, lists, sets and sorted sets. + +[Overview of Redis® Cluster](http://redis.io) + +Disclaimer: Redis is a registered trademark of Redis Ltd. Any rights therein are reserved to Redis Ltd. Any use by Bitnami is for referential purposes only and does not indicate any sponsorship, endorsement, or affiliation between Redis Ltd. + +## TL;DR + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/redis-cluster +``` + +## Introduction + +This chart bootstraps a [Redis®](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. + +### 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 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.19+ +- Helm 3.2.0+ +- 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-cluster +``` + +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. + +NOTE: if you get a timeout error waiting for the hook to complete increase the default timeout (300s) to a higher one, for example: + +``` +helm install --timeout 600s myrelease bitnami/redis-cluster +``` + +> **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 + +### Global parameters + +| Name | Description | Value | +| ------------------------- | ----------------------------------------------- | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | +| `global.redis.password` | Redis® password (overrides `password`) | `""` | + + +### Redis® Cluster Common parameters + +| Name | Description | Value | +| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | +| `nameOverride` | String to partially override common.names.fullname template (will maintain the release name) | `""` | +| `fullnameOverride` | String to fully override common.names.fullname template | `""` | +| `clusterDomain` | Kubernetes Cluster Domain | `cluster.local` | +| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | +| `commonLabels` | Labels to add to all deployed objects | `{}` | +| `extraDeploy` | Array of extra objects to deploy with the release (evaluated as a template) | `[]` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the deployment | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the deployment | `["infinity"]` | +| `image.registry` | Redis® cluster image registry | `docker.io` | +| `image.repository` | Redis® cluster image repository | `bitnami/redis-cluster` | +| `image.tag` | Redis® cluster image tag (immutable tags are recommended) | `6.2.7-debian-11-r9` | +| `image.pullPolicy` | Redis® cluster image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Enable image debug mode | `false` | +| `networkPolicy.enabled` | Enable NetworkPolicy | `false` | +| `networkPolicy.allowExternal` | The Policy model to apply. Don't require client label for connections | `true` | +| `networkPolicy.ingressNSMatchLabels` | Allow connections from other namespacess. Just set label for namespace and set label for pods (optional). | `{}` | +| `networkPolicy.ingressNSPodMatchLabels` | For other namespaces match by pod labels and namespace labels | `{}` | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `false` | +| `serviceAccount.name` | The name of the ServiceAccount to create | `""` | +| `serviceAccount.annotations` | Annotations for Cassandra Service Account | `{}` | +| `serviceAccount.automountServiceAccountToken` | Automount API credentials for a service account. | `false` | +| `rbac.create` | Specifies whether RBAC resources should be created | `false` | +| `rbac.role.rules` | Rules to create. It follows the role specification | `[]` | +| `podSecurityContext.enabled` | Enable Redis® pod Security Context | `true` | +| `podSecurityContext.fsGroup` | Group ID for the pods | `1001` | +| `podSecurityContext.runAsUser` | User ID for the pods | `1001` | +| `podSecurityContext.sysctls` | Set namespaced sysctls for the pods | `[]` | +| `podDisruptionBudget` | Limits the number of pods of the replicated application that are down simultaneously from voluntary disruptions | `{}` | +| `minAvailable` | Min number of pods that must still be available after the eviction | `""` | +| `maxUnavailable` | Max number of pods that can be unavailable after the eviction | `""` | +| `containerSecurityContext.enabled` | Enable Containers' Security Context | `true` | +| `containerSecurityContext.runAsUser` | User ID for the containers. | `1001` | +| `containerSecurityContext.runAsNonRoot` | Run container as non root | `true` | +| `usePassword` | Use password authentication | `true` | +| `password` | Redis® password (ignored if existingSecret set) | `""` | +| `existingSecret` | Name of existing secret object (for password authentication) | `""` | +| `existingSecretPasswordKey` | Name of key containing password to be retrieved from the existing secret | `""` | +| `usePasswordFile` | Mount passwords as files instead of environment variables | `false` | +| `tls.enabled` | Enable TLS support for replication traffic | `false` | +| `tls.authClients` | Require clients to authenticate or not | `true` | +| `tls.autoGenerated` | Generate automatically self-signed TLS certificates | `false` | +| `tls.existingSecret` | The name of the existing secret that contains the TLS certificates | `""` | +| `tls.certificatesSecret` | DEPRECATED. Use tls.existingSecret instead | `""` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename | `""` | +| `tls.dhParamsFilename` | File containing DH params (in order to support DH based ciphers) | `""` | +| `service.ports.redis` | Kubernetes Redis service port | `6379` | +| `service.nodePorts.redis` | Node port for Redis | `""` | +| `service.extraPorts` | Extra ports to expose in the service (normally used with the `sidecar` value) | `[]` | +| `service.annotations` | Provide any additional annotations which may be required. | `{}` | +| `service.labels` | Additional labels for redis service | `{}` | +| `service.type` | Service type for default redis service | `ClusterIP` | +| `service.clusterIP` | Service Cluster IP | `""` | +| `service.loadBalancerIP` | Load balancer IP if `service.type` is `LoadBalancer` | `""` | +| `service.loadBalancerSourceRanges` | Service Load Balancer sources | `[]` | +| `service.externalTrafficPolicy` | Service external traffic policy | `Cluster` | +| `service.sessionAffinity` | Session Affinity for Kubernetes service, can be "None" or "ClientIP" | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `persistence.path` | Path to mount the volume at, to use other images Redis® images. | `/bitnami/redis/data` | +| `persistence.subPath` | The subdirectory of the volume to mount to, useful in dev environments and one PV for multiple services | `""` | +| `persistence.storageClass` | Storage class of backing PVC | `""` | +| `persistence.annotations` | Persistent Volume Claim annotations | `{}` | +| `persistence.accessModes` | Persistent Volume Access Modes | `["ReadWriteOnce"]` | +| `persistence.size` | Size of data volume | `8Gi` | +| `persistence.matchLabels` | Persistent Volume selectors | `{}` | +| `persistence.matchExpressions` | matchExpressions Persistent Volume selectors | `{}` | +| `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 repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Init container volume-permissions image tag | `11-debian-11-r10` | +| `volumePermissions.image.pullPolicy` | Init container volume-permissions image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `volumePermissions.resources.limits` | The resources limits for the container | `{}` | +| `volumePermissions.resources.requests` | The requested resources for the container | `{}` | +| `podSecurityPolicy.create` | Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later | `false` | + + +### Redis® statefulset parameters + +| Name | Description | Value | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------- | +| `redis.command` | Redis® entrypoint string. The command `redis-server` is executed if this is not provided | `[]` | +| `redis.args` | Arguments for the provided command if needed | `[]` | +| `redis.updateStrategy.type` | Argo Workflows statefulset strategy type | `RollingUpdate` | +| `redis.updateStrategy.rollingUpdate.partition` | Partition update strategy | `0` | +| `redis.podManagementPolicy` | Statefulset Pod management policy, it needs to be Parallel to be able to complete the cluster join | `Parallel` | +| `redis.hostAliases` | Deployment pod host aliases | `[]` | +| `redis.hostNetwork` | Host networking requested for this pod. Use the host's network namespace. | `false` | +| `redis.useAOFPersistence` | Whether to use AOF Persistence mode or not | `yes` | +| `redis.containerPorts.redis` | Redis® port | `6379` | +| `redis.containerPorts.bus` | The busPort should be obtained adding 10000 to the redisPort. By default: 10000 + 6379 = 16379 | `16379` | +| `redis.lifecycleHooks` | LifecycleHook to set additional configuration before or after startup. Evaluated as a template | `{}` | +| `redis.extraVolumes` | Extra volumes to add to the deployment | `[]` | +| `redis.extraVolumeMounts` | Extra volume mounts to add to the container | `[]` | +| `redis.customLivenessProbe` | Override default liveness probe | `{}` | +| `redis.customReadinessProbe` | Override default readiness probe | `{}` | +| `redis.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `redis.initContainers` | Extra init containers to add to the deployment | `[]` | +| `redis.sidecars` | Extra sidecar containers to add to the deployment | `[]` | +| `redis.podLabels` | Additional labels for Redis® pod | `{}` | +| `redis.priorityClassName` | Redis® Master pod priorityClassName | `""` | +| `redis.configmap` | Additional Redis® configuration for the nodes | `""` | +| `redis.extraEnvVars` | An array to add extra environment variables | `[]` | +| `redis.extraEnvVarsCM` | ConfigMap with extra environment variables | `""` | +| `redis.extraEnvVarsSecret` | Secret with extra environment variables | `""` | +| `redis.podAnnotations` | Redis® additional annotations | `{}` | +| `redis.resources.limits` | The resources limits for the container | `{}` | +| `redis.resources.requests` | The requested resources for the container | `{}` | +| `redis.schedulerName` | Use an alternate scheduler, e.g. "stork". | `""` | +| `redis.shareProcessNamespace` | Enable shared process namespace in a pod. | `false` | +| `redis.livenessProbe.enabled` | Enable livenessProbe | `true` | +| `redis.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `5` | +| `redis.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `5` | +| `redis.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `redis.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `5` | +| `redis.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `redis.readinessProbe.enabled` | Enable readinessProbe | `true` | +| `redis.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `5` | +| `redis.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` | +| `redis.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `redis.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `5` | +| `redis.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `redis.startupProbe.enabled` | Enable startupProbe | `false` | +| `redis.startupProbe.path` | Path to check for startupProbe | `/` | +| `redis.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `300` | +| `redis.startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `redis.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `5` | +| `redis.startupProbe.failureThreshold` | Failure threshold for startupProbe | `6` | +| `redis.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `redis.podAffinityPreset` | Redis® pod affinity preset. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `redis.podAntiAffinityPreset` | Redis® pod anti-affinity preset. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `redis.nodeAffinityPreset.type` | Redis® node affinity preset type. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `redis.nodeAffinityPreset.key` | Redis® node label key to match Ignored if `redis.affinity` is set. | `""` | +| `redis.nodeAffinityPreset.values` | Redis® node label values to match. Ignored if `redis.affinity` is set. | `[]` | +| `redis.affinity` | Affinity settings for Redis® pod assignment | `{}` | +| `redis.nodeSelector` | Node labels for Redis® pods assignment | `{}` | +| `redis.tolerations` | Tolerations for Redis® pods assignment | `[]` | +| `redis.topologySpreadConstraints` | Pod topology spread constraints for Redis® pod | `[]` | + + +### Cluster update job parameters + +| Name | Description | Value | +| ------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------ | +| `updateJob.activeDeadlineSeconds` | Number of seconds the Job to create the cluster will be waiting for the Nodes to be ready. | `600` | +| `updateJob.command` | Container command (using container default if not set) | `[]` | +| `updateJob.args` | Container args (using container default if not set) | `[]` | +| `updateJob.hostAliases` | Deployment pod host aliases | `[]` | +| `updateJob.annotations` | Job annotations | `{}` | +| `updateJob.podAnnotations` | Job pod annotations | `{}` | +| `updateJob.podLabels` | Pod extra labels | `{}` | +| `updateJob.extraEnvVars` | An array to add extra environment variables | `[]` | +| `updateJob.extraEnvVarsCM` | ConfigMap containing extra environment variables | `""` | +| `updateJob.extraEnvVarsSecret` | Secret containing extra environment variables | `""` | +| `updateJob.extraVolumes` | Extra volumes to add to the deployment | `[]` | +| `updateJob.extraVolumeMounts` | Extra volume mounts to add to the container | `[]` | +| `updateJob.initContainers` | Extra init containers to add to the deployment | `[]` | +| `updateJob.podAffinityPreset` | Update job pod affinity preset. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `updateJob.podAntiAffinityPreset` | Update job pod anti-affinity preset. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `updateJob.nodeAffinityPreset.type` | Update job node affinity preset type. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `updateJob.nodeAffinityPreset.key` | Update job node label key to match Ignored if `updateJob.affinity` is set. | `""` | +| `updateJob.nodeAffinityPreset.values` | Update job node label values to match. Ignored if `updateJob.affinity` is set. | `[]` | +| `updateJob.affinity` | Affinity for update job pods assignment | `{}` | +| `updateJob.nodeSelector` | Node labels for update job pods assignment | `{}` | +| `updateJob.tolerations` | Tolerations for update job pods assignment | `[]` | +| `updateJob.priorityClassName` | Priority class name | `""` | +| `updateJob.resources.limits` | The resources limits for the container | `{}` | +| `updateJob.resources.requests` | The requested resources for the container | `{}` | + + +### Cluster management parameters + +| Name | Description | Value | +| --------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -------------- | +| `cluster.init` | Enable the initialization of the Redis® Cluster | `true` | +| `cluster.nodes` | The number of master nodes should always be >= 3, otherwise cluster creation will fail | `6` | +| `cluster.replicas` | Number of replicas for every master in the cluster | `1` | +| `cluster.externalAccess.enabled` | Enable access to the Redis | `false` | +| `cluster.externalAccess.service.type` | Type for the services used to expose every Pod | `LoadBalancer` | +| `cluster.externalAccess.service.port` | Port for the services used to expose every Pod | `6379` | +| `cluster.externalAccess.service.loadBalancerIP` | Array of load balancer IPs for each Redis® node. Length must be the same as cluster.nodes | `[]` | +| `cluster.externalAccess.service.loadBalancerSourceRanges` | Service Load Balancer sources | `[]` | +| `cluster.externalAccess.service.annotations` | Annotations to add to the services used to expose every Pod of the Redis® Cluster | `{}` | +| `cluster.update.addNodes` | Boolean to specify if you want to add nodes after the upgrade | `false` | +| `cluster.update.currentNumberOfNodes` | Number of currently deployed Redis® nodes | `6` | +| `cluster.update.currentNumberOfReplicas` | Number of currently deployed Redis® replicas | `1` | +| `cluster.update.newExternalIPs` | External IPs obtained from the services for the new nodes to add to the cluster | `[]` | + + +### Metrics sidecar parameters + +| Name | Description | Value | +| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `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 | `1.43.0-debian-11-r3` | +| `metrics.image.pullPolicy` | Redis® exporter image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `metrics.resources` | Metrics exporter resource requests and limits | `{}` | +| `metrics.extraArgs` | Extra arguments for the binary; possible values [here](https://github.com/oliver006/redis_exporter | `{}` | +| `metrics.podAnnotations` | Additional annotations for Metrics exporter pod | `{}` | +| `metrics.podLabels` | Additional labels for Metrics exporter pod | `{}` | +| `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 | `""` | +| `metrics.serviceMonitor.interval` | How frequently to scrape metrics (use by default, falling back to Prometheus' default) | `""` | +| `metrics.serviceMonitor.scrapeTimeout` | Timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.selector` | Prometheus instance selector labels | `{}` | +| `metrics.serviceMonitor.labels` | ServiceMonitor extra labels | `{}` | +| `metrics.serviceMonitor.annotations` | ServiceMonitor annotations | `{}` | +| `metrics.serviceMonitor.jobLabel` | The name of the label on the target service to use as the job name in prometheus. | `""` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `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 | `""` | +| `metrics.prometheusRule.rules` | Create specified [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/), check values for an example. | `[]` | +| `metrics.priorityClassName` | Metrics exporter pod priorityClassName | `""` | +| `metrics.service.type` | Kubernetes Service type (redis metrics) | `ClusterIP` | +| `metrics.service.loadBalancerIP` | Use serviceLoadBalancerIP to request a specific static IP, otherwise leave blank | `""` | +| `metrics.service.annotations` | Annotations for the services to monitor. | `{}` | +| `metrics.service.labels` | Additional labels for the metrics service | `{}` | +| `metrics.service.clusterIP` | Service Cluster IP | `""` | + + +### Sysctl Image parameters + +| Name | Description | Value | +| -------------------------------- | -------------------------------------------------- | ----------------------- | +| `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 repository | `bitnami/bitnami-shell` | +| `sysctlImage.tag` | sysctlImage Init container tag | `11-debian-11-r10` | +| `sysctlImage.pullPolicy` | sysctlImage Init container pull policy | `IfNotPresent` | +| `sysctlImage.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `sysctlImage.mountHostSys` | Mount the host `/sys` folder to `/host-sys` | `false` | +| `sysctlImage.resources.limits` | The resources limits for the container | `{}` | +| `sysctlImage.resources.requests` | The requested resources for the container | `{}` | + + +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-cluster +``` + +The above command sets the Redis® server password to `secretpassword`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +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-cluster +``` + +> **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. 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. + +### Use a different Redis® version + +To modify the application version used in this chart, specify a different version of the image using the `image.tag` parameter and/or a different repository using the `image.repository` parameter. Refer to the [chart documentation for more information on these parameters and how to use them with images from a private registry](https://docs.bitnami.com/kubernetes/infrastructure/redis-cluster/configuration/change-image-version/). + +### Cluster topology + +To successfully set the cluster up, it will need to have at least 3 master nodes. The total number of nodes is calculated like- `nodes = numOfMasterNodes + numOfMasterNodes * replicas`. Hence, the defaults `cluster.nodes = 6` and `cluster.replicas = 1` means, 3 master and 3 replica nodes will be deployed by the chart. + +By default the Redis® Cluster is not accessible from outside the Kubernetes cluster, to access the Redis® Cluster from outside you have to set `cluster.externalAccess.enabled=true` at deployment time. It will create in the first installation only 6 LoadBalancer services, one for each Redis® node, once you have the external IPs of each service you will need to perform an upgrade passing those IPs to the `cluster.externalAccess.service.loadbalancerIP` array. + +The replicas will be read-only replicas of the masters. By default only one service is exposed (when not using the external access mode). You will connect your client to the exposed service, regardless you need to read or write. When a write operation arrives to a replica it will redirect the client to the proper master node. For example, using `redis-cli` you will need to provide the `-c` flag for `redis-cli` to follow the redirection automatically. + +Using the external access mode, you can connect to any of the pods and the slaves will redirect the client in the same way as explained before, but the all the IPs will be public. + +In case the master crashes, one of his slaves will be promoted to master. The slots stored by the crashed master will be unavailable until the slave finish the promotion. If a master and all his slaves crash, the cluster will be down until one of them is up again. To avoid downtime, it is possible to configure the number of Redis® nodes with `cluster.nodes` and the number of replicas that will be assigned to each master with `cluster.replicas`. For example: + +- `cluster.nodes=9` ( 3 master plus 2 replicas for each master) +- `cluster.replicas=2` + +Providing the values above, the cluster will have 3 masters and, each master, will have 2 replicas. + +> NOTE: By default `cluster.init` will be set to `true` in order to initialize the Redis® Cluster in the first installation. If for testing purposes you only want to deploy or upgrade the nodes but avoiding the creation of the cluster you can set `cluster.init` to `false`. + +#### Adding a new node to the cluster + +There is a job that will be executed using a `post-upgrade` hook that will allow you to add a new node. To use it, you should provide some parameters to the upgrade: + +- Pass as `password` the password used in the installation time. If you did not provide a password follow the instructions from the NOTES.txt to get the generated password. +- Set the desired number of nodes at `cluster.nodes`. +- Set the number of current nodes at `cluster.update.currentNumberOfNodes`. +- Set to true `cluster.update.addNodes`. + +The following will be an example to add one more node: + +``` +helm upgrade --timeout 600s --set "password=${REDIS_PASSWORD},cluster.nodes=7,cluster.update.addNodes=true,cluster.update.currentNumberOfNodes=6" bitnami/redis-cluster +``` + +Where `REDIS_PASSWORD` is the password obtained with the command that appears after the first installation of the Helm Chart. +The cluster will continue up while restarting pods one by one as the quorum is not lost. + +##### External Access + +If you are using external access, to add a new node you will need to perform two upgrades. First upgrade the release to add a new Redis® node and to get a LoadBalancerIP service. For example: + +``` +helm upgrade --set "password=${REDIS_PASSWORD},cluster.externalAccess.enabled=true,cluster.externalAccess.service.type=LoadBalancer,cluster.externalAccess.service.loadBalancerIP[0]=,cluster.externalAccess.service.loadBalancerIP[1]=,cluster.externalAccess.service.loadBalancerIP[2]=,cluster.externalAccess.service.loadBalancerIP[3]=,cluster.externalAccess.service.loadBalancerIP[4]=,cluster.externalAccess.service.loadBalancerIP[5]=,cluster.externalAccess.service.loadBalancerIP[6]=,cluster.nodes=7,cluster.init=false bitnami/redis-cluster +``` + +> Important here to provide the loadBalancerIP parameters for the new nodes empty to not get an index error. + +As we want to add a new node, we are setting `cluster.nodes=7` and we leave empty the LoadBalancerIP for the new node, so the cluster will provide the correct one. +`REDIS_PASSWORD` is the password obtained with the command that appears after the first installation of the Helm Chart. +At this point, you will have a new Redis® Pod that will remain in `crashLoopBackOff` state until we provide the LoadBalancerIP for the new service. +Now, wait until the cluster provides the new LoadBalancerIP for the new service and perform the second upgrade: + +``` +helm upgrade --set "password=${REDIS_PASSWORD},cluster.externalAccess.enabled=true,cluster.externalAccess.service.type=LoadBalancer,cluster.externalAccess.service.loadBalancerIP[0]=,cluster.externalAccess.service.loadBalancerIP[1]=,cluster.externalAccess.service.loadBalancerIP[2]=,cluster.externalAccess.service.loadBalancerIP[3]=,cluster.externalAccess.service.loadBalancerIP[4]=,cluster.externalAccess.service.loadBalancerIP[5]=,cluster.externalAccess.service.loadBalancerIP[6]=,cluster.nodes=7,cluster.init=false,cluster.update.addNodes=true,cluster.update.newExternalIPs[0]=" bitnami/redis-cluster +``` + +Note we are providing the new IPs at `cluster.update.newExternalIPs`, the flag `cluster.update.addNodes=true` to enable the creation of the Job that adds a new node and now we are setting the LoadBalancerIP of the new service instead of leave it empty. + +> NOTE: To avoid the creation of the Job that initializes the Redis® Cluster again, you will need to provide `cluster.init=false`. + +#### Scale down the cluster + +To scale down the Redis® Cluster, follow these steps: + +First perform a normal upgrade setting the `cluster.nodes` value to the desired number of nodes. It should not be less than `6` and the difference between current number of nodes and the desired should be less or equal to `cluster.replicas` to avoid removing master node an its slaves at the same time. Also it is needed to provide the password using the `password`. For example, having more than 6 nodes, to scale down the cluster to 6 nodes: + +``` +helm upgrade --timeout 600s --set "password=${REDIS_PASSWORD},cluster.nodes=6" . +``` + +The cluster will continue working during the update as long as the quorum is not lost. + +> NOTE: To avoid the creation of the Job that initializes the Redis® Cluster again, you will need to provide `cluster.init=false`. + +Once all the nodes are ready, get the list of nodes in the cluster using the `CLUSTER NODES` command. You will see references to the ones that were removed. Write down the node IDs of the nodes that show `fail`. In the following example the cluster scaled down from 7 to 6 nodes. + +``` +redis-cli -a $REDIS_PASSWORD CLUSTER NODES + +... +b23bcffa1fd64368d445c1d9bd9aeb92641105f7 10.0.0.70:6379@16379 slave,fail - 1645633139060 0 0 connected +... +``` + +In each cluster node, execute the following command. Replace the NODE_ID placeholder. + +``` +redis-cli -a $REDIS_PASSWORD CLUSTER FORGET NODE_ID +``` + +In the previous example the commands would look like this in each cluster node: + +``` +redis-cli -a $REDIS_PASSWORD CLUSTER FORGET b23bcffa1fd64368d445c1d9bd9aeb92641105f7 +``` + +### 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-secret +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 cluster: + +- `tls.enabled`: Enable TLS support. Defaults to `false` +- `tls.existingSecret`: 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 certificates 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.existingSecret="certificates-tls-secret" +tls.certFilename="cert.pem" +tls.certKeyFilename="cert.key" +tls.certCAFilename="ca.pem" +``` + +### Sidecars and Init Containers + +If you have a need for additional containers to run within the same pod as Redis® (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 +``` + +### 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: REDIS_WHATEVER + value: value +``` + +Alternatively, you can use a ConfigMap or a Secret with the environment variables. To do so, use the `extraEnvVarsCM` or the `extraEnvVarsSecret` values. + +### 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 + - |- + sysctl -w net.core.somaxconn=10000 + echo never > /host-sys/kernel/mm/transparent_hugepage/enabled +``` + +Alternatively, for Kubernetes 1.12+ you can set `podSecurityContext.sysctls` which will configure sysctls for master and slave pods. Example: + +```yaml +podSecurityContext: + sysctls: + - name: net.core.somaxconn + value: "10000" +``` + +Note that this will not disable transparent huge tables. + +## Helm Upgrade + +By default `cluster.init` will be set to `true` in order to initialize the Redis® Cluster in the first installation. If for testing purposes you only want to deploy or upgrade the nodes but avoiding the creation of the cluster you can set `cluster.init` to `false`. + +## Persistence + +By default, the chart mounts a [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) at the `/bitnami` path. The volume is created using dynamic volume provisioning. + +## 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: + +```yaml +networkPolicy: + enabled: true + ingressNSMatchLabels: + redis: external + ingressNSPodMatchLabels: + redis-client: true +``` + +### Setting Pod's affinity + +This chart allows you to set your custom affinity using the `XXX.affinity` paremeter(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 + +### To 7.0.0 + +This major release renames several values in this chart and adds missing features, in order to be inline with the rest of assets in the Bitnami charts repository. + +Since this version performs changes in the statefulset, in order to upgrade from previous versions you need to delete the statefulset object before the upgrade. + +```console +kubectl delete statefulset +helm upgrade bitnami/redis-cluster --set redis.password= +``` + +### To 6.0.0 + +The cluster initialization job have been removed. Instead, the pod with index 0 from the statefulset will handle the initialization of the cluster. + +As consequence, the `initJob` configuration section have been removed. + +### To 5.0.0 + +This major version updates the Redis® docker image version used from `6.0` to `6.2`, the new stable version. There are no major changes in the chart and there shouldn't be any breaking changes in it as `6.2` breaking changes center around some command and behaviour changes. For more information, please refer to [Redis® 6.2 release notes](https://raw.githubusercontent.com/redis/redis/6.2/00-RELEASENOTES). + +### To 4.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 3.0.0 + +This version of the chart adapts the chart to the most recent Bitnami best practices and standards. Most of the Redis® parameters were moved to the `redis` values section (such as extraEnvVars, sidecars, and so on). No major issues are expected during the upgrade. + +### To 2.0.0 + +The version `1.0.0` was using a label in the Statefulset's volumeClaimTemplate that didn't allow to upgrade the chart. The version `2.0.0` fixed that issue. Also it adds more docs in the README.md. + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/rds/base/charts/redis-cluster/charts/common-1.16.0.tgz b/rds/base/charts/redis-cluster/charts/common-1.16.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7992cf00fd307325e78af97d419ed39cbd40dc44 GIT binary patch literal 14693 zcmV-rIhw{FiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PKBhciT9U=zP|%s7t3eRwkk(JC7O9X74(WneplF#Me$IXSRE8 z8zLbIZ3G?ULG}OP;Q9U^hEJY8eLmcO@_cXbhrw{~$@9HGz~CBisXdvHi2q@5 z<+iGw`$ir_DU(PNM#Vt~AVLyP7+)N~5gl_RB+nw5aoj;E`GC0~j2^&zuY=Mw|8p1) zpM`@?j77vrD)qZV_<@W#;tTjYPG+Fl3&xDYgtIIqbOMI)AYl^{!3fI>j46zWq-aJU zQsqV10eC+p!u$zD0A&(loF;66MhS+|0*Dki8Hb%Io8bePN|}m-et$ybG#iBxoAoWo zz5&^n9OM2B35j{}Nu$ysk;2@8upc#ym|f6>p;+`2Hetej>&*~94^uknoa4m>8sMB+O?aUCN5)q2Iu1=Ba$V>YXAmhmz{py%% zwr#;O9$^h`ble#^Zk4MebNtHm47!yiTJSLP#f<-9Pt!RT4^8f7F z-moJ7PoF*=+{^z>JfA=JAHkf=4nSZDW0GJs>A#pMvP4sS0FU}#zjivRSmy-Gj8i}$ z*jNH(F+McBvVa&HrePF1pFabkQIf?N0>fUR$5X1B2o;0?c4HDr2+r{$fZ(t1-USet zi70^Gga`@2%fpjE{rg`*w+p?mUppF|&!2my#g#3nDk;Zf%sGxkze(X^ndxcO-PVN?)Kae^bs_m97Mb)Mff06+_SbLm2 z6kN9-;feGrJL87efA#f$gi;Id0F0uZXQw$RE z36}7Eu2#VA4z$W@iLI16vinEHBuB>iJUU6(2qh)qs($og_yw1g>UqVqJbQn;2{L@; z$yT7(6>(#Q!sn`8cp%~HfM1pht^_lCVeP@nnR`5^4(0QKw*foZZoAC9m@!=Oyo zwz9$2XhpHSVEo*6d_5}JI>#Fl>-m*wg+ED5wKHQqdRasXorLFq(pJGXNf&WOe`BLH ziRMb*ByWwBxwamuT+!{3$LwLxFkM&pyb5a*Y53&EnF^(;*eaofL=um)M69i*{2Dh& zD%w~WXx5Y}K0?t3`sV8My=gF$Q^v`^bO6^zBwSW~d!&4I@_JnYMm*ktephEb zl#+t&HzHk2&FxX`@c8Hy^Erv|nzTbHaTV4l;b0pir3 zV-~|Kf8P)H z)$EfcQeglH0zxI`V-#Uckul3?902B=DNcU$p1}wM$1^s^FpJm*3+rF~@j!)hHs~Kx+v0r~!9sjjId{T}7dh&e#>AnB=CZ6^Dzgo?| zpK_K>rbhD^MU$lyj$$RpnNa_lUAYtqp~K;AXbi|HlAu6p1vbK_T-fRSB+vvOm~G2B zN=U4Yfj6pw!2k|k)b>=0Lc#{tgsR~LhZ|B&VTdp~Gr02p|wR;#lTC z-l7=}AQ%&z#JY+p+8YdlVt%e+IJ_OX$I?{8!Q5G2)m?SE$`UTt(7iZ?8%!y$$~xBUEqO z*Qwy|Tp}7b)UR#*9*Lg}9;N+<2O399(}Ncf{6dcpPftI-J9&KyZ%$6$ogBbFSZ3rg zA~UfS zwEvoMImH|Z9Wy>N+!-ON+GitulqUux^J6tJi$v*M%>+}-QPQ8Xi=JeC$yqi^u$VF? zdsCduOptt!2$5mY-zhrZGwj#9n34qd@^P`{?Z}8rbn&}NB^y0aDpqI6FEB_5-zl5&bAz$~e_j!Vxn*J~D?V-81}A@V+-YHJvD)En%^IORA(62}Li z9V{ij;F!jkA7@EoY6<7I=`LZ{v?yL-+T1X1<=f%o_HTRLf~(6hD(kR#2`kUY^xi9` z)aWUiR(Z|mKzlC1XDOdXy*wtd*1`)wX{yGp^fqi{#PVOAQq!hWrE2y|dP9hiG3@$r zq#l^A9*-(n4l0{(5Iik!ipic{2_8tWbPc16lg6N4B~SvfuQX<2HZ*sebbW$!AZh-(7Q>RqG9YQ7qOlQ!ZcC9h;B>$(0#kBLkEk zP$uL1MT$Z1XDEKQJIpvkk!|4*JheSUBM-^8jk6?K z!0#DK$e5^wNt)`;hMkYtK-cA^YC~Zz@(^^vX4v=26{|+GY)QzBSSOU65&^rGRIlHj zsmg1-zc88``*74jC_q#LgS^xG+KOsM!8B2#!Von- zWp#=QyKQAW;9++o?zsW`uWtXF^9DD!|2up#cv6l3d^#N5=YPJDX9N2`-~KfmVyDd| z^QPmZ(p2d-R8Q^smL|7zxr~}p^+cldhmn>5g4rVfxom^C0;&gZN-z5P*^_svITdR5 zNEJ55?!f*Qu!|zp@4Y({U_$1Yx@AQsFt;iAa%;moFsh2UsgY3}f}RGa>~eu&isq^tbIegvrKwPw-OrA5-TT>P9<7-$tJQvK*E*j=8wgYa{h~U* zSW~B7Z^PAM&mM`c@_jMC>qx5F&U3N1X6jSl#&S4^%@cRs_-ifdBU(_vb-&pZsOp__ zyjV-^=nRur&^jhVii}8nY&e-Z1 z+F`@F+P$C)L4xT-P6HSY^cXTVhB}U*yoxXVUK&7Vg}=tJ&?~X*Lo<>O^UOkUF~uC4 zh;K(@B0xx{*nOESR3JA9gSo-NClvJbnc9Ch!2Y8Qt*VyXNk11?-k_Eq0}dw#$_x{~ zCpm?K5I&M5fs{n&FeY3`bM_ZY%xzGhmCx7!GpL#|fu4>rD)gno*?h4*omk$Cfg>8T zSxz_u#tbL);3Z=TMzjp-;}q*to06%eBZ$Rp#LWKO8lVdgMuVYK84u&g(W+UWpB0G2 z#T9nOxN-g`k&UeXeAO9c=+zD>chtBs9_yp1Hr0uCl(}2x>?vv8P+6K4^A6SYU<5dy zhiY7E6x8m~7*GQXtF~B1W5}q&BHtQtyyvLQel5?57scTx5~20({d!k#ve|g$zHg_8VIn&53FYHgs-; zAaIlC11Fo6*M%ymrsB^(xS|fQ)nZ=x;Q&J0{k*W{0moV28xy5Ek7kl2BAj6PJzo5k zvzgNB{tY}LfUu(ay$P&XuCz7#!DY2?ZCIC3O@Hqh*m|07?8DO5&G5WN66w`e!q|D1 zLNHg;x3kqBEXjFq$G02C3WRs&(F5$8a5j9*|LouMQ)UNej5U}3+>k}u*;oxJ7v2{J z_AkMA;+FtohE0wutKRa-+rewmm%k!kXC#QjX_@Wuk5^Am8j1>#Q10$yIB`TU)4Y zXt=L3R$Om;FWUxOCNjn%q>-K zu1yM1tLES0Rc`Uc@=8m$NVKl5*Rr2>@$Y&Ys8pm|aVxty+%IDeJ^m8>68L!*e7j;* zU1P{JW7gW6`^n_)J$3zGWOLZo46s7~51tKb@gGl~Jh|8ZH}Pzw|7nbQ?oBk#+T1nk zIA~cQWkaG6C}#?3mp_^T{-EU;LypO}B^nRd&O9?n3zo znkC)l+IJ|gNL~JopYJBme+&k-{LfFH-k<-tk!J(>H;4HNHTm-O$y>=_qSo_nb#3?b z+BJY-F1t-R!P3h8@@2hCdTih7Iz9JtO})_4aK6HxBkp2;nsL_cy=QOe`~%|d8c+Nw zWn9{OT@8S*Utc`fJS zv8aIZ+@Si44uMJ&qP-qM{m?m~mAz{_?A6e2!dFP(zt*a1Z)2~T8>`xeKS{JgE7D=L zmDmO2uneKwU=Zs+3*Ticl$N**SxmiS&Sq}1G$XijQY~J`gVSr+s7X!OJ;!r`O(Z_1 zl5iUi!N&0LJMqx^DS=RMHx)lvz9Unl0M9VIfL*tdR3KC45bwhaSFOn%mpf$VYW#}5 zM^i;ZgeAUNv)k)v=_v-ParN`8ST|m<#;9dU@~y?4Pz&nalj5)j_`8hH2Qi@zgDCS$gbH2^9idg#4>3w zeBmrf^eHSfS!VX9d2tq#ONsc=Gn>?cWaIr06}wSmH?H7*A&*S_*y4cs^GUAV4HKu zC4S9=xm*x`dBw0^2uS&Ki7jWsK8nt|6zd$@*bf&2pJSc7KSa-BYua^DW=)alVIG)X zwB!!WAyL$2T#+?|*JjmsVEWrSN3Kg;Zw*>G`qHqXDIi#owPRH!cs2xZEkLGh1wuUf zNnn1=@tAyC3(PrQgr-sjl8YGhV+o73QE`5u(q=iVO`8>r8^hs2_1urnKwCXSruSI8 z?xQ7$NlIBhDO8K>6IJKcCQ*^K?zV0EA%S5EvbRcucMn`KNUC&BDWQ6THre`eSRlajCobcnufFd zSJ0Pw&@%H}YI&{r98diq|i_40Lj{S%xfB+@L> z)}4BZI@CM5B(9E9>$131uzhK4d8DU?c+0Ef5(eeoXe50`xy(V_8pAcMkjsIQ{tas- z9J=)Y_iZ~N9Hr|--l`j-8gK7+-&`Q%YoEZAg~|?N?o{$ocS}-CW6&cZ_AE3CBGYtY++`9lcQRozhXpvE!i|;{`=c? z!>#cDFgjt^?Ee`Kp6u_}{6Ej`&wt#=b1DCi;)>TV|Bd6xU$*ZAvMhIZoIrkW+dV-B z?JN3%{HCtSAyg;tW)2}+e=825GOQMd&~i6U8AOX4XQflA(y4Z*(p_^1y+#s^kf_#= z?dzLzD4k+=&ZF*1GT&B0I?UwsO~ET3oSZLO+)(=Un|3za#9!iLv0^QH4c;%~gYZ$f zlWsAq>i!3~#Y_cX$ga|A4rjG9PH|SH%N1O%-zQg@(e9K_PGGr)ORi8$tDJGROs&|@n27C_y0a04)4!@-pF$)|KH;D|NO9h$6x6tiQ9Mk=}um2Gvut=o?EDrwF;H_iwvS17VeQertbeHy##Oyb8t5JOEv@au zXgA%i(}i(8;&o-ug^{aoJf5rBjQ-|EbA1n6*J`Lr+q<`-aCEF_!&@sYi7skxtHnO`s7 zzOHSMjV+~^sbf)guUHCs1Gy0w`lDEkUfGY(&_s1SmJoYWhr&|kTx$fb*S)a3XLrfb za5?ii#u15ee#aeFh^ZOTUKV8WTsG&`nJp7IbT=H8a&n*JMURbp?%T3?t@YA;omt%8 z`|q$dH}Dc_v365fMTxggD$9FNHm56#n3g-08cTWYcOFqke0? zrqwg(W}b-E@Vtr65UTZ>-whbF+Q{mF+2)n9OJu%V9a9gNW*<_w+?^%s>DD$j1-xZf zmDj}$Ip%Ust0jQ0=(+L->u$TRoU@~55^gvIEoS94IzzU*JXpI&*P=a&qCe#uqN_EC zww3FmxrA@Qm$9^FGM4XjYN}K(Nn2UFHVZYkcnzAwH&!pFO3LQ$H_XM2{n?&&^9jA7 zrmI3-0cy+o&TlQfyvl1_+kV6B(%PpLEYrdgX+_L^eppuScKpZ7{l6T?7A9Z7ZV~*Z zuSBZ!BWMbu=&r0)uYiAV?3!~8wz=C|?do;nz^cyT<|wdwkYG%N?|j^dRJjXrHIe+H zYm=vhC&HJwKcTHQnuk5n( z;?FAFwj_4WFWK(fF00_yhBuCr^IQM+9oMVeP)5Nno2#2Ah0UB#%`mRGGBZH$q|3?7 zc;+riw-7VEUz^g}BV#AdYg}V(%?fFs_S=Wq)bYn`Iq6?bada;7T^LPojlYKYP&kST zW6wDSZe$X6t<3=c@dAcVx@(U_quFr<{%?fmIlKIs$0_yl^tHQz&DAw?VbEfGsRrz| zpqHFGs@N_!KghVPd0%1`d#@12+g?9?>(BD_zdx5<-V3l|{r}|I{-7HFwfFS-{rdkV zp35x%?Q&R^(F^?%uCMit?XJsf!G6W)crZ}PWw&bb<(K~IS3%oaKM!WGCQ@%Oux4^g zYs2lfHq2XF$K-3X^vt!#jkIa%Lj4Nl$}m9cP4=#r%}$Q@Q+utoWX=-NSks9GMW|eN)W#%_0*ciJF^ek9%0d zt}mu4b7ORvd&f!(rq$dd2%XfH3k45{`9Qa>i|ifw4cAMrXWr&q=B}JyD($QL)Y_Mz zdPTuXk(@1he%Znr_I}J`X1+(anLvQzb0 zUiPB)LgLyER~nPrbSD0yf2o^ktt$W``{P) z3w6qH(BJVFhaxqwl?Q1gufwZmesp{U1(oG9wepINp96EA0Ql)4l5bzkAOI!+ZPx zCY~KQMp9`ip?KSHfSPtFj53nMN{go`I!6;M!p;u7pArEgOH;wgXUopAIo-q-)S7t={!{p0==bJ{NejZk!+rF!in zI*&qek#-)1BXr(*6v|oJdG!A}JMaNcB>Ix2Boh6^6#Z3Cg`&Ts%5=5=d%{_k zDwSfm4iYvY5sa|Bz)Im*L`=P~)7ja9_dopQ^+~65c6O#L>rRYQOk+$Vg01zg=GFrX zPtWt#m3jcf@KgB7Mcpt#B;$(%aERMRY@1=kW_|lxHKtn}Au;Vd0EIr}4KE-|m7SWG z(s-eid~of~hn;gm;{#ADcry7BrJWg;NLkMcWrJ|Tl1Y6K;W$eY6AP(#v|X*)!k9)7 z{2eC=gO7|SabS?x*?}V}Ig2yB4bkanC2b6ml(RXBu>j;~?mT^}Un%ASE~X@!LWHO? zfwcQyA9R@Ggrz|7g&hyblwobSp*H?qH%Y(@Q8d9k>~vn1hO^Xw*7!evpN%j|Q&B)W zw{P@t7?z1~ny|%88HdVPHQ@E1Yz&$+Um5s>LX>1eVlF~6P3f_xtKyySA{mriYtSDO^ z(eW=4OR-)V#$-Ik+G-a#Dp1{l!||9@ex8-&B$&%yLvBHcJ`BROSd3PNK6itF)qGh*G1<^`W8#>J>`Sh$L%@RD-Zw zO2*cInxt{Hz+GLGw$Xq;+L1@+P$_ylr=L~3Ye!#cNQt=8_MSR++snIV!i*B4bfi0f zM8@F44ZIeB2ZcIoCt^_1fv zTw>fez_XahtEjGvrw8F0@e~qCJkAn%fnZ|v0xdG@FwVPC`WNKnrq|v>g4q$h`A}ADN9JSXhL;eJk?Bwql^=|u)wYZ zPm1LQ?#%iKSl-b^P8gFxzUXn$S8|F?TM)R@;SbIpgTNSm zK62Imxx1swaK>aekDwNkXF3mJ2n`)bvm`MGEW}x32VS%29CO2iaDpd9NWK7jCgUxd zVF9}_PC1T{#PNZeprG6Y$27+LxM&Cn6%wPkTj_c(EUmpCK5qZE*DYvM&T?|r)bSct zm+!vI6jPjh6S?15Y`>FD8p5niHuHAe#*}??+GcCdX0DmrnB2b^gQ9p!0%;f4BNUxu z8lN=`0sPIzZUJ(FX;~g@Ex=R>rrCH>(>^m|e0Y46TgsaayOUaB7Ar2Qjm z_^j`#^#zsaQ@*VbVF67*7PN|G`2fyt=gvkHtVoc$qO$>cad2$+Y3j zD4ZF;eJD~Kg#sryl8l#YsWru!m`CL@XUiNq+EQK5Jlqy%Tz;EtTb!|qCrk*5QQQr< zD>5tfK)p!p{E(*_tuv<%REc+U%sGk6pjPojIf$j~rF_M)F?ifwm@2k_-!qh`fr|m9 zsm5)o8+APl^uH;JaB08cFiF^jJuhm4eDiWPMWZKo|a@oA1On!r_t_`2%isgz5L<)`Ma(`>8{X9u%I{?qq`XlK}) zQvHAK?l^$LrWR>%i*1AZYUDe|i+m#|C!ak#a*j9Hpt}=5X zUE9N;{%$YmLXcoOk<$Q%0|;KX-S$vMt*x znuFe_Cdcl!Y`HsZwQb4Vy2FN*&xbNi@(h7A#@w)?`X%!7-uh&&Qih1oV)v)CFPyco zqB-AKo(1wl%lTnx5L1>VF&Gp~)*;u-<9tyu=J@vvQ<*GW<*2miID2rWW=*vkv0VF; zB&)qe*SlM^HAY{N{zw>8i{?=>77HnaN6j3hB&IwzdbnK=#2i9U^>4RjL{fsDzp zVK|s!*`@>nn@sZcZd48+=k9X7bm!&_iO5fcIGe;IMA(=YRv0d>clNDrq~~6iw^*y) zs2qJaCaNrS!I-mI?i@M;Hu?=m(hdlb!X(p2a=KAB&P&-?%wL-rUya@P9xqfUejvg> zG9X|~xR72K6vp}#60#WuV%t^5ZCSe>)W)(l!xNy4_FQSd8+D`l9506D&S_J+se4vy zeJBzcTq4B(_QTcg@Z5gW&n1pN+`i>-+wI?>$5$*X@{9PJ&ue7NxlX1l*=1{Gx(ex4 z^6H$~+3d01`c>=tJpV;`J%2@9jpL_NT;1~MDID(5*frfAd$#e|dG`lzWdQ4A=UsSty94OmU2`L& zxMKV&7eZW8_Ppyqt~7r2aWl!>Z+FPNRMIyib8C!SgoHpe=BPAwn~vKn2W3lovp#+~ zp0_-PIUYAPj;rQ8Mknm*Hj9Gt`sEfR50(w0MfSF*K7+;0PJY@I^AT}M=w!7YW(}Xq zmE0mL9SLhJ+OM>(_qqDQQYGXpNeG?1M-w&kTVeqku}s<=LAC@)G|@^vKhJZv`T3sC z&JLWWI3g$kqA4h%DV`zWog`DBid;q)QYA~@{*d7i<476WSf4fPbY+a>U1QUtO9G` zNHp=m48!5GaM+9Sh#=Y<4to6Aey5XWNn(y-uL7!xJzfAZ;E(f)`4te_q5beMxj+l? z5XiW|L(%M^J|rX`J~o#*2rPB+qw!lNk2w~Y%7+FY8z@#Fgg8fw4gitZB! zEI>GcY0Z?E3MrUa;^gi6?fxL zk|Q8=%=pZllrV|Tgg;2;4h_ZIht!1m(Fj{$BYe==$=TB9xSdiavogQzDEhaI{tkG< z5B^AWcEEJqVr27)?SQvmTu7F^A7v$-+@&I>Jb1g!p-%)w$4>q{_kv#V82o15>J)52VAEQ~L?}E3#0y2x$Rte@gek>V!Z_#! zoeqj3EX0p2#;edh)FolY;^H38R=i6MonRDy`L>kUCdFWm>JIqG?f~Rgr7GlZocbOwa#AW<>JBT!GNq|L+LP!y(KrZ91<^TP zwJd52)6djLXujF`7zCSPkYt910&sz-7RrgLZ4L==v#*~rA&5>Ru`tcrFlfR>l^P-} zMt7&s&GVogF`$ zzb0!%qRPU*N}8bLgtr3r_TBq8r=k30^i;&CU^sTiDAG zbO!m#P`wC(#}#V62m;OX4=Y92Oi^EL;DHnMUloW-+WPj?`<-0w@TZh<2_Bdi4|dP9 z5sqY{E4!;Kdd0aEcd)162Iw*PO)#p^dl7sNmCg%2KKvRe1&0KF_5^xyx6?#|;iX70 z^hq$>4he?WK!RbF1TLrVPcGq$4aQ_J3uXCusExc?aT2780YHiy!l zJAexpNS_&<=>^i5P$H%{F3xLDh@564ooQDwuT6-&BP5cU=&^B6PI1pBI`2LPIVA#S z=r_jwJWb{(l~5hFCQNaTb&BW&3zN&%Bo$WQi=X=Lf7D$=J^H9)9xdV* zm}4L|u`;%op;U?RzaKs7bb4^?;?M1CVaVZ<5S#w$6Lw{j(HPEDmf z-MxKBdk>HrS(AlYcfSmCuMX2B1_nlxMK^?>|Bg9cp26E??p7>-Qer!0g2}pi-5VjQ zcq)b&(^M)}yT%cusC(w8D>G4hogVz8s9@|Mn_c*7GWeXSsX@ zHmbq*Wo9x54pOuJb=LMHO;|nOKhhTg6d9u#x6!FdIz=NaNu&izo48XpV-rlV8u<&f zD7ji)UTH5!wPLYXicw?C?Wt^?;i*XNq=kg!Y-E`0$hsP=^%qX*Wc9Vq#5qADz0kDxo zTF4caZg#V!07+vdD0&?9`xh4%A<|fej8FQBDJA+p9KCw;_Vi6}PfgoD(F6-oOpv36 z%k!?2v7-x6+GE18(dvZ41t(Hpog&y+ULcM;F%goJQ6@_Sa##@oegUP7DFlb7aC92L z%fr*7)5o2UNALgs?x*+g@$ls2@a_AfH>dFK1YW&+`}*kp(Yv>&@b0g0`1T+0{n6Xk zj{y_o$orJ)d=iWUnd#{~?3~) - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. + +The order in which this function returns a secret password: + 1. Already existing 'Secret' resource + (If a 'Secret' resource is found under the name provided to the 'secret' parameter to this function and that 'Secret' resource contains a key with the name passed as the 'key' parameter to this function then the value of this existing secret password will be returned) + 2. Password provided via the values.yaml + (If one of the keys passed to the 'providedValues' parameter to this function is a valid path to a key in the values.yaml and has a value, the value of the first key with a value will be returned) + 3. Randomly generated secret password + (A new random secret password with the length specified in the 'length' parameter will be generated and returned) + +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secretData := (lookup "v1" "Secret" $.context.Release.Namespace .secret).data }} +{{- if $secretData }} + {{- if hasKey $secretData .key }} + {{- $password = index $secretData .key }} + {{- else }} + {{- printf "\nPASSWORDS ERROR: The secret \"%s\" does not contain the key \"%s\"\n" .secret .key | fail -}} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/charts/common/templates/_storage.tpl b/rds/base/charts/redis-cluster/charts/common/templates/_storage.tpl new file mode 100644 index 0000000..60e2a84 --- /dev/null +++ b/rds/base/charts/redis-cluster/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/rds/base/charts/redis-cluster/charts/common/templates/_tplvalues.tpl b/rds/base/charts/redis-cluster/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000..2db1668 --- /dev/null +++ b/rds/base/charts/redis-cluster/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/rds/base/charts/redis-cluster/charts/common/templates/_utils.tpl b/rds/base/charts/redis-cluster/charts/common/templates/_utils.tpl new file mode 100644 index 0000000..8c22b2a --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* 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 | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 -d) +{{- 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 -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/charts/common/templates/_warnings.tpl b/rds/base/charts/redis-cluster/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000..ae10fa4 --- /dev/null +++ b/rds/base/charts/redis-cluster/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/rds/base/charts/redis-cluster/charts/common/templates/validations/_cassandra.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000..ded1ae3 --- /dev/null +++ b/rds/base/charts/redis-cluster/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 (or (not $existingSecret) (eq $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/rds/base/charts/redis-cluster/charts/common/templates/validations/_mariadb.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000..b6906ff --- /dev/null +++ b/rds/base/charts/redis-cluster/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 (or (not $existingSecret) (eq $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/rds/base/charts/redis-cluster/charts/common/templates/validations/_mongodb.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000..f820ec1 --- /dev/null +++ b/rds/base/charts/redis-cluster/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 (or (not $existingSecret) (eq $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 MongoDB® 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/rds/base/charts/redis-cluster/charts/common/templates/validations/_mysql.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_mysql.tpl new file mode 100644 index 0000000..74472a0 --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/templates/validations/_mysql.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MySQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.mysql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MySQL values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mysql.passwords" -}} + {{- $existingSecret := include "common.mysql.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mysql.values.enabled" . -}} + {{- $architecture := include "common.mysql.values.architecture" . -}} + {{- $authPrefix := include "common.mysql.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 (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mysql-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" "mysql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mysql-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.mysql.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mysql. + +Usage: +{{ include "common.mysql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mysql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mysql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mysql.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mysql.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.key.auth" -}} + {{- if .subchart -}} + mysql.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/charts/common/templates/validations/_postgresql.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000..164ec0d --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* 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 (or (not $existingSecret) (eq $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/rds/base/charts/redis-cluster/charts/common/templates/validations/_redis.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000..dcccfc1 --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* 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" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "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 -}} + +{{/* +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 -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/charts/common/templates/validations/_validations.tpl b/rds/base/charts/redis-cluster/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000..9a814cf --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* 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" "subchart" "subchart" "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" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- 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=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/charts/common/values.yaml b/rds/base/charts/redis-cluster/charts/common/values.yaml new file mode 100644 index 0000000..f2df68e --- /dev/null +++ b/rds/base/charts/redis-cluster/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/rds/base/charts/redis-cluster/img/redis-cluster-topology.png b/rds/base/charts/redis-cluster/img/redis-cluster-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a02a9f8835381302731c9cb000b2835a45e7c9 GIT binary patch literal 11448 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 -- bash + +In order to replicate the container startup scripts execute this command: + + /opt/bitnami/scripts/redis-cluster/entrypoint.sh /opt/bitnami/scripts/redis-cluster/run.sh + +{{- else }} + +{{ if .Values.usePassword }} +To get your password run: + {{ include "common.utils.secret.getvalue" (dict "secret" $secretName "field" $secretPasswordKey "context" $) }} +{{- end }} + +{{- if .Values.cluster.externalAccess.enabled }} + +To connect to your Redis® server from outside the cluster check the following information: + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + You will have a different external IP for each Redis® node. Get the external ip from `-external` suffixed services: `kubectl get svc`. + Redis® port: {{ .Values.cluster.externalAccess.service.port }} + + {{- if not .Values.cluster.externalAccess.service.loadBalancerIP }} + Once the LoadBalancerIPs are ready, you need to provide them and perform a Helm Upgrade: + + helm upgrade --namespace {{ .Release.Namespace }} {{ .Release.Name }} --set "cluster.externalAccess.enabled=true,cluster.externalAccess.service.type=LoadBalancer{{- $root := . }}{{ $count := .Values.cluster.nodes | int }}{{ range $i, $v := until $count }},cluster.externalAccess.service.loadBalancerIP[{{ $i }}]=load-balancerip-{{- $i }}{{- end }}" bitnami/redis-cluster + Where loadbalancer-ip-i are the LoadBalancerIPs provided by the cluster. + {{- else -}} + {{- if .Values.cluster.init -}} + INFO: The Job to create the cluster will be created. + {{- end -}} + + To connect to your database from outside the cluster execute the following commands: + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") + redis-cli -c -h $SERVICE_IP -p {{ .Values.service.ports.redis }} {{- if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + {{- end }} + +{{- else }} + +You have deployed a Redis® Cluster accessible only from within you Kubernetes Cluster. + +{{- if .Values.cluster.init -}} +INFO: The Job to create the cluster will be created. +{{- end -}} + +To connect to your Redis® cluster: + +1. Run a Redis® pod that you can use as a client: + +{{- if .Values.tls.enabled }} + kubectl run --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }}-client --restart='Never' --env REDIS_PASSWORD=$REDIS_PASSWORD --image {{ template "redis-cluster.image" . }} --command -- sleep infinity + + Copy your TLS certificates to the pod: + + kubectl cp --namespace {{ .Release.Namespace }} /path/to/client.cert {{ template "common.names.fullname" . }}-client:/tmp/client.cert + kubectl cp --namespace {{ .Release.Namespace }} /path/to/client.key {{ template "common.names.fullname" . }}-client:/tmp/client.key + kubectl cp --namespace {{ .Release.Namespace }} /path/to/CA.cert {{ template "common.names.fullname" . }}-client:/tmp/CA.cert + + Use the following command to attach to the pod: + + kubectl exec --tty -i {{ template "common.names.fullname" . }}-client \ + {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "common.names.fullname" . }}-client=true" \{{- end }} + --namespace {{ .Release.Namespace }} -- bash +{{- else }} +kubectl run --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }}-client --rm --tty -i --restart='Never' \ +{{ if .Values.usePassword }} --env REDIS_PASSWORD=$REDIS_PASSWORD \{{ end }} +{{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "common.names.fullname" . }}-client=true" \{{- end }} +--image {{ template "redis-cluster.image" . }} -- bash +{{- end }} + +2. Connect using the Redis® CLI: + +redis-cli -c -h {{ template "common.names.fullname" . }}{{ if .Values.usePassword }} -a $REDIS_PASSWORD{{ end }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{ if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} +Note: Since NetworkPolicy is enabled, only pods with label +{{ template "common.names.fullname" . }}-client=true" +will be able to connect to redis. +{{- end -}} +{{- end -}} + +{{- include "redis-cluster.validateValues" . }} +{{- include "redis-cluster.checkRollingTags" . }} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} +{{- include "common.warnings.rollingTag" .Values.sysctlImage }} + +{{- if and .Values.usePassword (not .Values.existingSecret) -}} + + {{- $requiredPassword := dict "valueKey" "password" "secret" $secretName "field" $secretPasswordKey "context" $ -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" (list $requiredPasswordError) "context" $) -}} +{{- end -}} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/_helpers.tpl b/rds/base/charts/redis-cluster/templates/_helpers.tpl new file mode 100644 index 0000000..2c137aa --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/_helpers.tpl @@ -0,0 +1,254 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the proper Redis® image name +*/}} +{{- define "redis-cluster.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the metrics image) +*/}} +{{- define "redis-cluster.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 "redis-cluster.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return sysctl image +*/}} +{{- define "redis-cluster.sysctl.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.sysctlImage "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "redis-cluster.imagePullSecrets" -}} +{{- include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.metrics.image) "global" .Values.global) -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "networkPolicy.apiVersion" -}} +{{- if semverCompare ">=1.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 true if a TLS secret object should be created +*/}} +{{- define "redis-cluster.createTlsSecret" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated (not .Values.tls.existingSecret) (not .Values.tls.certificatesSecret) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing Redis TLS certificates +*/}} +{{- define "redis-cluster.tlsSecretName" -}} +{{- $secretName := coalesce .Values.tls.existingSecret .Values.tls.certificatesSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-crt" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "redis-cluster.tlsCert" -}} +{{- if (include "redis-cluster.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "tls.crt" -}} +{{- else -}} + {{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "redis-cluster.tlsCertKey" -}} +{{- if (include "redis-cluster.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "tls.key" -}} +{{- else -}} + {{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "redis-cluster.tlsCACert" -}} +{{- if (include "redis-cluster.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "ca.crt" -}} +{{- else -}} + {{- required "Certificate CA filename is required when TLS in enabled" .Values.tls.certCAFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the DH params file. +*/}} +{{- define "redis-cluster.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-cluster.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "common.names.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "redis-cluster.secretName" -}} +{{- if .Values.existingSecret -}} +{{- printf "%s" .Values.existingSecret -}} +{{- else -}} +{{- printf "%s" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password key to be retrieved from Redis® secret. +*/}} +{{- define "redis-cluster.secretPasswordKey" -}} +{{- if and .Values.existingSecret .Values.existingSecretPasswordKey -}} +{{- printf "%s" .Values.existingSecretPasswordKey -}} +{{- else -}} +{{- printf "redis-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Return Redis® password +*/}} +{{- define "redis-cluster.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 -}} + +{{/* +Determines whether or not to create the Statefulset +*/}} +{{- define "redis-cluster.createStatefulSet" -}} + {{- if not .Values.cluster.externalAccess.enabled -}} + {{- true -}} + {{- end -}} + {{- if and .Values.cluster.externalAccess.enabled .Values.cluster.externalAccess.service.loadBalancerIP -}} + {{- true -}} + {{- end -}} +{{- end -}} + +{{/* Check if there are rolling tags in the images */}} +{{- define "redis-cluster.checkRollingTags" -}} +{{- include "common.warnings.rollingTag" .Values.image -}} +{{- include "common.warnings.rollingTag" .Values.metrics.image -}} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "redis-cluster.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "redis-cluster.validateValues.updateParameters" .) -}} +{{- $messages := append $messages (include "redis-cluster.validateValues.tlsParameters" .) -}} +{{- $messages := append $messages (include "redis-cluster.validateValues.tls" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis® Cluster - check update parameters */}} +{{- define "redis-cluster.validateValues.updateParameters" -}} +{{- if and .Values.cluster.update.addNodes ( or (and .Values.cluster.externalAccess.enabled .Values.cluster.externalAccess.service.loadBalancerIP) ( not .Values.cluster.externalAccess.enabled )) -}} + {{- if .Values.cluster.externalAccess.enabled }} + {{- if not .Values.cluster.update.newExternalIPs -}} +redis-cluster: newExternalIPs + You must provide the newExternalIPs to perform the cluster upgrade when using external access. + {{- end -}} + {{- else }} + {{- if not .Values.cluster.update.currentNumberOfNodes -}} +redis-cluster: currentNumberOfNodes + You must provide the currentNumberOfNodes to perform an upgrade when not using external access. + {{- end -}} + {{- if not .Values.cluster.update.currentNumberOfReplicas -}} +redis-cluster: currentNumberOfReplicas + You must provide the currentNumberOfReplicas to perform an upgrade when not using external access. + {{- end -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis® Cluster - tls settings */}} +{{- define "redis-cluster.validateValues.tlsParameters" -}} +{{- if and .Values.tls.enabled (not .Values.tls.autoGenerated) }} +{{- if and (not .Values.tls.existingSecret) (not .Values.tls.certificatesSecret) -}} +redis-cluster: TLSSecretMissingSecret + A secret containing the certificates for the TLS traffic is required when TLS is enabled. Please set the tls.existingSecret value +{{- end -}} +{{- if not .Values.tls.certFilename -}} +redis-cluster: TLSSecretMissingCert + A certificate filename is required when TLS is enabled. Please set the tls.certFilename value +{{- end -}} +{{- if not .Values.tls.certKeyFilename -}} +redis-cluster: TLSSecretMissingCertKey + A certificate key filename is required when TLS is enabled. Please set the tls.certKeyFilename value +{{- end -}} +{{- if not .Values.tls.certCAFilename -}} +redis-cluster: TLSSecretMissingCertCA + A certificate CA filename is required when TLS is enabled. Please set the tls.certCAFilename value +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis® - PodSecurityPolicy create */}} +{{- define "redis-cluster.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.tls.autoGenerated) (not .Values.tls.existingSecret) (not .Values.tls.certificatesSecret) }} +redis-cluster: tls.enabled + In order to enable TLS, you also need to provide + an existing secret containing the TLS certificates or + enable auto-generated certificates. +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/configmap.yaml b/rds/base/charts/redis-cluster/templates/configmap.yaml new file mode 100644 index 0000000..375e8f6 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/configmap.yaml @@ -0,0 +1,1829 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }}-default + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + redis-default.conf: |- + # Redis configuration file example. + # + # Note that in order to read the configuration file, Redis must be + # started with the file path as first argument: + # + # ./redis-server /path/to/redis.conf + + # Note on units: when memory size is needed, it is possible to specify + # it in the usual form of 1k 5GB 4M and so forth: + # + # 1k => 1000 bytes + # 1kb => 1024 bytes + # 1m => 1000000 bytes + # 1mb => 1024*1024 bytes + # 1g => 1000000000 bytes + # 1gb => 1024*1024*1024 bytes + # + # units are case insensitive so 1GB 1Gb 1gB are all the same. + + ################################## INCLUDES ################################### + + # Include one or more other config files here. This is useful if you + # have a standard template that goes to all Redis servers but also need + # to customize a few per-server settings. Include files can include + # other files, so use this wisely. + # + # Notice option "include" won't be rewritten by command "CONFIG REWRITE" + # from admin or Redis Sentinel. Since Redis always uses the last processed + # line as value of a configuration directive, you'd better put includes + # at the beginning of this file to avoid overwriting config change at runtime. + # + # If instead you are interested in using includes to override configuration + # options, it is better to use include as the last line. + # + # include /path/to/local.conf + # include /path/to/other.conf + + ################################## MODULES ##################################### + + # Load modules at startup. If the server is not able to load modules + # it will abort. It is possible to use multiple loadmodule directives. + # + # loadmodule /path/to/my_module.so + # loadmodule /path/to/other_module.so + + ################################## NETWORK ##################################### + + # By default, if no "bind" configuration directive is specified, Redis listens + # for connections from all the network interfaces available on the server. + # It is possible to listen to just one or multiple selected interfaces using + # the "bind" configuration directive, followed by one or more IP addresses. + # + # Examples: + # + # bind 192.168.1.100 10.0.0.1 + # bind 127.0.0.1 ::1 + # + # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the + # internet, binding to all the interfaces is dangerous and will expose the + # instance to everybody on the internet. So by default we uncomment the + # following bind directive, that will force Redis to listen only into + # the IPv4 loopback interface address (this means Redis will be able to + # accept connections only from clients running into the same computer it + # is running). + # + # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES + # JUST COMMENT THE FOLLOWING LINE. + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + bind 127.0.0.1 + + # Protected mode is a layer of security protection, in order to avoid that + # Redis instances left open on the internet are accessed and exploited. + # + # When protected mode is on and if: + # + # 1) The server is not binding explicitly to a set of addresses using the + # "bind" directive. + # 2) No password is configured. + # + # The server only accepts connections from clients connecting from the + # IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain + # sockets. + # + # By default protected mode is enabled. You should disable it only if + # you are sure you want clients from other hosts to connect to Redis + # even if no authentication is configured, nor a specific set of interfaces + # are explicitly listed using the "bind" directive. + protected-mode yes + + # Accept connections on the specified port, default is 6379 (IANA #815344). + # If port 0 is specified Redis will not listen on a TCP socket. + port 6379 + + # TCP listen() backlog. + # + # In high requests-per-second environments you need an high backlog in order + # to avoid slow clients connections issues. Note that the Linux kernel + # will silently truncate it to the value of /proc/sys/net/core/somaxconn so + # make sure to raise both the value of somaxconn and tcp_max_syn_backlog + # in order to get the desired effect. + tcp-backlog 511 + + # Unix socket. + # + # Specify the path for the Unix socket that will be used to listen for + # incoming connections. There is no default, so Redis will not listen + # on a unix socket when not specified. + # + # unixsocket /tmp/redis.sock + # unixsocketperm 700 + + # Close the connection after a client is idle for N seconds (0 to disable) + timeout 0 + + # TCP keepalive. + # + # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence + # of communication. This is useful for two reasons: + # + # 1) Detect dead peers. + # 2) Take the connection alive from the point of view of network + # equipment in the middle. + # + # On Linux, the specified value (in seconds) is the period used to send ACKs. + # Note that to close the connection the double of the time is needed. + # On other kernels the period depends on the kernel configuration. + # + # A reasonable value for this option is 300 seconds, which is the new + # Redis default starting with Redis 3.2.1. + tcp-keepalive 300 + + ################################# TLS/SSL ##################################### + + # By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration + # directive can be used to define TLS-listening ports. To enable TLS on the + # default port, use: + # + # port 0 + # tls-port 6379 + + # Configure a X.509 certificate and private key to use for authenticating the + # server to connected clients, masters or cluster peers. These files should be + # PEM formatted. + # + # tls-cert-file redis.crt + # tls-key-file redis.key + + # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: + # + # tls-dh-params-file redis.dh + + # Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL + # clients and peers. Redis requires an explicit configuration of at least one + # of these, and will not implicitly use the system wide configuration. + # + # tls-ca-cert-file ca.crt + # tls-ca-cert-dir /etc/ssl/certs + + # By default, clients (including replica servers) on a TLS port are required + # to authenticate using valid client side certificates. + # + # It is possible to disable authentication using this directive. + # + # tls-auth-clients no + + # By default, a Redis replica does not attempt to establish a TLS connection + # with its master. + # + # Use the following directive to enable TLS on replication links. + # + # tls-replication yes + + # By default, the Redis Cluster bus uses a plain TCP connection. To enable + # TLS for the bus protocol, use the following directive: + # + # tls-cluster yes + + # Explicitly specify TLS versions to support. Allowed values are case insensitive + # and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or + # any combination. To enable only TLSv1.2 and TLSv1.3, use: + # + # tls-protocols "TLSv1.2 TLSv1.3" + + # Configure allowed ciphers. See the ciphers(1ssl) manpage for more information + # about the syntax of this string. + # + # Note: this configuration applies only to <= TLSv1.2. + # + # tls-ciphers DEFAULT:!MEDIUM + + # Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more + # information about the syntax of this string, and specifically for TLSv1.3 + # ciphersuites. + # + # tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + + # When choosing a cipher, use the server's preference instead of the client + # preference. By default, the server follows the client's preference. + # + # tls-prefer-server-ciphers yes + + ################################# GENERAL ##################################### + + # By default Redis does not run as a daemon. Use 'yes' if you need it. + # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. + daemonize no + + # If you run Redis from upstart or systemd, Redis can interact with your + # supervision tree. Options: + # supervised no - no supervision interaction + # supervised upstart - signal upstart by putting Redis into SIGSTOP mode + # supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET + # supervised auto - detect upstart or systemd method based on + # UPSTART_JOB or NOTIFY_SOCKET environment variables + # Note: these supervision methods only signal "process is ready." + # They do not enable continuous liveness pings back to your supervisor. + supervised no + + # If a pid file is specified, Redis writes it where specified at startup + # and removes it at exit. + # + # When the server runs non daemonized, no pid file is created if none is + # specified in the configuration. When the server is daemonized, the pid file + # is used even if not specified, defaulting to "/var/run/redis.pid". + # + # Creating a pid file is best effort: if Redis is not able to create it + # nothing bad happens, the server will start and run normally. + pidfile /opt/bitnami/redis/tmp/redis_6379.pid + + # Specify the server verbosity level. + # This can be one of: + # debug (a lot of information, useful for development/testing) + # verbose (many rarely useful info, but not a mess like the debug level) + # notice (moderately verbose, what you want in production probably) + # warning (only very important / critical messages are logged) + loglevel notice + + # Specify the log file name. Also the empty string can be used to force + # Redis to log on the standard output. Note that if you use standard + # output for logging but daemonize, logs will be sent to /dev/null + logfile "" + + # To enable logging to the system logger, just set 'syslog-enabled' to yes, + # and optionally update the other syslog parameters to suit your needs. + # syslog-enabled no + + # Specify the syslog identity. + # syslog-ident redis + + # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. + # syslog-facility local0 + + # Set the number of databases. The default database is DB 0, you can select + # a different one on a per-connection basis using SELECT where + # dbid is a number between 0 and 'databases'-1 + databases 16 + + # By default Redis shows an ASCII art logo only when started to log to the + # standard output and if the standard output is a TTY. Basically this means + # that normally a logo is displayed only in interactive sessions. + # + # However it is possible to force the pre-4.0 behavior and always show a + # ASCII art logo in startup logs by setting the following option to yes. + always-show-logo yes + + ################################ SNAPSHOTTING ################################ + # + # Save the DB on disk: + # + # save + # + # Will save the DB if both the given number of seconds and the given + # number of write operations against the DB occurred. + # + # In the example below the behaviour will be to save: + # after 900 sec (15 min) if at least 1 key changed + # after 300 sec (5 min) if at least 10 keys changed + # after 60 sec if at least 10000 keys changed + # + # Note: you can disable saving completely by commenting out all "save" lines. + # + # It is also possible to remove all the previously configured save + # points by adding a save directive with a single empty string argument + # like in the following example: + # + # save "" + + save 900 1 + save 300 10 + save 60 10000 + + # By default Redis will stop accepting writes if RDB snapshots are enabled + # (at least one save point) and the latest background save failed. + # This will make the user aware (in a hard way) that data is not persisting + # on disk properly, otherwise chances are that no one will notice and some + # disaster will happen. + # + # If the background saving process will start working again Redis will + # automatically allow writes again. + # + # However if you have setup your proper monitoring of the Redis server + # and persistence, you may want to disable this feature so that Redis will + # continue to work as usual even if there are problems with disk, + # permissions, and so forth. + stop-writes-on-bgsave-error yes + + # Compress string objects using LZF when dump .rdb databases? + # For default that's set to 'yes' as it's almost always a win. + # If you want to save some CPU in the saving child set it to 'no' but + # the dataset will likely be bigger if you have compressible values or keys. + rdbcompression yes + + # Since version 5 of RDB a CRC64 checksum is placed at the end of the file. + # This makes the format more resistant to corruption but there is a performance + # hit to pay (around 10%) when saving and loading RDB files, so you can disable it + # for maximum performances. + # + # RDB files created with checksum disabled have a checksum of zero that will + # tell the loading code to skip the check. + rdbchecksum yes + + # The filename where to dump the DB + dbfilename dump.rdb + + # Remove RDB files used by replication in instances without persistence + # enabled. By default this option is disabled, however there are environments + # where for regulations or other security concerns, RDB files persisted on + # disk by masters in order to feed replicas, or stored on disk by replicas + # in order to load them for the initial synchronization, should be deleted + # ASAP. Note that this option ONLY WORKS in instances that have both AOF + # and RDB persistence disabled, otherwise is completely ignored. + # + # An alternative (and sometimes better) way to obtain the same effect is + # to use diskless replication on both master and replicas instances. However + # in the case of replicas, diskless is not always an option. + rdb-del-sync-files no + + # The working directory. + # + # The DB will be written inside this directory, with the filename specified + # above using the 'dbfilename' configuration directive. + # + # The Append Only File will also be created inside this directory. + # + # Note that you must specify a directory here, not a file name. + dir /bitnami/redis/data + + ################################# REPLICATION ################################# + + # Master-Replica replication. Use replicaof to make a Redis instance a copy of + # another Redis server. A few things to understand ASAP about Redis replication. + # + # +------------------+ +---------------+ + # | Master | ---> | Replica | + # | (receive writes) | | (exact copy) | + # +------------------+ +---------------+ + # + # 1) Redis replication is asynchronous, but you can configure a master to + # stop accepting writes if it appears to be not connected with at least + # a given number of replicas. + # 2) Redis replicas are able to perform a partial resynchronization with the + # master if the replication link is lost for a relatively small amount of + # time. You may want to configure the replication backlog size (see the next + # sections of this file) with a sensible value depending on your needs. + # 3) Replication is automatic and does not need user intervention. After a + # network partition replicas automatically try to reconnect to masters + # and resynchronize with them. + # + # replicaof + + # If the master is password protected (using the "requirepass" configuration + # directive below) it is possible to tell the replica to authenticate before + # starting the replication synchronization process, otherwise the master will + # refuse the replica request. + # + # masterauth + # + # However this is not enough if you are using Redis ACLs (for Redis version + # 6 or greater), and the default user is not capable of running the PSYNC + # command and/or other commands needed for replication. In this case it's + # better to configure a special user to use with replication, and specify the + # masteruser configuration as such: + # + # masteruser + # + # When masteruser is specified, the replica will authenticate against its + # master using the new AUTH form: AUTH . + + # When a replica loses its connection with the master, or when the replication + # is still in progress, the replica can act in two different ways: + # + # 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will + # still reply to client requests, possibly with out of date data, or the + # data set may just be empty if this is the first synchronization. + # + # 2) if replica-serve-stale-data is set to 'no' the replica will reply with + # an error "SYNC with master in progress" to all the kind of commands + # but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, + # SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, + # COMMAND, POST, HOST: and LATENCY. + # + replica-serve-stale-data yes + + # You can configure a replica instance to accept writes or not. Writing against + # a replica instance may be useful to store some ephemeral data (because data + # written on a replica will be easily deleted after resync with the master) but + # may also cause problems if clients are writing to it because of a + # misconfiguration. + # + # Since Redis 2.6 by default replicas are read-only. + # + # Note: read only replicas are not designed to be exposed to untrusted clients + # on the internet. It's just a protection layer against misuse of the instance. + # Still a read only replica exports by default all the administrative commands + # such as CONFIG, DEBUG, and so forth. To a limited extent you can improve + # security of read only replicas using 'rename-command' to shadow all the + # administrative / dangerous commands. + replica-read-only yes + + # Replication SYNC strategy: disk or socket. + # + # New replicas and reconnecting replicas that are not able to continue the + # replication process just receiving differences, need to do what is called a + # "full synchronization". An RDB file is transmitted from the master to the + # replicas. + # + # The transmission can happen in two different ways: + # + # 1) Disk-backed: The Redis master creates a new process that writes the RDB + # file on disk. Later the file is transferred by the parent + # process to the replicas incrementally. + # 2) Diskless: The Redis master creates a new process that directly writes the + # RDB file to replica sockets, without touching the disk at all. + # + # With disk-backed replication, while the RDB file is generated, more replicas + # can be queued and served with the RDB file as soon as the current child + # producing the RDB file finishes its work. With diskless replication instead + # once the transfer starts, new replicas arriving will be queued and a new + # transfer will start when the current one terminates. + # + # When diskless replication is used, the master waits a configurable amount of + # time (in seconds) before starting the transfer in the hope that multiple + # replicas will arrive and the transfer can be parallelized. + # + # With slow disks and fast (large bandwidth) networks, diskless replication + # works better. + repl-diskless-sync no + + # When diskless replication is enabled, it is possible to configure the delay + # the server waits in order to spawn the child that transfers the RDB via socket + # to the replicas. + # + # This is important since once the transfer starts, it is not possible to serve + # new replicas arriving, that will be queued for the next RDB transfer, so the + # server waits a delay in order to let more replicas arrive. + # + # The delay is specified in seconds, and by default is 5 seconds. To disable + # it entirely just set it to 0 seconds and the transfer will start ASAP. + repl-diskless-sync-delay 5 + + # ----------------------------------------------------------------------------- + # WARNING: RDB diskless load is experimental. Since in this setup the replica + # does not immediately store an RDB on disk, it may cause data loss during + # failovers. RDB diskless load + Redis modules not handling I/O reads may also + # cause Redis to abort in case of I/O errors during the initial synchronization + # stage with the master. Use only if your do what you are doing. + # ----------------------------------------------------------------------------- + # + # Replica can load the RDB it reads from the replication link directly from the + # socket, or store the RDB to a file and read that file after it was completely + # received from the master. + # + # In many cases the disk is slower than the network, and storing and loading + # the RDB file may increase replication time (and even increase the master's + # Copy on Write memory and salve buffers). + # However, parsing the RDB file directly from the socket may mean that we have + # to flush the contents of the current database before the full rdb was + # received. For this reason we have the following options: + # + # "disabled" - Don't use diskless load (store the rdb file to the disk first) + # "on-empty-db" - Use diskless load only when it is completely safe. + # "swapdb" - Keep a copy of the current db contents in RAM while parsing + # the data directly from the socket. note that this requires + # sufficient memory, if you don't have it, you risk an OOM kill. + repl-diskless-load disabled + + # Replicas send PINGs to server in a predefined interval. It's possible to + # change this interval with the repl_ping_replica_period option. The default + # value is 10 seconds. + # + # repl-ping-replica-period 10 + + # The following option sets the replication timeout for: + # + # 1) Bulk transfer I/O during SYNC, from the point of view of replica. + # 2) Master timeout from the point of view of replicas (data, pings). + # 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). + # + # It is important to make sure that this value is greater than the value + # specified for repl-ping-replica-period otherwise a timeout will be detected + # every time there is low traffic between the master and the replica. + # + # repl-timeout 60 + + # Disable TCP_NODELAY on the replica socket after SYNC? + # + # If you select "yes" Redis will use a smaller number of TCP packets and + # less bandwidth to send data to replicas. But this can add a delay for + # the data to appear on the replica side, up to 40 milliseconds with + # Linux kernels using a default configuration. + # + # If you select "no" the delay for data to appear on the replica side will + # be reduced but more bandwidth will be used for replication. + # + # By default we optimize for low latency, but in very high traffic conditions + # or when the master and replicas are many hops away, turning this to "yes" may + # be a good idea. + repl-disable-tcp-nodelay no + + # Set the replication backlog size. The backlog is a buffer that accumulates + # replica data when replicas are disconnected for some time, so that when a + # replica wants to reconnect again, often a full resync is not needed, but a + # partial resync is enough, just passing the portion of data the replica + # missed while disconnected. + # + # The bigger the replication backlog, the longer the time the replica can be + # disconnected and later be able to perform a partial resynchronization. + # + # The backlog is only allocated once there is at least a replica connected. + # + # repl-backlog-size 1mb + + # After a master has no longer connected replicas for some time, the backlog + # will be freed. The following option configures the amount of seconds that + # need to elapse, starting from the time the last replica disconnected, for + # the backlog buffer to be freed. + # + # Note that replicas never free the backlog for timeout, since they may be + # promoted to masters later, and should be able to correctly "partially + # resynchronize" with the replicas: hence they should always accumulate backlog. + # + # A value of 0 means to never release the backlog. + # + # repl-backlog-ttl 3600 + + # The replica priority is an integer number published by Redis in the INFO + # output. It is used by Redis Sentinel in order to select a replica to promote + # into a master if the master is no longer working correctly. + # + # A replica with a low priority number is considered better for promotion, so + # for instance if there are three replicas with priority 10, 100, 25 Sentinel + # will pick the one with priority 10, that is the lowest. + # + # However a special priority of 0 marks the replica as not able to perform the + # role of master, so a replica with priority of 0 will never be selected by + # Redis Sentinel for promotion. + # + # By default the priority is 100. + replica-priority 100 + + # It is possible for a master to stop accepting writes if there are less than + # N replicas connected, having a lag less or equal than M seconds. + # + # The N replicas need to be in "online" state. + # + # The lag in seconds, that must be <= the specified value, is calculated from + # the last ping received from the replica, that is usually sent every second. + # + # This option does not GUARANTEE that N replicas will accept the write, but + # will limit the window of exposure for lost writes in case not enough replicas + # are available, to the specified number of seconds. + # + # For example to require at least 3 replicas with a lag <= 10 seconds use: + # + # min-replicas-to-write 3 + # min-replicas-max-lag 10 + # + # Setting one or the other to 0 disables the feature. + # + # By default min-replicas-to-write is set to 0 (feature disabled) and + # min-replicas-max-lag is set to 10. + + # A Redis master is able to list the address and port of the attached + # replicas in different ways. For example the "INFO replication" section + # offers this information, which is used, among other tools, by + # Redis Sentinel in order to discover replica instances. + # Another place where this info is available is in the output of the + # "ROLE" command of a master. + # + # The listed IP and address normally reported by a replica is obtained + # in the following way: + # + # IP: The address is auto detected by checking the peer address + # of the socket used by the replica to connect with the master. + # + # Port: The port is communicated by the replica during the replication + # handshake, and is normally the port that the replica is using to + # listen for connections. + # + # However when port forwarding or Network Address Translation (NAT) is + # used, the replica may be actually reachable via different IP and port + # pairs. The following two options can be used by a replica in order to + # report to its master a specific set of IP and port, so that both INFO + # and ROLE will report those values. + # + # There is no need to use both the options if you need to override just + # the port or the IP address. + # + # replica-announce-ip 5.5.5.5 + # replica-announce-port 1234 + + ############################### KEYS TRACKING ################################# + + # Redis implements server assisted support for client side caching of values. + # This is implemented using an invalidation table that remembers, using + # 16 millions of slots, what clients may have certain subsets of keys. In turn + # this is used in order to send invalidation messages to clients. Please + # to understand more about the feature check this page: + # + # https://redis.io/topics/client-side-caching + # + # When tracking is enabled for a client, all the read only queries are assumed + # to be cached: this will force Redis to store information in the invalidation + # table. When keys are modified, such information is flushed away, and + # invalidation messages are sent to the clients. However if the workload is + # heavily dominated by reads, Redis could use more and more memory in order + # to track the keys fetched by many clients. + # + # For this reason it is possible to configure a maximum fill value for the + # invalidation table. By default it is set to 1M of keys, and once this limit + # is reached, Redis will start to evict keys in the invalidation table + # even if they were not modified, just to reclaim memory: this will in turn + # force the clients to invalidate the cached values. Basically the table + # maximum size is a trade off between the memory you want to spend server + # side to track information about who cached what, and the ability of clients + # to retain cached objects in memory. + # + # If you set the value to 0, it means there are no limits, and Redis will + # retain as many keys as needed in the invalidation table. + # In the "stats" INFO section, you can find information about the number of + # keys in the invalidation table at every given moment. + # + # Note: when key tracking is used in broadcasting mode, no memory is used + # in the server side so this setting is useless. + # + # tracking-table-max-keys 1000000 + + ################################## SECURITY ################################### + + # Warning: since Redis is pretty fast an outside user can try up to + # 1 million passwords per second against a modern box. This means that you + # should use very strong passwords, otherwise they will be very easy to break. + # Note that because the password is really a shared secret between the client + # and the server, and should not be memorized by any human, the password + # can be easily a long string from /dev/urandom or whatever, so by using a + # long and unguessable password no brute force attack will be possible. + + # Redis ACL users are defined in the following format: + # + # user ... acl rules ... + # + # For example: + # + # user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 + # + # The special username "default" is used for new connections. If this user + # has the "nopass" rule, then new connections will be immediately authenticated + # as the "default" user without the need of any password provided via the + # AUTH command. Otherwise if the "default" user is not flagged with "nopass" + # the connections will start in not authenticated state, and will require + # AUTH (or the HELLO command AUTH option) in order to be authenticated and + # start to work. + # + # The ACL rules that describe what an user can do are the following: + # + # on Enable the user: it is possible to authenticate as this user. + # off Disable the user: it's no longer possible to authenticate + # with this user, however the already authenticated connections + # will still work. + # + Allow the execution of that command + # - Disallow the execution of that command + # +@ Allow the execution of all the commands in such category + # with valid categories are like @admin, @set, @sortedset, ... + # and so forth, see the full list in the server.c file where + # the Redis command table is described and defined. + # The special category @all means all the commands, but currently + # present in the server, and that will be loaded in the future + # via modules. + # +|subcommand Allow a specific subcommand of an otherwise + # disabled command. Note that this form is not + # allowed as negative like -DEBUG|SEGFAULT, but + # only additive starting with "+". + # allcommands Alias for +@all. Note that it implies the ability to execute + # all the future commands loaded via the modules system. + # nocommands Alias for -@all. + # ~ Add a pattern of keys that can be mentioned as part of + # commands. For instance ~* allows all the keys. The pattern + # is a glob-style pattern like the one of KEYS. + # It is possible to specify multiple patterns. + # allkeys Alias for ~* + # resetkeys Flush the list of allowed keys patterns. + # > Add this password to the list of valid password for the user. + # For example >mypass will add "mypass" to the list. + # This directive clears the "nopass" flag (see later). + # < Remove this password from the list of valid passwords. + # nopass All the set passwords of the user are removed, and the user + # is flagged as requiring no password: it means that every + # password will work against this user. If this directive is + # used for the default user, every new connection will be + # immediately authenticated with the default user without + # any explicit AUTH command required. Note that the "resetpass" + # directive will clear this condition. + # resetpass Flush the list of allowed passwords. Moreover removes the + # "nopass" status. After "resetpass" the user has no associated + # passwords and there is no way to authenticate without adding + # some password (or setting it as "nopass" later). + # reset Performs the following actions: resetpass, resetkeys, off, + # -@all. The user returns to the same state it has immediately + # after its creation. + # + # ACL rules can be specified in any order: for instance you can start with + # passwords, then flags, or key patterns. However note that the additive + # and subtractive rules will CHANGE MEANING depending on the ordering. + # For instance see the following example: + # + # user alice on +@all -DEBUG ~* >somepassword + # + # This will allow "alice" to use all the commands with the exception of the + # DEBUG command, since +@all added all the commands to the set of the commands + # alice can use, and later DEBUG was removed. However if we invert the order + # of two ACL rules the result will be different: + # + # user alice on -DEBUG +@all ~* >somepassword + # + # Now DEBUG was removed when alice had yet no commands in the set of allowed + # commands, later all the commands are added, so the user will be able to + # execute everything. + # + # Basically ACL rules are processed left-to-right. + # + # For more information about ACL configuration please refer to + # the Redis web site at https://redis.io/topics/acl + + # ACL LOG + # + # The ACL Log tracks failed commands and authentication events associated + # with ACLs. The ACL Log is useful to troubleshoot failed commands blocked + # by ACLs. The ACL Log is stored in and consumes memory. There is no limit + # to its length.You can reclaim memory with ACL LOG RESET or set a maximum + # length below. + acllog-max-len 128 + + # Using an external ACL file + # + # Instead of configuring users here in this file, it is possible to use + # a stand-alone file just listing users. The two methods cannot be mixed: + # if you configure users here and at the same time you activate the exteranl + # ACL file, the server will refuse to start. + # + # The format of the external ACL user file is exactly the same as the + # format that is used inside redis.conf to describe users. + # + # aclfile /etc/redis/users.acl + + # IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility + # layer on top of the new ACL system. The option effect will be just setting + # the password for the default user. Clients will still authenticate using + # AUTH as usually, or more explicitly with AUTH default + # if they follow the new protocol: both will work. + # + # requirepass foobared + + # Command renaming (DEPRECATED). + # + # ------------------------------------------------------------------------ + # WARNING: avoid using this option if possible. Instead use ACLs to remove + # commands from the default user, and put them only in some admin user you + # create for administrative purposes. + # ------------------------------------------------------------------------ + # + # It is possible to change the name of dangerous commands in a shared + # environment. For instance the CONFIG command may be renamed into something + # hard to guess so that it will still be available for internal-use tools + # but not available for general clients. + # + # Example: + # + # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 + # + # It is also possible to completely kill a command by renaming it into + # an empty string: + # + # rename-command CONFIG "" + # + # Please note that changing the name of commands that are logged into the + # AOF file or transmitted to replicas may cause problems. + + ################################### CLIENTS #################################### + + # Set the max number of connected clients at the same time. By default + # this limit is set to 10000 clients, however if the Redis server is not + # able to configure the process file limit to allow for the specified limit + # the max number of allowed clients is set to the current file limit + # minus 32 (as Redis reserves a few file descriptors for internal uses). + # + # Once the limit is reached Redis will close all the new connections sending + # an error 'max number of clients reached'. + # + # maxclients 10000 + + ############################## MEMORY MANAGEMENT ################################ + + # Set a memory usage limit to the specified amount of bytes. + # When the memory limit is reached Redis will try to remove keys + # according to the eviction policy selected (see maxmemory-policy). + # + # If Redis can't remove keys according to the policy, or if the policy is + # set to 'noeviction', Redis will start to reply with errors to commands + # that would use more memory, like SET, LPUSH, and so on, and will continue + # to reply to read-only commands like GET. + # + # This option is usually useful when using Redis as an LRU or LFU cache, or to + # set a hard memory limit for an instance (using the 'noeviction' policy). + # + # WARNING: If you have replicas attached to an instance with maxmemory on, + # the size of the output buffers needed to feed the replicas are subtracted + # from the used memory count, so that network problems / resyncs will + # not trigger a loop where keys are evicted, and in turn the output + # buffer of replicas is full with DELs of keys evicted triggering the deletion + # of more keys, and so forth until the database is completely emptied. + # + # In short... if you have replicas attached it is suggested that you set a lower + # limit for maxmemory so that there is some free RAM on the system for replica + # output buffers (but this is not needed if the policy is 'noeviction'). + # + # maxmemory + + # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory + # is reached. You can select one from the following behaviors: + # + # volatile-lru -> Evict using approximated LRU, only keys with an expire set. + # allkeys-lru -> Evict any key using approximated LRU. + # volatile-lfu -> Evict using approximated LFU, only keys with an expire set. + # allkeys-lfu -> Evict any key using approximated LFU. + # volatile-random -> Remove a random key having an expire set. + # allkeys-random -> Remove a random key, any key. + # volatile-ttl -> Remove the key with the nearest expire time (minor TTL) + # noeviction -> Don't evict anything, just return an error on write operations. + # + # LRU means Least Recently Used + # LFU means Least Frequently Used + # + # Both LRU, LFU and volatile-ttl are implemented using approximated + # randomized algorithms. + # + # Note: with any of the above policies, Redis will return an error on write + # operations, when there are no suitable keys for eviction. + # + # At the date of writing these commands are: set setnx setex append + # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd + # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby + # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby + # getset mset msetnx exec sort + # + # The default is: + # + # maxmemory-policy noeviction + + # LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated + # algorithms (in order to save memory), so you can tune it for speed or + # accuracy. For default Redis will check five keys and pick the one that was + # used less recently, you can change the sample size using the following + # configuration directive. + # + # The default of 5 produces good enough results. 10 Approximates very closely + # true LRU but costs more CPU. 3 is faster but not very accurate. + # + # maxmemory-samples 5 + + # Starting from Redis 5, by default a replica will ignore its maxmemory setting + # (unless it is promoted to master after a failover or manually). It means + # that the eviction of keys will be just handled by the master, sending the + # DEL commands to the replica as keys evict in the master side. + # + # This behavior ensures that masters and replicas stay consistent, and is usually + # what you want, however if your replica is writable, or you want the replica + # to have a different memory setting, and you are sure all the writes performed + # to the replica are idempotent, then you may change this default (but be sure + # to understand what you are doing). + # + # Note that since the replica by default does not evict, it may end using more + # memory than the one set via maxmemory (there are certain buffers that may + # be larger on the replica, or data structures may sometimes take more memory + # and so forth). So make sure you monitor your replicas and make sure they + # have enough memory to never hit a real out-of-memory condition before the + # master hits the configured maxmemory setting. + # + # replica-ignore-maxmemory yes + + # Redis reclaims expired keys in two ways: upon access when those keys are + # found to be expired, and also in background, in what is called the + # "active expire key". The key space is slowly and interactively scanned + # looking for expired keys to reclaim, so that it is possible to free memory + # of keys that are expired and will never be accessed again in a short time. + # + # The default effort of the expire cycle will try to avoid having more than + # ten percent of expired keys still in memory, and will try to avoid consuming + # more than 25% of total memory and to add latency to the system. However + # it is possible to increase the expire "effort" that is normally set to + # "1", to a greater value, up to the value "10". At its maximum value the + # system will use more CPU, longer cycles (and technically may introduce + # more latency), and will tollerate less already expired keys still present + # in the system. It's a tradeoff between memory, CPU and latecy. + # + # active-expire-effort 1 + + ############################# LAZY FREEING #################################### + + # Redis has two primitives to delete keys. One is called DEL and is a blocking + # deletion of the object. It means that the server stops processing new commands + # in order to reclaim all the memory associated with an object in a synchronous + # way. If the key deleted is associated with a small object, the time needed + # in order to execute the DEL command is very small and comparable to most other + # O(1) or O(log_N) commands in Redis. However if the key is associated with an + # aggregated value containing millions of elements, the server can block for + # a long time (even seconds) in order to complete the operation. + # + # For the above reasons Redis also offers non blocking deletion primitives + # such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and + # FLUSHDB commands, in order to reclaim memory in background. Those commands + # are executed in constant time. Another thread will incrementally free the + # object in the background as fast as possible. + # + # DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. + # It's up to the design of the application to understand when it is a good + # idea to use one or the other. However the Redis server sometimes has to + # delete keys or flush the whole database as a side effect of other operations. + # Specifically Redis deletes objects independently of a user call in the + # following scenarios: + # + # 1) On eviction, because of the maxmemory and maxmemory policy configurations, + # in order to make room for new data, without going over the specified + # memory limit. + # 2) Because of expire: when a key with an associated time to live (see the + # EXPIRE command) must be deleted from memory. + # 3) Because of a side effect of a command that stores data on a key that may + # already exist. For example the RENAME command may delete the old key + # content when it is replaced with another one. Similarly SUNIONSTORE + # or SORT with STORE option may delete existing keys. The SET command + # itself removes any old content of the specified key in order to replace + # it with the specified string. + # 4) During replication, when a replica performs a full resynchronization with + # its master, the content of the whole database is removed in order to + # load the RDB file just transferred. + # + # In all the above cases the default is to delete objects in a blocking way, + # like if DEL was called. However you can configure each case specifically + # in order to instead release memory in a non-blocking way like if UNLINK + # was called, using the following configuration directives. + + lazyfree-lazy-eviction no + lazyfree-lazy-expire no + lazyfree-lazy-server-del no + replica-lazy-flush no + + # It is also possible, for the case when to replace the user code DEL calls + # with UNLINK calls is not easy, to modify the default behavior of the DEL + # command to act exactly like UNLINK, using the following configuration + # directive: + + lazyfree-lazy-user-del no + + ################################ THREADED I/O ################################# + + # Redis is mostly single threaded, however there are certain threaded + # operations such as UNLINK, slow I/O accesses and other things that are + # performed on side threads. + # + # Now it is also possible to handle Redis clients socket reads and writes + # in different I/O threads. Since especially writing is so slow, normally + # Redis users use pipelining in order to speedup the Redis performances per + # core, and spawn multiple instances in order to scale more. Using I/O + # threads it is possible to easily speedup two times Redis without resorting + # to pipelining nor sharding of the instance. + # + # By default threading is disabled, we suggest enabling it only in machines + # that have at least 4 or more cores, leaving at least one spare core. + # Using more than 8 threads is unlikely to help much. We also recommend using + # threaded I/O only if you actually have performance problems, with Redis + # instances being able to use a quite big percentage of CPU time, otherwise + # there is no point in using this feature. + # + # So for instance if you have a four cores boxes, try to use 2 or 3 I/O + # threads, if you have a 8 cores, try to use 6 threads. In order to + # enable I/O threads use the following configuration directive: + # + # io-threads 4 + # + # Setting io-threads to 1 will just use the main thread as usually. + # When I/O threads are enabled, we only use threads for writes, that is + # to thread the write(2) syscall and transfer the client buffers to the + # socket. However it is also possible to enable threading of reads and + # protocol parsing using the following configuration directive, by setting + # it to yes: + # + # io-threads-do-reads no + # + # Usually threading reads doesn't help much. + # + # NOTE 1: This configuration directive cannot be changed at runtime via + # CONFIG SET. Aso this feature currently does not work when SSL is + # enabled. + # + # NOTE 2: If you want to test the Redis speedup using redis-benchmark, make + # sure you also run the benchmark itself in threaded mode, using the + # --threads option to match the number of Redis theads, otherwise you'll not + # be able to notice the improvements. + + ############################## APPEND ONLY MODE ############################### + + # By default Redis asynchronously dumps the dataset on disk. This mode is + # good enough in many applications, but an issue with the Redis process or + # a power outage may result into a few minutes of writes lost (depending on + # the configured save points). + # + # The Append Only File is an alternative persistence mode that provides + # much better durability. For instance using the default data fsync policy + # (see later in the config file) Redis can lose just one second of writes in a + # dramatic event like a server power outage, or a single write if something + # wrong with the Redis process itself happens, but the operating system is + # still running correctly. + # + # AOF and RDB persistence can be enabled at the same time without problems. + # If the AOF is enabled on startup Redis will load the AOF, that is the file + # with the better durability guarantees. + # + # Please check http://redis.io/topics/persistence for more information. + + appendonly no + + # The name of the append only file (default: "appendonly.aof") + + appendfilename "appendonly.aof" + + # The fsync() call tells the Operating System to actually write data on disk + # instead of waiting for more data in the output buffer. Some OS will really flush + # data on disk, some other OS will just try to do it ASAP. + # + # Redis supports three different modes: + # + # no: don't fsync, just let the OS flush the data when it wants. Faster. + # always: fsync after every write to the append only log. Slow, Safest. + # everysec: fsync only one time every second. Compromise. + # + # The default is "everysec", as that's usually the right compromise between + # speed and data safety. It's up to you to understand if you can relax this to + # "no" that will let the operating system flush the output buffer when + # it wants, for better performances (but if you can live with the idea of + # some data loss consider the default persistence mode that's snapshotting), + # or on the contrary, use "always" that's very slow but a bit safer than + # everysec. + # + # More details please check the following article: + # http://antirez.com/post/redis-persistence-demystified.html + # + # If unsure, use "everysec". + + # appendfsync always + appendfsync everysec + # appendfsync no + + # When the AOF fsync policy is set to always or everysec, and a background + # saving process (a background save or AOF log background rewriting) is + # performing a lot of I/O against the disk, in some Linux configurations + # Redis may block too long on the fsync() call. Note that there is no fix for + # this currently, as even performing fsync in a different thread will block + # our synchronous write(2) call. + # + # In order to mitigate this problem it's possible to use the following option + # that will prevent fsync() from being called in the main process while a + # BGSAVE or BGREWRITEAOF is in progress. + # + # This means that while another child is saving, the durability of Redis is + # the same as "appendfsync none". In practical terms, this means that it is + # possible to lose up to 30 seconds of log in the worst scenario (with the + # default Linux settings). + # + # If you have latency problems turn this to "yes". Otherwise leave it as + # "no" that is the safest pick from the point of view of durability. + + no-appendfsync-on-rewrite no + + # Automatic rewrite of the append only file. + # Redis is able to automatically rewrite the log file implicitly calling + # BGREWRITEAOF when the AOF log size grows by the specified percentage. + # + # This is how it works: Redis remembers the size of the AOF file after the + # latest rewrite (if no rewrite has happened since the restart, the size of + # the AOF at startup is used). + # + # This base size is compared to the current size. If the current size is + # bigger than the specified percentage, the rewrite is triggered. Also + # you need to specify a minimal size for the AOF file to be rewritten, this + # is useful to avoid rewriting the AOF file even if the percentage increase + # is reached but it is still pretty small. + # + # Specify a percentage of zero in order to disable the automatic AOF + # rewrite feature. + + auto-aof-rewrite-percentage 100 + auto-aof-rewrite-min-size 64mb + + # An AOF file may be found to be truncated at the end during the Redis + # startup process, when the AOF data gets loaded back into memory. + # This may happen when the system where Redis is running + # crashes, especially when an ext4 filesystem is mounted without the + # data=ordered option (however this can't happen when Redis itself + # crashes or aborts but the operating system still works correctly). + # + # Redis can either exit with an error when this happens, or load as much + # data as possible (the default now) and start if the AOF file is found + # to be truncated at the end. The following option controls this behavior. + # + # If aof-load-truncated is set to yes, a truncated AOF file is loaded and + # the Redis server starts emitting a log to inform the user of the event. + # Otherwise if the option is set to no, the server aborts with an error + # and refuses to start. When the option is set to no, the user requires + # to fix the AOF file using the "redis-check-aof" utility before to restart + # the server. + # + # Note that if the AOF file will be found to be corrupted in the middle + # the server will still exit with an error. This option only applies when + # Redis will try to read more data from the AOF file but not enough bytes + # will be found. + aof-load-truncated yes + + # When rewriting the AOF file, Redis is able to use an RDB preamble in the + # AOF file for faster rewrites and recoveries. When this option is turned + # on the rewritten AOF file is composed of two different stanzas: + # + # [RDB file][AOF tail] + # + # When loading Redis recognizes that the AOF file starts with the "REDIS" + # string and loads the prefixed RDB file, and continues loading the AOF + # tail. + aof-use-rdb-preamble yes + + ################################ LUA SCRIPTING ############################### + + # Max execution time of a Lua script in milliseconds. + # + # If the maximum execution time is reached Redis will log that a script is + # still in execution after the maximum allowed time and will start to + # reply to queries with an error. + # + # When a long running script exceeds the maximum execution time only the + # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be + # used to stop a script that did not yet called write commands. The second + # is the only way to shut down the server in the case a write command was + # already issued by the script but the user doesn't want to wait for the natural + # termination of the script. + # + # Set it to 0 or a negative value for unlimited execution without warnings. + lua-time-limit 5000 + + ################################ REDIS CLUSTER ############################### + + # Normal Redis instances can't be part of a Redis Cluster; only nodes that are + # started as cluster nodes can. In order to start a Redis instance as a + # cluster node enable the cluster support uncommenting the following: + # + cluster-enabled yes + + # Every cluster node has a cluster configuration file. This file is not + # intended to be edited by hand. It is created and updated by Redis nodes. + # Every Redis Cluster node requires a different cluster configuration file. + # Make sure that instances running in the same system do not have + # overlapping cluster configuration file names. + # + cluster-config-file /bitnami/redis/data/nodes.conf + + # Cluster node timeout is the amount of milliseconds a node must be unreachable + # for it to be considered in failure state. + # Most other internal time limits are multiple of the node timeout. + # + # cluster-node-timeout 15000 + + # A replica of a failing master will avoid to start a failover if its data + # looks too old. + # + # There is no simple way for a replica to actually have an exact measure of + # its "data age", so the following two checks are performed: + # + # 1) If there are multiple replicas able to failover, they exchange messages + # in order to try to give an advantage to the replica with the best + # replication offset (more data from the master processed). + # Replicas will try to get their rank by offset, and apply to the start + # of the failover a delay proportional to their rank. + # + # 2) Every single replica computes the time of the last interaction with + # its master. This can be the last ping or command received (if the master + # is still in the "connected" state), or the time that elapsed since the + # disconnection with the master (if the replication link is currently down). + # If the last interaction is too old, the replica will not try to failover + # at all. + # + # The point "2" can be tuned by user. Specifically a replica will not perform + # the failover if, since the last interaction with the master, the time + # elapsed is greater than: + # + # (node-timeout * replica-validity-factor) + repl-ping-replica-period + # + # So for example if node-timeout is 30 seconds, and the replica-validity-factor + # is 10, and assuming a default repl-ping-replica-period of 10 seconds, the + # replica will not try to failover if it was not able to talk with the master + # for longer than 310 seconds. + # + # A large replica-validity-factor may allow replicas with too old data to failover + # a master, while a too small value may prevent the cluster from being able to + # elect a replica at all. + # + # For maximum availability, it is possible to set the replica-validity-factor + # to a value of 0, which means, that replicas will always try to failover the + # master regardless of the last time they interacted with the master. + # (However they'll always try to apply a delay proportional to their + # offset rank). + # + # Zero is the only value able to guarantee that when all the partitions heal + # the cluster will always be able to continue. + # + # cluster-replica-validity-factor 10 + + # Cluster replicas are able to migrate to orphaned masters, that are masters + # that are left without working replicas. This improves the cluster ability + # to resist to failures as otherwise an orphaned master can't be failed over + # in case of failure if it has no working replicas. + # + # Replicas migrate to orphaned masters only if there are still at least a + # given number of other working replicas for their old master. This number + # is the "migration barrier". A migration barrier of 1 means that a replica + # will migrate only if there is at least 1 other working replica for its master + # and so forth. It usually reflects the number of replicas you want for every + # master in your cluster. + # + # Default is 1 (replicas migrate only if their masters remain with at least + # one replica). To disable migration just set it to a very large value. + # A value of 0 can be set but is useful only for debugging and dangerous + # in production. + # + # cluster-migration-barrier 1 + + # By default Redis Cluster nodes stop accepting queries if they detect there + # is at least an hash slot uncovered (no available node is serving it). + # This way if the cluster is partially down (for example a range of hash slots + # are no longer covered) all the cluster becomes, eventually, unavailable. + # It automatically returns available as soon as all the slots are covered again. + # + # However sometimes you want the subset of the cluster which is working, + # to continue to accept queries for the part of the key space that is still + # covered. In order to do so, just set the cluster-require-full-coverage + # option to no. + # + # cluster-require-full-coverage yes + + # This option, when set to yes, prevents replicas from trying to failover its + # master during master failures. However the master can still perform a + # manual failover, if forced to do so. + # + # This is useful in different scenarios, especially in the case of multiple + # data center operations, where we want one side to never be promoted if not + # in the case of a total DC failure. + # + # cluster-replica-no-failover no + + # This option, when set to yes, allows nodes to serve read traffic while the + # the cluster is in a down state, as long as it believes it owns the slots. + # + # This is useful for two cases. The first case is for when an application + # doesn't require consistency of data during node failures or network partitions. + # One example of this is a cache, where as long as the node has the data it + # should be able to serve it. + # + # The second use case is for configurations that don't meet the recommended + # three shards but want to enable cluster mode and scale later. A + # master outage in a 1 or 2 shard configuration causes a read/write outage to the + # entire cluster without this option set, with it set there is only a write outage. + # Without a quorum of masters, slot ownership will not change automatically. + # + # cluster-allow-reads-when-down no + + # In order to setup your cluster make sure to read the documentation + # available at http://redis.io web site. + + ########################## CLUSTER DOCKER/NAT support ######################## + + # In certain deployments, Redis Cluster nodes address discovery fails, because + # addresses are NAT-ted or because ports are forwarded (the typical case is + # Docker and other containers). + # + # In order to make Redis Cluster working in such environments, a static + # configuration where each node knows its public address is needed. The + # following two options are used for this scope, and are: + # + # * cluster-announce-ip + # * cluster-announce-port + # * cluster-announce-bus-port + # + # Each instruct the node about its address, client port, and cluster message + # bus port. The information is then published in the header of the bus packets + # so that other nodes will be able to correctly map the address of the node + # publishing the information. + # + # If the above options are not used, the normal Redis Cluster auto-detection + # will be used instead. + # + # Note that when remapped, the bus port may not be at the fixed offset of + # clients port + 10000, so you can specify any port and bus-port depending + # on how they get remapped. If the bus-port is not set, a fixed offset of + # 10000 will be used as usually. + # + # Example: + # + # cluster-announce-ip 10.1.1.5 + # cluster-announce-port 6379 + # cluster-announce-bus-port 6380 + + ################################## SLOW LOG ################################### + + # The Redis Slow Log is a system to log queries that exceeded a specified + # execution time. The execution time does not include the I/O operations + # like talking with the client, sending the reply and so forth, + # but just the time needed to actually execute the command (this is the only + # stage of command execution where the thread is blocked and can not serve + # other requests in the meantime). + # + # You can configure the slow log with two parameters: one tells Redis + # what is the execution time, in microseconds, to exceed in order for the + # command to get logged, and the other parameter is the length of the + # slow log. When a new command is logged the oldest one is removed from the + # queue of logged commands. + + # The following time is expressed in microseconds, so 1000000 is equivalent + # to one second. Note that a negative number disables the slow log, while + # a value of zero forces the logging of every command. + slowlog-log-slower-than 10000 + + # There is no limit to this length. Just be aware that it will consume memory. + # You can reclaim memory used by the slow log with SLOWLOG RESET. + slowlog-max-len 128 + + ################################ LATENCY MONITOR ############################## + + # The Redis latency monitoring subsystem samples different operations + # at runtime in order to collect data related to possible sources of + # latency of a Redis instance. + # + # Via the LATENCY command this information is available to the user that can + # print graphs and obtain reports. + # + # The system only logs operations that were performed in a time equal or + # greater than the amount of milliseconds specified via the + # latency-monitor-threshold configuration directive. When its value is set + # to zero, the latency monitor is turned off. + # + # By default latency monitoring is disabled since it is mostly not needed + # if you don't have latency issues, and collecting data has a performance + # impact, that while very small, can be measured under big load. Latency + # monitoring can easily be enabled at runtime using the command + # "CONFIG SET latency-monitor-threshold " if needed. + latency-monitor-threshold 0 + + ############################# EVENT NOTIFICATION ############################## + + # Redis can notify Pub/Sub clients about events happening in the key space. + # This feature is documented at http://redis.io/topics/notifications + # + # For instance if keyspace events notification is enabled, and a client + # performs a DEL operation on key "foo" stored in the Database 0, two + # messages will be published via Pub/Sub: + # + # PUBLISH __keyspace@0__:foo del + # PUBLISH __keyevent@0__:del foo + # + # It is possible to select the events that Redis will notify among a set + # of classes. Every class is identified by a single character: + # + # K Keyspace events, published with __keyspace@__ prefix. + # E Keyevent events, published with __keyevent@__ prefix. + # g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... + # $ String commands + # l List commands + # s Set commands + # h Hash commands + # z Sorted set commands + # x Expired events (events generated every time a key expires) + # e Evicted events (events generated when a key is evicted for maxmemory) + # t Stream commands + # m Key-miss events (Note: It is not included in the 'A' class) + # A Alias for g$lshzxet, so that the "AKE" string means all the events + # (Except key-miss events which are excluded from 'A' due to their + # unique nature). + # + # The "notify-keyspace-events" takes as argument a string that is composed + # of zero or multiple characters. The empty string means that notifications + # are disabled. + # + # Example: to enable list and generic events, from the point of view of the + # event name, use: + # + # notify-keyspace-events Elg + # + # Example 2: to get the stream of the expired keys subscribing to channel + # name __keyevent@0__:expired use: + # + # notify-keyspace-events Ex + # + # By default all notifications are disabled because most users don't need + # this feature and the feature has some overhead. Note that if you don't + # specify at least one of K or E, no events will be delivered. + notify-keyspace-events "" + + ############################### GOPHER SERVER ################################# + + # Redis contains an implementation of the Gopher protocol, as specified in + # the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt). + # + # The Gopher protocol was very popular in the late '90s. It is an alternative + # to the web, and the implementation both server and client side is so simple + # that the Redis server has just 100 lines of code in order to implement this + # support. + # + # What do you do with Gopher nowadays? Well Gopher never *really* died, and + # lately there is a movement in order for the Gopher more hierarchical content + # composed of just plain text documents to be resurrected. Some want a simpler + # internet, others believe that the mainstream internet became too much + # controlled, and it's cool to create an alternative space for people that + # want a bit of fresh air. + # + # Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol + # as a gift. + # + # --- HOW IT WORKS? --- + # + # The Redis Gopher support uses the inline protocol of Redis, and specifically + # two kind of inline requests that were anyway illegal: an empty request + # or any request that starts with "/" (there are no Redis commands starting + # with such a slash). Normal RESP2/RESP3 requests are completely out of the + # path of the Gopher protocol implementation and are served as usually as well. + # + # If you open a connection to Redis when Gopher is enabled and send it + # a string like "/foo", if there is a key named "/foo" it is served via the + # Gopher protocol. + # + # In order to create a real Gopher "hole" (the name of a Gopher site in Gopher + # talking), you likely need a script like the following: + # + # https://github.com/antirez/gopher2redis + # + # --- SECURITY WARNING --- + # + # If you plan to put Redis on the internet in a publicly accessible address + # to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance. + # Once a password is set: + # + # 1. The Gopher server (when enabled, not by default) will still serve + # content via Gopher. + # 2. However other commands cannot be called before the client will + # authenticate. + # + # So use the 'requirepass' option to protect your instance. + # + # To enable Gopher support uncomment the following line and set + # the option from no (the default) to yes. + # + # gopher-enabled no + + ############################### ADVANCED CONFIG ############################### + + # Hashes are encoded using a memory efficient data structure when they have a + # small number of entries, and the biggest entry does not exceed a given + # threshold. These thresholds can be configured using the following directives. + hash-max-ziplist-entries 512 + hash-max-ziplist-value 64 + + # Lists are also encoded in a special way to save a lot of space. + # The number of entries allowed per internal list node can be specified + # as a fixed maximum size or a maximum number of elements. + # For a fixed maximum size, use -5 through -1, meaning: + # -5: max size: 64 Kb <-- not recommended for normal workloads + # -4: max size: 32 Kb <-- not recommended + # -3: max size: 16 Kb <-- probably not recommended + # -2: max size: 8 Kb <-- good + # -1: max size: 4 Kb <-- good + # Positive numbers mean store up to _exactly_ that number of elements + # per list node. + # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), + # but if your use case is unique, adjust the settings as necessary. + list-max-ziplist-size -2 + + # Lists may also be compressed. + # Compress depth is the number of quicklist ziplist nodes from *each* side of + # the list to *exclude* from compression. The head and tail of the list + # are always uncompressed for fast push/pop operations. Settings are: + # 0: disable all list compression + # 1: depth 1 means "don't start compressing until after 1 node into the list, + # going from either the head or tail" + # So: [head]->node->node->...->node->[tail] + # [head], [tail] will always be uncompressed; inner nodes will compress. + # 2: [head]->[next]->node->node->...->node->[prev]->[tail] + # 2 here means: don't compress head or head->next or tail->prev or tail, + # but compress all nodes between them. + # 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] + # etc. + list-compress-depth 0 + + # Sets have a special encoding in just one case: when a set is composed + # of just strings that happen to be integers in radix 10 in the range + # of 64 bit signed integers. + # The following configuration setting sets the limit in the size of the + # set in order to use this special memory saving encoding. + set-max-intset-entries 512 + + # Similarly to hashes and lists, sorted sets are also specially encoded in + # order to save a lot of space. This encoding is only used when the length and + # elements of a sorted set are below the following limits: + zset-max-ziplist-entries 128 + zset-max-ziplist-value 64 + + # HyperLogLog sparse representation bytes limit. The limit includes the + # 16 bytes header. When an HyperLogLog using the sparse representation crosses + # this limit, it is converted into the dense representation. + # + # A value greater than 16000 is totally useless, since at that point the + # dense representation is more memory efficient. + # + # The suggested value is ~ 3000 in order to have the benefits of + # the space efficient encoding without slowing down too much PFADD, + # which is O(N) with the sparse encoding. The value can be raised to + # ~ 10000 when CPU is not a concern, but space is, and the data set is + # composed of many HyperLogLogs with cardinality in the 0 - 15000 range. + hll-sparse-max-bytes 3000 + + # Streams macro node max size / items. The stream data structure is a radix + # tree of big nodes that encode multiple items inside. Using this configuration + # it is possible to configure how big a single node can be in bytes, and the + # maximum number of items it may contain before switching to a new node when + # appending new stream entries. If any of the following settings are set to + # zero, the limit is ignored, so for instance it is possible to set just a + # max entries limit by setting max-bytes to 0 and max-entries to the desired + # value. + stream-node-max-bytes 4096 + stream-node-max-entries 100 + + # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in + # order to help rehashing the main Redis hash table (the one mapping top-level + # keys to values). The hash table implementation Redis uses (see dict.c) + # performs a lazy rehashing: the more operation you run into a hash table + # that is rehashing, the more rehashing "steps" are performed, so if the + # server is idle the rehashing is never complete and some more memory is used + # by the hash table. + # + # The default is to use this millisecond 10 times every second in order to + # actively rehash the main dictionaries, freeing memory when possible. + # + # If unsure: + # use "activerehashing no" if you have hard latency requirements and it is + # not a good thing in your environment that Redis can reply from time to time + # to queries with 2 milliseconds delay. + # + # use "activerehashing yes" if you don't have such hard requirements but + # want to free memory asap when possible. + activerehashing yes + + # The client output buffer limits can be used to force disconnection of clients + # that are not reading data from the server fast enough for some reason (a + # common reason is that a Pub/Sub client can't consume messages as fast as the + # publisher can produce them). + # + # The limit can be set differently for the three different classes of clients: + # + # normal -> normal clients including MONITOR clients + # replica -> replica clients + # pubsub -> clients subscribed to at least one pubsub channel or pattern + # + # The syntax of every client-output-buffer-limit directive is the following: + # + # client-output-buffer-limit + # + # A client is immediately disconnected once the hard limit is reached, or if + # the soft limit is reached and remains reached for the specified number of + # seconds (continuously). + # So for instance if the hard limit is 32 megabytes and the soft limit is + # 16 megabytes / 10 seconds, the client will get disconnected immediately + # if the size of the output buffers reach 32 megabytes, but will also get + # disconnected if the client reaches 16 megabytes and continuously overcomes + # the limit for 10 seconds. + # + # By default normal clients are not limited because they don't receive data + # without asking (in a push way), but just after a request, so only + # asynchronous clients may create a scenario where data is requested faster + # than it can read. + # + # Instead there is a default limit for pubsub and replica clients, since + # subscribers and replicas receive data in a push fashion. + # + # Both the hard or the soft limit can be disabled by setting them to zero. + client-output-buffer-limit normal 0 0 0 + client-output-buffer-limit replica 256mb 64mb 60 + client-output-buffer-limit pubsub 32mb 8mb 60 + + # Client query buffers accumulate new commands. They are limited to a fixed + # amount by default in order to avoid that a protocol desynchronization (for + # instance due to a bug in the client) will lead to unbound memory usage in + # the query buffer. However you can configure it here if you have very special + # needs, such us huge multi/exec requests or alike. + # + # client-query-buffer-limit 1gb + + # In the Redis protocol, bulk requests, that are, elements representing single + # strings, are normally limited to 512 mb. However you can change this limit + # here. + # + # proto-max-bulk-len 512mb + + # Redis calls an internal function to perform many background tasks, like + # closing connections of clients in timeout, purging expired keys that are + # never requested, and so forth. + # + # Not all tasks are performed with the same frequency, but Redis checks for + # tasks to perform according to the specified "hz" value. + # + # By default "hz" is set to 10. Raising the value will use more CPU when + # Redis is idle, but at the same time will make Redis more responsive when + # there are many keys expiring at the same time, and timeouts may be + # handled with more precision. + # + # The range is between 1 and 500, however a value over 100 is usually not + # a good idea. Most users should use the default of 10 and raise this up to + # 100 only in environments where very low latency is required. + hz 10 + + # Normally it is useful to have an HZ value which is proportional to the + # number of clients connected. This is useful in order, for instance, to + # avoid too many clients are processed for each background task invocation + # in order to avoid latency spikes. + # + # Since the default HZ value by default is conservatively set to 10, Redis + # offers, and enables by default, the ability to use an adaptive HZ value + # which will temporary raise when there are many connected clients. + # + # When dynamic HZ is enabled, the actual configured HZ will be used + # as a baseline, but multiples of the configured HZ value will be actually + # used as needed once more clients are connected. In this way an idle + # instance will use very little CPU time while a busy instance will be + # more responsive. + dynamic-hz yes + + # When a child rewrites the AOF file, if the following option is enabled + # the file will be fsync-ed every 32 MB of data generated. This is useful + # in order to commit the file to the disk more incrementally and avoid + # big latency spikes. + aof-rewrite-incremental-fsync yes + + # When redis saves RDB file, if the following option is enabled + # the file will be fsync-ed every 32 MB of data generated. This is useful + # in order to commit the file to the disk more incrementally and avoid + # big latency spikes. + rdb-save-incremental-fsync yes + + # Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good + # idea to start with the default settings and only change them after investigating + # how to improve the performances and how the keys LFU change over time, which + # is possible to inspect via the OBJECT FREQ command. + # + # There are two tunable parameters in the Redis LFU implementation: the + # counter logarithm factor and the counter decay time. It is important to + # understand what the two parameters mean before changing them. + # + # The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis + # uses a probabilistic increment with logarithmic behavior. Given the value + # of the old counter, when a key is accessed, the counter is incremented in + # this way: + # + # 1. A random number R between 0 and 1 is extracted. + # 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). + # 3. The counter is incremented only if R < P. + # + # The default lfu-log-factor is 10. This is a table of how the frequency + # counter changes with a different number of accesses with different + # logarithmic factors: + # + # +--------+------------+------------+------------+------------+------------+ + # | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | + # +--------+------------+------------+------------+------------+------------+ + # | 0 | 104 | 255 | 255 | 255 | 255 | + # +--------+------------+------------+------------+------------+------------+ + # | 1 | 18 | 49 | 255 | 255 | 255 | + # +--------+------------+------------+------------+------------+------------+ + # | 10 | 10 | 18 | 142 | 255 | 255 | + # +--------+------------+------------+------------+------------+------------+ + # | 100 | 8 | 11 | 49 | 143 | 255 | + # +--------+------------+------------+------------+------------+------------+ + # + # NOTE: The above table was obtained by running the following commands: + # + # redis-benchmark -n 1000000 incr foo + # redis-cli object freq foo + # + # NOTE 2: The counter initial value is 5 in order to give new objects a chance + # to accumulate hits. + # + # The counter decay time is the time, in minutes, that must elapse in order + # for the key counter to be divided by two (or decremented if it has a value + # less <= 10). + # + # The default value for the lfu-decay-time is 1. A Special value of 0 means to + # decay the counter every time it happens to be scanned. + # + # lfu-log-factor 10 + # lfu-decay-time 1 + + ########################### ACTIVE DEFRAGMENTATION ####################### + # + # What is active defragmentation? + # ------------------------------- + # + # Active (online) defragmentation allows a Redis server to compact the + # spaces left between small allocations and deallocations of data in memory, + # thus allowing to reclaim back memory. + # + # Fragmentation is a natural process that happens with every allocator (but + # less so with Jemalloc, fortunately) and certain workloads. Normally a server + # restart is needed in order to lower the fragmentation, or at least to flush + # away all the data and create it again. However thanks to this feature + # implemented by Oran Agra for Redis 4.0 this process can happen at runtime + # in an "hot" way, while the server is running. + # + # Basically when the fragmentation is over a certain level (see the + # configuration options below) Redis will start to create new copies of the + # values in contiguous memory regions by exploiting certain specific Jemalloc + # features (in order to understand if an allocation is causing fragmentation + # and to allocate it in a better place), and at the same time, will release the + # old copies of the data. This process, repeated incrementally for all the keys + # will cause the fragmentation to drop back to normal values. + # + # Important things to understand: + # + # 1. This feature is disabled by default, and only works if you compiled Redis + # to use the copy of Jemalloc we ship with the source code of Redis. + # This is the default with Linux builds. + # + # 2. You never need to enable this feature if you don't have fragmentation + # issues. + # + # 3. Once you experience fragmentation, you can enable this feature when + # needed with the command "CONFIG SET activedefrag yes". + # + # The configuration parameters are able to fine tune the behavior of the + # defragmentation process. If you are not sure about what they mean it is + # a good idea to leave the defaults untouched. + + # Enabled active defragmentation + # activedefrag no + + # Minimum amount of fragmentation waste to start active defrag + # active-defrag-ignore-bytes 100mb + + # Minimum percentage of fragmentation to start active defrag + # active-defrag-threshold-lower 10 + + # Maximum percentage of fragmentation at which we use maximum effort + # active-defrag-threshold-upper 100 + + # Minimal effort for defrag in CPU percentage, to be used when the lower + # threshold is reached + # active-defrag-cycle-min 1 + + # Maximal effort for defrag in CPU percentage, to be used when the upper + # threshold is reached + # active-defrag-cycle-max 25 + + # Maximum number of set/hash/zset/list fields that will be processed from + # the main dictionary scan + # active-defrag-max-scan-fields 1000 + + # Jemalloc background thread for purging will be enabled by default + jemalloc-bg-thread yes + + # It is possible to pin different threads and processes of Redis to specific + # CPUs in your system, in order to maximize the performances of the server. + # This is useful both in order to pin different Redis threads in different + # CPUs, but also in order to make sure that multiple Redis instances running + # in the same host will be pinned to different CPUs. + # + # Normally you can do this using the "taskset" command, however it is also + # possible to this via Redis configuration directly, both in Linux and FreeBSD. + # + # You can pin the server/IO threads, bio threads, aof rewrite child process, and + # the bgsave child process. The syntax to specify the cpu list is the same as + # the taskset command: + # + # Set redis server/io threads to cpu affinity 0,2,4,6: + # server_cpulist 0-7:2 + # + # Set bio threads to cpu affinity 1,3: + # bio_cpulist 1,3 + # + # Set aof rewrite child process to cpu affinity 8,9,10,11: + # aof_rewrite_cpulist 8-11 + # + # Set bgsave child process to cpu affinity 1,10,11 + # bgsave_cpulist 1,10-11 +{{- if .Values.redis.configmap }} +{{- include "common.tplvalues.render" (dict "value" .Values.redis.configmap "context" $) | nindent 4 }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/extra-list.yaml b/rds/base/charts/redis-cluster/templates/extra-list.yaml new file mode 100644 index 0000000..9ac65f9 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/headless-svc.yaml b/rds/base/charts/redis-cluster/templates/headless-svc.yaml new file mode 100644 index 0000000..e95badc --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/headless-svc.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: tcp-redis + port: {{ .Values.redis.containerPorts.redis }} + targetPort: tcp-redis + - name: tcp-redis-bus + port: {{ .Values.redis.containerPorts.bus }} + targetPort: tcp-redis-bus + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/rds/base/charts/redis-cluster/templates/metrics-prometheus.yaml b/rds/base/charts/redis-cluster/templates/metrics-prometheus.yaml new file mode 100644 index 0000000..540cf3d --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/metrics-prometheus.yaml @@ -0,0 +1,54 @@ +{{- if and (.Values.metrics.enabled) (.Values.metrics.serviceMonitor.enabled) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "common.names.fullname" . }} + {{- if .Values.metrics.serviceMonitor.namespace }} + namespace: {{ .Values.metrics.serviceMonitor.namespace | default .Release.Namespace | quote }} + {{- else}} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.labels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.labels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.commonAnnotations .Values.metrics.serviceMonitor.annotations }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + {{- if .Values.metrics.serviceMonitor.jobLabel }} + jobLabel: {{ .Values.metrics.serviceMonitor.jobLabel }} + {{- end }} + endpoints: + - port: metrics + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.relabelings "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.serviceMonitor.metricRelabelings "context" $) | nindent 8 }} + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + {{- if .Values.metrics.serviceMonitor.selector }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.selector "context" $) | nindent 6 }} + {{- end }} + app.kubernetes.io/component: "metrics" + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/metrics-svc.yaml b/rds/base/charts/redis-cluster/templates/metrics-svc.yaml new file mode 100644 index 0000000..14305dd --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/metrics-svc.yaml @@ -0,0 +1,35 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }}-metrics + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.metrics.service.labels }} + {{ include "common.tplvalues.render" ( dict "value" .Values.metrics.service.labels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + app.kubernetes.io/component: "metrics" + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.service.annotations }} + {{ include "common.tplvalues.render" ( dict "value" .Values.metrics.service.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + {{- if and .Values.metrics.service.clusterIP (eq .Values.metrics.service.type "ClusterIP") }} + clusterIP: {{ .Values.metrics.service.clusterIP }} + {{- end }} + {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{- end }} + ports: + - name: metrics + port: 9121 + targetPort: http-metrics + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/networkpolicy.yaml b/rds/base/charts/redis-cluster/templates/networkpolicy.yaml new file mode 100644 index 0000000..d7b4f16 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/networkpolicy.yaml @@ -0,0 +1,66 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + namespace: {{ .Release.Namespace }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ template "common.names.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + policyTypes: + - Ingress + - Egress + egress: + # Allow dns resolution + - ports: + - port: 53 + protocol: UDP + # Allow outbound connections to other cluster pods + - ports: + - port: {{ .Values.redis.containerPorts.redis }} + - port: {{ .Values.redis.containerPorts.bus }} + to: + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} + ingress: + # Allow inbound connections + - ports: + - port: {{ .Values.redis.containerPorts.redis }} + - port: {{ .Values.redis.containerPorts.bus }} + from: + {{- if not .Values.networkPolicy.allowExternal }} + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} + {{- end }} + {{- if .Values.networkPolicy.ingressNSMatchLabels }} + - namespaceSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.networkPolicy.ingressNSPodMatchLabels }} + - podSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSPodMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.metrics.enabled }} + # Allow prometheus scrapes for metrics + - ports: + - port: 9121 + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/poddisruptionbudget.yaml b/rds/base/charts/redis-cluster/templates/poddisruptionbudget.yaml new file mode 100644 index 0000000..abef667 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/poddisruptionbudget.yaml @@ -0,0 +1,20 @@ +{{- if .Values.podDisruptionBudget }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + matchExpressions: + - {key: job-name, operator: NotIn, values: [{{ template "common.names.fullname" . }}-cluster-update]} + {{- toYaml .Values.podDisruptionBudget | nindent 2 }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/prometheusrule.yaml b/rds/base/charts/redis-cluster/templates/prometheusrule.yaml new file mode 100644 index 0000000..d781ee1 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/prometheusrule.yaml @@ -0,0 +1,27 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} + {{- if .Values.metrics.prometheusRule.namespace }} + namespace: {{ .Values.metrics.prometheusRule.namespace }} + {{- else }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.metrics.prometheusRule.additionalLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.prometheusRule.additionalLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "common.names.name" $ }} + rules: {{- include "common.tplvalues.render" ( dict "value" . "context" $ ) | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/psp.yaml b/rds/base/charts/redis-cluster/templates/psp.yaml new file mode 100644 index 0000000..e048bce --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/psp.yaml @@ -0,0 +1,46 @@ +{{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- if and $pspAvailable .Values.podSecurityPolicy.create }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + allowPrivilegeEscalation: false + fsGroup: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.podSecurityContext.fsGroup }} + max: {{ .Values.podSecurityContext.fsGroup }} + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: false + requiredDropCapabilities: + - ALL + runAsUser: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.podSecurityContext.runAsUser }} + max: {{ .Values.podSecurityContext.runAsUser }} + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.podSecurityContext.runAsUser }} + max: {{ .Values.podSecurityContext.runAsUser }} + volumes: + - 'configMap' + - 'secret' + - 'emptyDir' + - 'persistentVolumeClaim' +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/redis-role.yaml b/rds/base/charts/redis-cluster/templates/redis-role.yaml new file mode 100644 index 0000000..f951f23 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/redis-role.yaml @@ -0,0 +1,25 @@ +{{- if .Values.rbac.create -}} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +kind: Role +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +rules: + {{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} + {{- if and $pspAvailable .Values.podSecurityPolicy.create }} + - apiGroups: ['{{ template "podSecurityPolicy.apiGroup" . }}'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: [{{ template "common.names.fullname" . }}] +{{- end -}} +{{- if .Values.rbac.role.rules }} +{{- toYaml .Values.rbac.role.rules | nindent 2 }} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/redis-rolebinding.yaml b/rds/base/charts/redis-cluster/templates/redis-rolebinding.yaml new file mode 100644 index 0000000..2b7f431 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/redis-rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if .Values.rbac.create -}} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +kind: RoleBinding +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "common.names.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "redis-cluster.serviceAccountName" . }} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/redis-serviceaccount.yaml b/rds/base/charts/redis-cluster/templates/redis-serviceaccount.yaml new file mode 100644 index 0000000..6fdb831 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/redis-serviceaccount.yaml @@ -0,0 +1,21 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "redis-cluster.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.serviceAccount.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.serviceAccount.annotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.serviceAccount.annotations "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 4 }} + {{- end }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/redis-statefulset.yaml b/rds/base/charts/redis-cluster/templates/redis-statefulset.yaml new file mode 100644 index 0000000..3ff181d --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/redis-statefulset.yaml @@ -0,0 +1,449 @@ +{{- if (include "redis-cluster.createStatefulSet" .) }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.redis.updateStrategy }} + updateStrategy: {{- toYaml .Values.redis.updateStrategy | nindent 4 }} + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + replicas: {{ .Values.cluster.nodes }} + serviceName: {{ include "common.names.fullname" . }}-headless + podManagementPolicy: {{ .Values.redis.podManagementPolicy }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + {{- if .Values.redis.podLabels }} + {{- toYaml .Values.redis.podLabels | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- toYaml .Values.metrics.podLabels | nindent 8 }} + {{- end }} + annotations: + checksum/scripts: {{ include (print $.Template.BasePath "/scripts-configmap.yaml") . | sha256sum }} + {{- if not .Values.existingSecret }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- if .Values.redis.podAnnotations }} + {{- toYaml .Values.redis.podAnnotations | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- toYaml .Values.metrics.podAnnotations | nindent 8 }} + {{- end }} + spec: + hostNetwork: {{ .Values.redis.hostNetwork }} + {{- if semverCompare ">= 1.13" (include "common.capabilities.kubeVersion" .) }} + enableServiceLinks: false + {{- end }} + {{- include "redis-cluster.imagePullSecrets" . | nindent 6 }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "redis-cluster.serviceAccountName" . }} + {{- if .Values.redis.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.redis.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.redis.priorityClassName }} + priorityClassName: {{ .Values.redis.priorityClassName }} + {{- end }} + {{- if .Values.redis.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.redis.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.redis.podAffinityPreset "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.redis.podAntiAffinityPreset "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.redis.nodeAffinityPreset.type "key" .Values.redis.nodeAffinityPreset.key "values" .Values.redis.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.redis.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.redis.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.redis.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.redis.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.redis.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.redis.shareProcessNamespace }} + {{- end }} + {{- if .Values.redis.schedulerName }} + schedulerName: {{ .Values.redis.schedulerName | quote }} + {{- end }} + {{- if .Values.redis.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" ( dict "value" .Values.redis.topologySpreadConstraints "context" $ ) | nindent 8 }} + {{- end }} + containers: + - name: {{ include "common.names.fullname" . }} + image: {{ include "redis-cluster.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.redis.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.redis.command "context" $) | nindent 12 }} + {{- else }} + command: ['/bin/bash', '-c'] + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.redis.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.redis.args "context" $) | nindent 12 }} + {{- else if .Values.cluster.externalAccess.enabled }} + args: + - | + # Backwards compatibility change + if ! [[ -f /opt/bitnami/redis/etc/redis.conf ]]; then + cp /opt/bitnami/redis/etc/redis-default.conf /opt/bitnami/redis/etc/redis.conf + fi + pod_index=($(echo "$POD_NAME" | tr "-" "\n")) + pod_index="${pod_index[-1]}" + ips=($(echo "{{ .Values.cluster.externalAccess.service.loadBalancerIP }}" | cut -d [ -f2 | cut -d ] -f 1)) + export REDIS_CLUSTER_ANNOUNCE_IP="${ips[$pod_index]}" + export REDIS_NODES="${ips[@]}" + {{- if .Values.cluster.init }} + if [[ "$pod_index" == "0" ]]; then + export REDIS_CLUSTER_CREATOR="yes" + export REDIS_CLUSTER_REPLICAS="{{ .Values.cluster.replicas }}" + fi + {{- end }} + /opt/bitnami/scripts/redis-cluster/entrypoint.sh /opt/bitnami/scripts/redis-cluster/run.sh + {{- else }} + args: + - | + # Backwards compatibility change + if ! [[ -f /opt/bitnami/redis/etc/redis.conf ]]; then + echo COPYING FILE + cp /opt/bitnami/redis/etc/redis-default.conf /opt/bitnami/redis/etc/redis.conf + fi + {{- if .Values.cluster.init }} + pod_index=($(echo "$POD_NAME" | tr "-" "\n")) + pod_index="${pod_index[-1]}" + if [[ "$pod_index" == "0" ]]; then + export REDIS_CLUSTER_CREATOR="yes" + export REDIS_CLUSTER_REPLICAS="{{ .Values.cluster.replicas }}" + fi + {{- end }} + /opt/bitnami/scripts/redis-cluster/entrypoint.sh /opt/bitnami/scripts/redis-cluster/run.sh + {{- end }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- if .Values.cluster.externalAccess.enabled }} + - name: REDIS_CLUSTER_DYNAMIC_IPS + value: "no" + {{- else }} + - name: REDIS_NODES + value: "{{ $count := .Values.cluster.nodes | int }}{{ range $i, $v := until $count }}{{ include "common.names.fullname" $ }}-{{ $i }}.{{ template "common.names.fullname" $ }}-headless {{ end }}" + {{- end }} + {{- if .Values.usePassword }} + - name: REDISCLI_AUTH + valueFrom: + secretKeyRef: + name: {{ template "redis-cluster.secretName" . }} + key: {{ template "redis-cluster.secretPasswordKey" . }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- else }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis-cluster.secretName" . }} + key: {{ template "redis-cluster.secretPasswordKey" . }} + {{- end }} + {{- else }} + - name: ALLOW_EMPTY_PASSWORD + value: "yes" + {{- end }} + - name: REDIS_AOF_ENABLED + value: {{ .Values.redis.useAOFPersistence | quote }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.redis.containerPorts.redis | quote }} + - name: REDIS_TLS_AUTH_CLIENTS + value: {{ ternary "yes" "no" .Values.tls.authClients | quote }} + - name: REDIS_TLS_CERT_FILE + value: {{ template "redis-cluster.tlsCert" . }} + - name: REDIS_TLS_KEY_FILE + value: {{ template "redis-cluster.tlsCertKey" . }} + - name: REDIS_TLS_CA_FILE + value: {{ template "redis-cluster.tlsCACert" . }} + {{- if .Values.tls.dhParamsFilename }} + - name: REDIS_TLS_DH_PARAMS_FILE + value: {{ template "redis-cluster.tlsDHParams" . }} + {{- end }} + {{- else }} + - name: REDIS_PORT + value: {{ .Values.redis.containerPorts.redis | quote }} + {{- end }} + {{- if .Values.redis.extraEnvVars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.redis.extraEnvVars "context" $ ) | nindent 12 }} + {{- end }} + {{- if or .Values.redis.extraEnvVarsCM .Values.redis.extraEnvVarsSecret }} + envFrom: + {{- if .Values.redis.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" ( dict "value" .Values.redis.extraEnvVarsCM "context" $ ) }} + {{- end }} + {{- if .Values.redis.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" ( dict "value" .Values.redis.extraEnvVarsSecret "context" $ ) }} + {{- end }} + {{- end }} + ports: + - name: tcp-redis + containerPort: {{ .Values.redis.containerPorts.redis }} + - name: tcp-redis-bus + containerPort: {{ .Values.redis.containerPorts.bus }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.redis.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.redis.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.redis.livenessProbe.periodSeconds }} + # One second longer than command timeout should prevent generation of zombie processes. + timeoutSeconds: {{ add1 .Values.redis.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.redis.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.redis.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /scripts/ping_liveness_local.sh {{ .Values.redis.livenessProbe.timeoutSeconds }} + {{- else if .Values.redis.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.redis.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.redis.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.redis.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.redis.readinessProbe.periodSeconds }} + # One second longer than command timeout should prevent generation of zombie processes. + timeoutSeconds: {{ add1 .Values.redis.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.redis.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.redis.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /scripts/ping_readiness_local.sh {{ .Values.redis.readinessProbe.timeoutSeconds }} + {{- else if .Values.redis.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.redis.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.redis.startupProbe.enabled }} + startupProbe: + tcpSocket: + port: tcp-redis + initialDelaySeconds: {{ .Values.redis.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.redis.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.redis.startupProbe.timeoutSeconds }} + successThreshold: {{ .Values.redis.startupProbe.successThreshold }} + failureThreshold: {{ .Values.redis.startupProbe.failureThreshold }} + {{- else if .Values.redis.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.redis.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.redis.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.redis.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.redis.resources }} + resources: + {{- include "common.tplvalues.render" (dict "value" .Values.redis.resources "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: scripts + mountPath: /scripts + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.persistence.path }} + subPath: {{ .Values.persistence.subPath }} + - name: default-config + mountPath: /opt/bitnami/redis/etc/redis-default.conf + subPath: redis-default.conf + - 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 .Values.redis.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.redis.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis-cluster.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + - -c + - | + {{- if .Values.usePasswordFile }} + export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + {{- end }} + redis_exporter{{- range $key, $value := .Values.metrics.extraArgs }} --{{ $key }}={{ $value }}{{- end }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: REDIS_ALIAS + value: {{ template "common.names.fullname" . }} + - name: REDIS_ADDR + value: {{ printf "%s://127.0.0.1:%g" (ternary "rediss" "redis" .Values.tls.enabled) .Values.redis.containerPorts.redis | quote }} + {{- if and .Values.usePassword (not .Values.usePasswordFile) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis-cluster.secretName" . }} + key: {{ template "redis-cluster.secretPasswordKey" . }} + {{- end }} + {{- if .Values.usePasswordFile }} + - name: REDIS_PASSWORD_FILE + value: "/opt/bitnami/redis/secrets/redis-password" + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis-cluster.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis-cluster.tlsCert" . }} + - name: REDIS_EXPORTER_TLS_CA_CERT_FILE + value: {{ template "redis-cluster.tlsCACert" . }} + {{- end }} + {{- if or .Values.usePasswordFile .Values.tls.enabled }} + volumeMounts: + {{- if .Values.usePasswordFile }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- end }} + ports: + - name: http-metrics + containerPort: 9121 + resources: + {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + {{- if .Values.redis.sidecars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.redis.sidecars "context" $ ) | nindent 8 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled .Values.containerSecurityContext.enabled }} + {{- if or $needsVolumePermissions .Values.sysctlImage.enabled .Values.redis.initContainers }} + initContainers: + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: {{ include "redis-cluster.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: ["/bin/chown", "-R", "{{ .Values.containerSecurityContext.runAsUser }}:{{ .Values.podSecurityContext.fsGroup }}", "{{ .Values.persistence.path }}"] + securityContext: + runAsUser: 0 + resources: + {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.persistence.path }} + subPath: {{ .Values.persistence.subPath }} + {{- end }} + {{- if .Values.sysctlImage.enabled }} + - name: init-sysctl + image: {{ template "redis-cluster.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 }} + {{- if .Values.redis.initContainers }} + {{- toYaml .Values.redis.initContainers | nindent 8 }} + {{- end }} + {{- end }} + volumes: + - name: scripts + configMap: + name: {{ include "common.names.fullname" . }}-scripts + defaultMode: 0755 + {{- if .Values.usePasswordFile }} + - name: redis-password + secret: + secretName: {{ include "redis-cluster.secretName" . }} + items: + - key: {{ include "redis-cluster.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: default-config + configMap: + name: {{ include "common.names.fullname" . }}-default + {{- if .Values.sysctlImage.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: redis-tmp-conf + emptyDir: {} + {{- if .Values.redis.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.redis.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ include "redis-cluster.tlsSecretName" . }} + defaultMode: 256 + {{- end }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: {{- include "common.labels.matchLabels" . | nindent 10 }} + {{- 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 }} + {{- include "common.storage.class" (dict "persistence" .Values.persistence "global" .Values.global) | nindent 8 }} + {{- if or .Values.persistence.matchLabels .Values.persistence.matchExpressions }} + selector: + {{- if .Values.persistence.matchLabels }} + matchLabels: + {{- toYaml .Values.persistence.matchLabels | nindent 12 }} + {{- end -}} + {{- if .Values.persistence.matchExpressions }} + matchExpressions: + {{- toYaml .Values.persistence.matchExpressions | nindent 12 }} + {{- end -}} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/redis-svc.yaml b/rds/base/charts/redis-cluster/templates/redis-svc.yaml new file mode 100644 index 0000000..22c0017 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/redis-svc.yaml @@ -0,0 +1,53 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.service.labels -}} + {{ include "common.tplvalues.render" ( dict "value" .Values.service.labels "context" $ ) | nindent 4 }} + {{- end -}} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.service.annotations }} + {{ include "common.tplvalues.render" ( dict "value" .Values.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.sessionAffinity }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- end }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: tcp-redis + port: {{ .Values.service.ports.redis }} + targetPort: tcp-redis + protocol: TCP + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.redis)) }} + nodePort: {{ .Values.service.nodePorts.redis }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- if .Values.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} diff --git a/rds/base/charts/redis-cluster/templates/scripts-configmap.yaml b/rds/base/charts/redis-cluster/templates/scripts-configmap.yaml new file mode 100644 index 0000000..7cd2a4c --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/scripts-configmap.yaml @@ -0,0 +1,111 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "common.names.fullname" . }}-scripts + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + ping_readiness_local.sh: |- + #!/bin/sh + set -e + + REDIS_STATUS_FILE=/tmp/.redis_cluster_check + + {{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDISCLI_AUTH=$password_aux + {{- else }} + if [ ! -z "$REDIS_PASSWORD" ]; then export REDISCLI_AUTH=$REDIS_PASSWORD; fi; + {{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_TLS_PORT \ + --tls \ + --cert {{ template "redis-cluster.tlsCert" . }} \ + --key {{ template "redis-cluster.tlsCertKey" . }} \ + --cacert {{ template "redis-cluster.tlsCACert" . }} \ +{{- else }} + -p $REDIS_PORT \ +{{- end }} + ping + ) + if [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +{{- if not .Values.cluster.externalAccess.enabled }} + if [ ! -f "$REDIS_STATUS_FILE" ]; then + response=$( + timeout -s 3 $1 \ + redis-cli \ + -h localhost \ + {{- if .Values.tls.enabled }} + -p $REDIS_TLS_PORT \ + --tls \ + --cert {{ template "redis-cluster.tlsCert" . }} \ + --key {{ template "redis-cluster.tlsCertKey" . }} \ + --cacert {{ template "redis-cluster.tlsCACert" . }} \ + {{- else }} + -p $REDIS_PORT \ + {{- end }} + CLUSTER INFO | grep cluster_state | tr -d '[:space:]' + ) + if [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + if [ "$response" != "cluster_state:ok" ]; then + echo "$response" + exit 1 + else + touch "$REDIS_STATUS_FILE" + fi + fi +{{- end }} + ping_liveness_local.sh: |- + #!/bin/sh + set -e + + {{- if .Values.usePasswordFile }} + password_aux=`cat ${REDIS_PASSWORD_FILE}` + export REDISCLI_AUTH=$password_aux + {{- else }} + if [ ! -z "$REDIS_PASSWORD" ]; then export REDISCLI_AUTH=$REDIS_PASSWORD; fi; + {{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_TLS_PORT \ + --tls \ + --cert {{ template "redis-cluster.tlsCert" . }} \ + --key {{ template "redis-cluster.tlsCertKey" . }} \ + --cacert {{ template "redis-cluster.tlsCACert" . }} \ +{{- else }} + -p $REDIS_PORT \ +{{- end }} + ping + ) + if [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + responseFirstWord=$(echo $response | head -n1 | awk '{print $1;}') + if [ "$response" != "PONG" ] && [ "$responseFirstWord" != "LOADING" ] && [ "$responseFirstWord" != "MASTERDOWN" ]; then + echo "$response" + exit 1 + fi diff --git a/rds/base/charts/redis-cluster/templates/secret.yaml b/rds/base/charts/redis-cluster/templates/secret.yaml new file mode 100644 index 0000000..9b95aef --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/secret.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.usePassword (not .Values.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + redis-password: {{ include "redis-cluster.password" . | b64enc | quote }} +{{- end -}} diff --git a/rds/base/charts/redis-cluster/templates/svc-cluster-external-access.yaml b/rds/base/charts/redis-cluster/templates/svc-cluster-external-access.yaml new file mode 100644 index 0000000..9778040 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/svc-cluster-external-access.yaml @@ -0,0 +1,45 @@ +{{- if .Values.cluster.externalAccess.enabled }} +{{- $fullName := include "common.names.fullname" . }} +{{- $nodesCount := .Values.cluster.nodes | int }} +{{- $root := . }} + +{{- range $i, $e := until $nodesCount }} +{{- $targetPod := printf "%s-%d" (printf "%s" $fullName) $i }} +{{- $_ := set $ "targetPod" $targetPod }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" $ }}-{{ $i }}-svc + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + pod: {{ $targetPod }} + {{- if $root.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if $root.Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" $root.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $root.Values.cluster.externalAccess.service.annotations }} + {{ include "common.tplvalues.render" ( dict "value" $root.Values.cluster.externalAccess.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ $root.Values.cluster.externalAccess.service.type }} + {{- if $root.Values.cluster.externalAccess.service.loadBalancerIP }} + loadBalancerIP: {{ index $root.Values.cluster.externalAccess.service.loadBalancerIP $i }} + {{- end }} + {{- if and (eq $root.Values.cluster.externalAccess.service.type "LoadBalancer") $root.Values.cluster.externalAccess.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml $root.Values.cluster.externalAccess.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - name: tcp-redis + port: {{ $root.Values.cluster.externalAccess.service.port }} + targetPort: tcp-redis + - name: tcp-redis-bus + targetPort: tcp-redis-bus + port: {{ $root.Values.redis.containerPorts.bus }} + selector: {{- include "common.labels.matchLabels" $ | nindent 4 }} + statefulset.kubernetes.io/pod-name: {{ $targetPod }} +--- +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/tls-secret.yaml b/rds/base/charts/redis-cluster/templates/tls-secret.yaml new file mode 100644 index 0000000..5cf2afa --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/tls-secret.yaml @@ -0,0 +1,27 @@ +{{- if (include "redis-cluster.createTlsSecret" .) }} +{{- $ca := genCA "redis-cluster-ca" 365 }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "common.names.fullname" . }} +{{- $serviceName := include "common.names.fullname" . }} +{{- $headlessServiceName := printf "%s-headless" (include "common.names.fullname" .) }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "*.%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) "127.0.0.1" "localhost" $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.names.fullname" . }}-crt + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $crt.Cert | b64enc | quote }} + tls.key: {{ $crt.Key | b64enc | quote }} +{{- end }} diff --git a/rds/base/charts/redis-cluster/templates/update-cluster.yaml b/rds/base/charts/redis-cluster/templates/update-cluster.yaml new file mode 100644 index 0000000..a0b3fc6 --- /dev/null +++ b/rds/base/charts/redis-cluster/templates/update-cluster.yaml @@ -0,0 +1,266 @@ +{{- if and .Values.cluster.update.addNodes ( or (and .Values.cluster.externalAccess.enabled .Values.cluster.externalAccess.service.loadBalancerIP) ( not .Values.cluster.externalAccess.enabled )) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "common.names.fullname" . }}-cluster-update + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + "helm.sh/hook": post-upgrade + {{- if .Values.updateJob.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.updateJob.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + activeDeadlineSeconds: {{ .Values.updateJob.activeDeadlineSeconds }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.updateJob.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.podLabels "context" $) | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonAnnotations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.updateJob.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.podAnnotations "context" $) | nindent 8 }} + {{- end }} + spec: + {{- include "redis-cluster.imagePullSecrets" . | nindent 6 }} + {{- if .Values.updateJob.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.updateJob.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.updateJob.podAffinityPreset "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.updateJob.podAntiAffinityPreset "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.updateJob.nodeAffinityPreset.type "key" .Values.updateJob.nodeAffinityPreset.key "values" .Values.updateJob.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.updateJob.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.updateJob.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.updateJob.priorityClassName }} + priorityClassName: {{ .Values.updateJob.priorityClassName }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- omit .Values.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "redis-cluster.serviceAccountName" . }} + {{- if .Values.updateJob.initContainers }} + initContainers: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.initContainers "context" $) | nindent 8 }} + {{- end }} + containers: + - name: trigger + image: {{ include "redis-cluster.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.updateJob.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.command "context" $) | nindent 12 }} + {{- else }} + command: ['/bin/bash', '-c'] + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.updateJob.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.args "context" $) | nindent 12 }} + {{- else }} + args: + - | + . /opt/bitnami/scripts/libnet.sh + . /opt/bitnami/scripts/libos.sh + # Backwards compatibility change + if ! [[ -f /opt/bitnami/redis/etc/redis.conf ]]; then + cp /opt/bitnami/redis/etc/redis-default.conf /opt/bitnami/redis/etc/redis.conf + fi + firstNodeIP=$(wait_for_dns_lookup {{ template "common.names.fullname" . }}-0.{{ template "common.names.fullname" . }}-headless 120 5) + {{- if .Values.cluster.externalAccess.enabled }} + newNodeCounter=0 + for nodeIP in $(echo "{{ .Values.cluster.update.newExternalIPs }}" | cut -d [ -f2 | cut -d ] -f 1 ); do + {{- if .Values.tls.enabled }} + while [[ $(redis-cli -h "$nodeIP" -p "$REDIS_TLS_PORT" --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} ping) != 'PONG' ]]; do + {{- else }} + while [[ $(redis-cli -h "$nodeIP" -p "$REDIS_PORT" ping) != 'PONG' ]]; do + {{- end }} + echo "Node $nodeIP not ready, waiting for all the nodes to be ready..." + sleep 5 + done + slave=() + if (( $REDIS_CLUSTER_REPLICAS >= 1 )) && (( newNodeCounter % (( $REDIS_CLUSTER_REPLICAS + 1 )) )); then + slave+=("--cluster-slave") + fi + {{- if .Values.tls.enabled }} + while ! redis-cli --cluster --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} add-node "${nodeIP}:${REDIS_TLS_PORT}" "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_TLS_PORT}" ${slave[@]}; do + {{- else }} + while ! redis-cli --cluster add-node "${nodeIP}:${REDIS_PORT}" "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_PORT}" ${slave[@]}; do + {{- end }} + echo "Add-node ${newNodeIndex} ${newNodeIP} failed, retrying" + sleep 5 + done + ((newNodeCounter += 1)) + done + + {{- if .Values.tls.enabled }} + while ! redis-cli --cluster rebalance --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_TLS_PORT}" --cluster-use-empty-masters; do + {{- else }} + while ! redis-cli --cluster rebalance "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_PORT}" --cluster-use-empty-masters; do + {{- end }} + echo "Rebalance failed, retrying" + sleep 5 + {{- if .Values.tls.enabled }} + redis-cli --cluster fix --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_TLS_PORT}" + {{- else }} + redis-cli --cluster fix "{{ index .Values.cluster.externalAccess.service.loadBalancerIP 0 }}:${REDIS_PORT}" + {{- end }} + done + + {{- else }} + # number of currently deployed redis master nodes + currentMasterNodesNum="$(( {{ .Values.cluster.update.currentNumberOfNodes }} / (( {{ .Values.cluster.update.currentNumberOfReplicas }} + 1 )) ))" + # end postion of new replicas that should be assigned to original redis master nodes + slaveNodesEndPos="$(( {{ .Values.cluster.update.currentNumberOfNodes }} + (($REDIS_CLUSTER_REPLICAS - {{ .Values.cluster.update.currentNumberOfReplicas }})) * $currentMasterNodesNum ))" + for node in $(seq $((1+{{ .Values.cluster.update.currentNumberOfNodes }})) {{ .Values.cluster.nodes }}); do + newNodeIndex="$(($node - 1))" + newNodeIP=$(wait_for_dns_lookup "{{ template "common.names.fullname" . }}-${newNodeIndex}.{{ template "common.names.fullname" . }}-headless" 120 5) + {{- if .Values.tls.enabled }} + while [[ $(redis-cli -h "$newNodeIP" -p "$REDIS_TLS_PORT" --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} ping) != 'PONG' ]]; do + {{- else }} + while [[ $(redis-cli -h "$newNodeIP" -p "$REDIS_PORT" ping) != 'PONG' ]]; do + {{- end }} + echo "Node $newNodeIP not ready, waiting for all the nodes to be ready..." + newNodeIP=$(wait_for_dns_lookup "{{ template "common.names.fullname" . }}-${newNodeIndex}.{{ template "common.names.fullname" . }}-headless" 120 5) + sleep 5 + done + slave=() + # when the index of the new node is less than `slaveNodesEndPos`,the added node is a replica that assigned to original redis master node + # when the index of the new node is greater than or equal to `slaveNodesEndPos`,and it is not a multiple of `$REDIS_CLUSTER_REPLICAS + 1`, the added node is a replica that assigned to newly added master node + if (( $REDIS_CLUSTER_REPLICAS >= 1 )) && (( (( $newNodeIndex < $slaveNodesEndPos )) || (( (( $newNodeIndex >= $slaveNodesEndPos )) && (( $newNodeIndex % (( $REDIS_CLUSTER_REPLICAS + 1 )) )) )) )); then + slave+=("--cluster-slave") + fi + {{- if .Values.tls.enabled }} + while ! redis-cli --cluster add-node --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} "${newNodeIP}:${REDIS_TLS_PORT}" "${firstNodeIP}:${REDIS_TLS_PORT}" ${slave[@]}; do + {{- else }} + while ! redis-cli --cluster add-node "${newNodeIP}:${REDIS_PORT}" "${firstNodeIP}:${REDIS_PORT}" ${slave[@]}; do + {{- end }} + echo "Add-node ${newNodeIndex} ${newNodeIP} failed, retrying" + sleep 5 + firstNodeIP=$(wait_for_dns_lookup "{{ template "common.names.fullname" . }}-0.{{ template "common.names.fullname" . }}-headless" 120 5) + newNodeIP=$(wait_for_dns_lookup "{{ template "common.names.fullname" . }}-${newNodeIndex}.{{ template "common.names.fullname" . }}-headless" 120 5) + done + done + + {{- if .Values.tls.enabled }} + while ! redis-cli --cluster rebalance --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} "${firstNodeIP}:${REDIS_TLS_PORT}" --cluster-use-empty-masters; do + {{- else }} + while ! redis-cli --cluster rebalance "${firstNodeIP}:${REDIS_PORT}" --cluster-use-empty-masters; do + {{- end }} + echo "Rebalance failed, retrying" + sleep 5 + firstNodeIP=$(wait_for_dns_lookup "{{ template "common.names.fullname" . }}-0.{{ template "common.names.fullname" . }}-headless" 120 5) + {{- if .Values.tls.enabled }} + redis-cli --cluster fix --tls --cert ${REDIS_TLS_CERT_FILE} --key ${REDIS_TLS_KEY_FILE} --cacert ${REDIS_TLS_CA_FILE} "${firstNodeIP}:${REDIS_TLS_PORT}" + {{- else }} + redis-cli --cluster fix "${firstNodeIP}:${REDIS_PORT}" + {{- end }} + done + + {{- end }} + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + {{- if .Values.cluster.externalAccess.enabled }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_CERT_FILE + value: {{ template "redis-cluster.tlsCert" . }} + - name: REDIS_TLS_KEY_FILE + value: {{ template "redis-cluster.tlsCertKey" . }} + - name: REDIS_TLS_CA_FILE + value: {{ template "redis-cluster.tlsCACert" . }} + - name: REDIS_TLS_PORT + {{- else }} + - name: REDIS_PORT + {{- end }} + value: {{ .Values.cluster.externalAccess.service.port | quote }} + {{- else }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_CERT_FILE + value: {{ template "redis-cluster.tlsCert" . }} + - name: REDIS_TLS_KEY_FILE + value: {{ template "redis-cluster.tlsCertKey" . }} + - name: REDIS_TLS_CA_FILE + value: {{ template "redis-cluster.tlsCACert" . }} + - name: REDIS_TLS_PORT + {{- else }} + - name: REDIS_PORT + {{- end }} + value: {{ .Values.redis.containerPorts.redis | quote }} + {{- end }} + - name: REDIS_CLUSTER_REPLICAS + value: {{ .Values.cluster.replicas | quote }} + {{- if .Values.usePassword }} + - name: REDISCLI_AUTH + valueFrom: + secretKeyRef: + name: {{ template "redis-cluster.secretName" . }} + key: {{ template "redis-cluster.secretPasswordKey" . }} + {{- end }} + {{- if .Values.updateJob.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.updateJob.extraEnvVarsCM .Values.updateJob.extraEnvVarsSecret }} + envFrom: + {{- if .Values.updateJob.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.updateJob.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.updateJob.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.updateJob.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- end }} + {{- if .Values.updateJob.resources }} + resources: {{- toYaml .Values.updateJob.resources | nindent 12 }} + {{- end }} + {{- if or .Values.tls.enabled .Values.updateJob.extraVolumeMounts }} + volumeMounts: + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.updateJob.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- end }} + restartPolicy: OnFailure + {{- if or .Values.tls.enabled .Values.updateJob.extraVolumes }} + volumes: + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ include "common.tplvalues.render" (dict "value" .Values.tls.certificatesSecret "context" $) }} + {{- end }} + {{- if .Values.updateJob.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.updateJob.extraVolumes "context" $) | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} + diff --git a/rds/base/charts/redis-cluster/values.yaml b/rds/base/charts/redis-cluster/values.yaml new file mode 100644 index 0000000..ab68adc --- /dev/null +++ b/rds/base/charts/redis-cluster/values.yaml @@ -0,0 +1,980 @@ +## @section Global parameters +## 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, imagePullSecrets and storageClass +## + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## @param global.redis.password Redis® password (overrides `password`) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + redis: + password: "" + +## @section Redis® Cluster Common parameters +## + +## @param nameOverride String to partially override common.names.fullname template (will maintain the release name) +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname template +## +fullnameOverride: "" +## @param clusterDomain Kubernetes Cluster Domain +## +clusterDomain: cluster.local +## @param commonAnnotations Annotations to add to all deployed objects +## +commonAnnotations: {} +## @param commonLabels Labels to add to all deployed objects +## +commonLabels: {} +## @param extraDeploy Array of extra objects to deploy with the release (evaluated as a template) +## +extraDeploy: [] + +## Enable diagnostic mode in the deployment +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the deployment + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the deployment + ## + args: + - infinity + +## Bitnami Redis® image version +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## @param image.registry Redis® cluster image registry +## @param image.repository Redis® cluster image repository +## @param image.tag Redis® cluster image tag (immutable tags are recommended) +## @param image.pullPolicy Redis® cluster image pull policy +## @param image.pullSecrets Specify docker-registry secret names as an array +## @param image.debug Enable image debug mode +## +image: + registry: docker.io + repository: bitnami/redis-cluster + ## Bitnami Redis® image tag + ## ref: https://github.com/bitnami/bitnami-docker-redis#supported-tags-and-respective-dockerfile-links + ## + tag: 6.2.7-debian-11-r9 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Enable debug mode + ## + debug: false +## Network Policy +## @param networkPolicy.enabled Enable NetworkPolicy +## @param networkPolicy.allowExternal The Policy model to apply. Don't require client label for connections +## @param networkPolicy.ingressNSMatchLabels Allow connections from other namespacess. Just set label for namespace and set label for pods (optional). +## @param networkPolicy.ingressNSPodMatchLabels For other namespaces match by pod labels and namespace labels +## +networkPolicy: + enabled: false + ## 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 + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} + +serviceAccount: + ## @param serviceAccount.create Specifies whether a ServiceAccount should be created + ## + create: false + ## @param serviceAccount.name The name of the ServiceAccount to create + ## If not set and create is true, a name is generated using the fullname template + ## + name: "" + ## @param serviceAccount.annotations Annotations for Cassandra Service Account + ## + annotations: {} + ## @param serviceAccount.automountServiceAccountToken Automount API credentials for a service account. + ## + automountServiceAccountToken: false + +rbac: + ## @param rbac.create Specifies whether RBAC resources should be created + ## + create: false + role: + ## @param rbac.role.rules Rules to create. It follows the role specification + ## rules: + ## - apiGroups: + ## - extensions + ## resources: + ## - podsecuritypolicies + ## verbs: + ## - use + ## resourceNames: + ## - gce.unprivileged + ## + rules: [] +## Redis® pod Security Context +## @param podSecurityContext.enabled Enable Redis® pod Security Context +## @param podSecurityContext.fsGroup Group ID for the pods +## @param podSecurityContext.runAsUser User ID for the pods +## @param podSecurityContext.sysctls Set namespaced sysctls for the pods +## +podSecurityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + ## Uncomment the setting below to increase the net.core.somaxconn value + ## e.g: + ## sysctls: + ## - name: net.core.somaxconn + ## value: "10000" + ## + sysctls: [] +## @param podDisruptionBudget Limits the number of pods of the replicated application that are down simultaneously from voluntary disruptions +## ref: https://kubernetes.io/docs/concepts/workloads/pods/disruptions +## +podDisruptionBudget: {} +## @param minAvailable Min number of pods that must still be available after the eviction +## +minAvailable: "" +## @param maxUnavailable Max number of pods that can be unavailable after the eviction +## +maxUnavailable: "" +## Containers Security Context +## @param containerSecurityContext.enabled Enable Containers' Security Context +## @param containerSecurityContext.runAsUser User ID for the containers. +## @param containerSecurityContext.runAsNonRoot Run container as non root +## +containerSecurityContext: + enabled: true + runAsUser: 1001 + runAsNonRoot: true +## @param usePassword Use password authentication +## +usePassword: true +## @param password Redis® password (ignored if existingSecret set) +## 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: "" +## @param existingSecret Name of existing secret object (for password authentication) +## +existingSecret: "" +## @param existingSecretPasswordKey Name of key containing password to be retrieved from the existing secret +## +existingSecretPasswordKey: "" +## @param usePasswordFile Mount passwords as files instead of environment variables +## +usePasswordFile: false +## +## TLS configuration +## +tls: + ## @param tls.enabled Enable TLS support for replication traffic + ## + enabled: false + ## @param tls.authClients Require clients to authenticate or not + ## + authClients: true + ## @param tls.autoGenerated Generate automatically self-signed TLS certificates + ## + autoGenerated: false + ## @param tls.existingSecret The name of the existing secret that contains the TLS certificates + ## + existingSecret: "" + ## @param tls.certificatesSecret DEPRECATED. Use tls.existingSecret instead + ## + certificatesSecret: "" + ## @param tls.certFilename Certificate filename + ## + certFilename: "" + ## @param tls.certKeyFilename Certificate key filename + ## + certKeyFilename: "" + ## @param tls.certCAFilename CA Certificate filename + ## + certCAFilename: "" + ## @param tls.dhParamsFilename File containing DH params (in order to support DH based ciphers) + ## + dhParamsFilename: "" +## Redis® Service properties for standalone mode. +## +service: + ## @param service.ports.redis Kubernetes Redis service port + ## + ports: + redis: 6379 + ## Node ports to expose + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## @param service.nodePorts.redis Node port for Redis + ## + nodePorts: + redis: "" + ## @param service.extraPorts Extra ports to expose in the service (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param service.annotations 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: {} + ## @param service.labels Additional labels for redis service + ## + labels: {} + ## @param service.type Service type for default redis service + ## Setting this to LoadBalancer may require corresponding service annotations for loadbalancer creation to succeed. + ## Currently supported types are ClusterIP (default) and LoadBalancer + ## + type: ClusterIP + ## @param service.clusterIP Service Cluster IP + ## e.g.: + ## clusterIP: None + ## + clusterIP: "" + ## @param service.loadBalancerIP Load balancer IP if `service.type` is `LoadBalancer` + ## If service.type is LoadBalancer, request a specific static IP address if supported by the cloud provider, otherwise leave blank + ## + loadBalancerIP: "" + ## @param service.loadBalancerSourceRanges Service Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param service.externalTrafficPolicy Service external traffic policy + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP" + ## If "ClientIP", consecutive client requests will be directed to the same Pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} +## Enable persistence using Persistent Volume Claims +## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ +## +persistence: + ## @param persistence.path Path to mount the volume at, to use other images Redis® images. + ## + path: /bitnami/redis/data + ## @param persistence.subPath The subdirectory of the volume to mount to, useful in dev environments and one PV for multiple services + ## + subPath: "" + ## @param persistence.storageClass Storage class of backing PVC + ## 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: "" + ## @param persistence.annotations Persistent Volume Claim annotations + ## + annotations: {} + ## @param persistence.accessModes Persistent Volume Access Modes + ## + accessModes: + - ReadWriteOnce + ## @param persistence.size Size of data volume + ## + size: 8Gi + ## @param persistence.matchLabels Persistent Volume selectors + ## https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector + ## + matchLabels: {} + ## @param persistence.matchExpressions matchExpressions Persistent Volume selectors + ## + matchExpressions: {} + +## Init containers parameters: +## volumePermissions: Change the owner of the persist volume mountpoint to RunAsUser:fsGroup +## +volumePermissions: + ## @param 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) + ## + enabled: false + ## @param volumePermissions.image.registry Init container volume-permissions image registry + ## @param volumePermissions.image.repository Init container volume-permissions image repository + ## @param volumePermissions.image.tag Init container volume-permissions image tag + ## @param volumePermissions.image.pullPolicy Init container volume-permissions image pull policy + ## @param volumePermissions.image.pullSecrets Specify docker-registry secret names as an array + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 11-debian-11-r10 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param volumePermissions.resources.limits The resources limits for the container + ## @param volumePermissions.resources.requests The requested resources for the container + ## + resources: + ## Example: + ## limits: + ## cpu: 100m + ## memory: 128Mi + ## + limits: {} + ## Examples: + ## requests: + ## cpu: 100m + ## memory: 128Mi + ## + requests: {} +## PodSecurityPolicy configuration +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## @param podSecurityPolicy.create Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later +## +podSecurityPolicy: + create: false + +## @section Redis® statefulset parameters +## + +redis: + ## @param redis.command Redis® entrypoint string. The command `redis-server` is executed if this is not provided + ## + command: [] + ## @param redis.args Arguments for the provided command if needed + ## + args: [] + ## @param redis.updateStrategy.type Argo Workflows statefulset strategy type + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + ## + updateStrategy: + ## StrategyType + ## Can be set to RollingUpdate or OnDelete + ## + type: RollingUpdate + ## @param redis.updateStrategy.rollingUpdate.partition Partition update strategy + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions + ## + rollingUpdate: + partition: 0 + + ## @param redis.podManagementPolicy Statefulset Pod management policy, it needs to be Parallel to be able to complete the cluster join + ## Ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies + ## + podManagementPolicy: Parallel + ## @param redis.hostAliases Deployment pod host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param redis.hostNetwork Host networking requested for this pod. Use the host's network namespace. + ## https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podspec-v1-core + ## + hostNetwork: false + ## @param redis.useAOFPersistence Whether to use AOF Persistence mode or not + ## It is strongly recommended to use this type when dealing with clusters + ## ref: https://redis.io/topics/persistence#append-only-file + ## ref: https://redis.io/topics/cluster-tutorial#creating-and-using-a-redis-cluster + ## + useAOFPersistence: "yes" + ## @param redis.containerPorts.redis Redis® port + ## @param redis.containerPorts.bus The busPort should be obtained adding 10000 to the redisPort. By default: 10000 + 6379 = 16379 + ## + containerPorts: + redis: 6379 + bus: 16379 + ## @param redis.lifecycleHooks LifecycleHook to set additional configuration before or after startup. Evaluated as a template + ## + lifecycleHooks: {} + ## @param redis.extraVolumes Extra volumes to add to the deployment + ## + extraVolumes: [] + ## @param redis.extraVolumeMounts Extra volume mounts to add to the container + ## + extraVolumeMounts: [] + ## @param redis.customLivenessProbe Override default liveness probe + ## + customLivenessProbe: {} + ## @param redis.customReadinessProbe Override default readiness probe + ## + customReadinessProbe: {} + ## @param redis.customStartupProbe Custom startupProbe that overrides the default one + ## + customStartupProbe: {} + ## @param redis.initContainers Extra init containers to add to the deployment + ## + initContainers: [] + ## @param redis.sidecars Extra sidecar containers to add to the deployment + ## + sidecars: [] + ## @param redis.podLabels Additional labels for Redis® pod + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param redis.priorityClassName Redis® Master pod priorityClassName + ## + priorityClassName: "" + ## @param redis.configmap Additional Redis® configuration for the nodes + ## ref: https://redis.io/topics/config + ## + configmap: "" + ## @param redis.extraEnvVars An array to add extra environment variables + ## For example: + ## - name: BEARER_AUTH + ## value: true + ## + extraEnvVars: [] + ## @param redis.extraEnvVarsCM ConfigMap with extra environment variables + ## + extraEnvVarsCM: "" + ## @param redis.extraEnvVarsSecret Secret with extra environment variables + ## + extraEnvVarsSecret: "" + ## @param redis.podAnnotations Redis® additional annotations + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podAnnotations: {} + ## Redis® resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param redis.resources.limits The resources limits for the container + ## @param redis.resources.requests The requested resources for the container + ## + resources: + ## Example: + ## limits: + ## cpu: 100m + ## memory: 128Mi + ## + limits: {} + ## Examples: + ## requests: + ## cpu: 100m + ## memory: 128Mi + ## + requests: {} + ## @param redis.schedulerName Use an alternate scheduler, e.g. "stork". + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + schedulerName: "" + ## @param redis.shareProcessNamespace 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® liveness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## @param redis.livenessProbe.enabled Enable livenessProbe + ## @param redis.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param redis.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param redis.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param redis.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param redis.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + ## Configure extra options for Redis® readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) + ## @param redis.readinessProbe.enabled Enable readinessProbe + ## @param redis.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param redis.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param redis.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param redis.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param redis.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## @param redis.startupProbe.enabled Enable startupProbe + ## @param redis.startupProbe.path Path to check for startupProbe + ## @param redis.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param redis.startupProbe.periodSeconds Period seconds for startupProbe + ## @param redis.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param redis.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param redis.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: false + path: / + initialDelaySeconds: 300 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + ## @param redis.podAffinityPreset Redis® pod affinity preset. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param redis.podAntiAffinityPreset Redis® pod anti-affinity preset. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## Redis® node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param redis.nodeAffinityPreset.type Redis® node affinity preset type. Ignored if `redis.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param redis.nodeAffinityPreset.key Redis® node label key to match Ignored if `redis.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param redis.nodeAffinityPreset.values Redis® node label values to match. Ignored if `redis.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param redis.affinity Affinity settings for Redis® pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: redis.podAffinityPreset, redis.podAntiAffinityPreset, and redis.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param redis.nodeSelector Node labels for Redis® pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param redis.tolerations Tolerations for Redis® pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param redis.topologySpreadConstraints Pod topology spread constraints for Redis® pod + ## https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## The value is evaluated as a template + ## + topologySpreadConstraints: [] + +## @section Cluster update job parameters +## + +## Cluster update job settings +## +updateJob: + ## @param updateJob.activeDeadlineSeconds Number of seconds the Job to create the cluster will be waiting for the Nodes to be ready. + ## + activeDeadlineSeconds: 600 + ## @param updateJob.command Container command (using container default if not set) + ## + command: [] + ## @param updateJob.args Container args (using container default if not set) + ## + args: [] + ## @param updateJob.hostAliases Deployment pod host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param updateJob.annotations Job annotations + ## + annotations: {} + ## @param updateJob.podAnnotations Job pod annotations + ## + podAnnotations: {} + ## @param updateJob.podLabels Pod extra labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param updateJob.extraEnvVars An array to add extra environment variables + ## For example: + ## - name: BEARER_AUTH + ## value: true + ## + extraEnvVars: [] + ## @param updateJob.extraEnvVarsCM ConfigMap containing extra environment variables + ## + extraEnvVarsCM: "" + ## @param updateJob.extraEnvVarsSecret Secret containing extra environment variables + ## + extraEnvVarsSecret: "" + ## @param updateJob.extraVolumes Extra volumes to add to the deployment + ## + extraVolumes: [] + ## @param updateJob.extraVolumeMounts Extra volume mounts to add to the container + ## + extraVolumeMounts: [] + ## @param updateJob.initContainers Extra init containers to add to the deployment + ## + initContainers: [] + ## @param updateJob.podAffinityPreset Update job pod affinity preset. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param updateJob.podAntiAffinityPreset Update job pod anti-affinity preset. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## Update job node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param updateJob.nodeAffinityPreset.type Update job node affinity preset type. Ignored if `updateJob.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param updateJob.nodeAffinityPreset.key Update job node label key to match Ignored if `updateJob.affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param updateJob.nodeAffinityPreset.values Update job node label values to match. Ignored if `updateJob.affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param updateJob.affinity Affinity for update job pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## Note: updateJob.podAffinityPreset, updateJob.podAntiAffinityPreset, and updateJob.nodeAffinityPreset will be ignored when it's set + ## + affinity: {} + ## @param updateJob.nodeSelector Node labels for update job pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param updateJob.tolerations Tolerations for update job pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param updateJob.priorityClassName Priority class name + ## + priorityClassName: "" + ## Container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-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:'. + ## @param updateJob.resources.limits The resources limits for the container + ## @param updateJob.resources.requests The requested resources for the container + ## + resources: + ## Example: + ## limits: + ## cpu: 500m + ## memory: 1Gi + ## + limits: {} + ## Examples: + ## requests: + ## cpu: 250m + ## memory: 256Mi + ## + requests: {} + +## @section Cluster management parameters +## + +## Redis® Cluster settings +## +cluster: + ## @param cluster.init Enable the initialization of the Redis® Cluster + ## + init: true + ## Number of Redis® nodes to be deployed + ## + ## Note: + ## This is total number of nodes including the replicas. Meaning there will be 3 master and 3 replica + ## nodes (as replica count is set to 1 by default, there will be 1 replica per master node). + ## Hence, nodes = numberOfMasterNodes + numberOfMasterNodes * replicas + ## + ## @param cluster.nodes The number of master nodes should always be >= 3, otherwise cluster creation will fail + ## + nodes: 6 + ## @param cluster.replicas Number of replicas for every master in the cluster + ## Parameter to be passed as --cluster-replicas to the redis-cli --cluster create + ## 1 means that we want a replica for every master created + ## + replicas: 1 + ## Configuration to access the Redis® Cluster from outside the Kubernetes cluster + ## + externalAccess: + ## @param cluster.externalAccess.enabled Enable access to the Redis + ## + enabled: false + service: + ## @param cluster.externalAccess.service.type Type for the services used to expose every Pod + ## At this moment only LoadBalancer is supported + ## + type: LoadBalancer + ## @param cluster.externalAccess.service.port Port for the services used to expose every Pod + ## + port: 6379 + ## @param cluster.externalAccess.service.loadBalancerIP Array of load balancer IPs for each Redis® node. Length must be the same as cluster.nodes + ## + loadBalancerIP: [] + ## @param cluster.externalAccess.service.loadBalancerSourceRanges Service Load Balancer sources + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param cluster.externalAccess.service.annotations Annotations to add to the services used to expose every Pod of the Redis® Cluster + ## + annotations: {} + ## This section allows to update the Redis® cluster nodes. + ## + update: + ## @param cluster.update.addNodes Boolean to specify if you want to add nodes after the upgrade + ## Setting this to true a hook will add nodes to the Redis® cluster after the upgrade. currentNumberOfNodes and currentNumberOfReplicas is required + ## + addNodes: false + ## @param cluster.update.currentNumberOfNodes Number of currently deployed Redis® nodes + ## + currentNumberOfNodes: 6 + ## @param cluster.update.currentNumberOfReplicas Number of currently deployed Redis® replicas + ## + currentNumberOfReplicas: 1 + ## @param cluster.update.newExternalIPs External IPs obtained from the services for the new nodes to add to the cluster + ## + newExternalIPs: [] + +## @section Metrics sidecar parameters +## + +## Prometheus Exporter / Metrics +## +metrics: + ## @param metrics.enabled Start a side-car prometheus exporter + ## + enabled: false + ## @param metrics.image.registry Redis® exporter image registry + ## @param metrics.image.repository Redis® exporter image name + ## @param metrics.image.tag Redis® exporter image tag + ## @param metrics.image.pullPolicy Redis® exporter image pull policy + ## @param metrics.image.pullSecrets Specify docker-registry secret names as an array + ## + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.43.0-debian-11-r3 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param metrics.resources Metrics exporter resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## + resources: {} + ## @param metrics.extraArgs Extra arguments for the binary; possible values [here](https://github.com/oliver006/redis_exporter + ## extraArgs: + ## check-keys: myKey,myOtherKey + ## + extraArgs: {} + ## @param metrics.podAnnotations [object] Additional annotations for Metrics exporter pod + ## + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + ## @param metrics.podLabels Additional labels for Metrics exporter pod + ## + podLabels: {} + ## Enable this if you're using https://github.com/coreos/prometheus-operator + ## + serviceMonitor: + ## @param metrics.serviceMonitor.enabled If `true`, creates a Prometheus Operator ServiceMonitor (also requires `metrics.enabled` to be `true`) + ## + enabled: false + ## @param metrics.serviceMonitor.namespace Optional namespace which Prometheus is running in + ## + namespace: "" + ## @param metrics.serviceMonitor.interval How frequently to scrape metrics (use by default, falling back to Prometheus' default) + ## + interval: "" + ## @param metrics.serviceMonitor.scrapeTimeout Timeout after which the scrape is ended + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint + ## e.g: + ## scrapeTimeout: 10s + ## + scrapeTimeout: "" + ## @param metrics.serviceMonitor.selector Prometheus instance selector labels + ## ref: https://github.com/bitnami/charts/tree/master/bitnami/prometheus-operator#prometheus-configuration + ## e.g: + ## selector: + ## prometheus: my-prometheus + ## + selector: {} + ## @param metrics.serviceMonitor.labels ServiceMonitor extra labels + ## + labels: {} + ## @param metrics.serviceMonitor.annotations ServiceMonitor annotations + ## + annotations: {} + ## @param metrics.serviceMonitor.jobLabel The name of the label on the target service to use as the job name in prometheus. + ## + jobLabel: "" + ## @param metrics.serviceMonitor.relabelings RelabelConfigs to apply to samples before scraping + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig + ## + relabelings: [] + ## @param metrics.serviceMonitor.metricRelabelings MetricRelabelConfigs to apply to samples before ingestion + ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig + ## + metricRelabelings: [] + ## 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 + ## @param metrics.prometheusRule.enabled Set this to true to create prometheusRules for Prometheus operator + ## @param metrics.prometheusRule.additionalLabels Additional labels that can be used so prometheusRules will be discovered by Prometheus + ## @param metrics.prometheusRule.namespace namespace where prometheusRules resource should be created + ## @param metrics.prometheusRule.rules Create specified [rules](https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/), check values for an example. + ## + prometheusRule: + enabled: false + additionalLabels: {} + namespace: "" + ## These are just examples rules, please adapt them to your needs. + ## Make sure to constraint the rules to the current postgresql service. + ## - alert: RedisDown + ## expr: redis_up{service="{{ template "common.names.fullname" . }}-metrics"} == 0 + ## for: 2m + ## labels: + ## severity: error + ## annotations: + ## summary: Redis® instance {{ "{{ $instance }}" }} down + ## description: Redis® instance {{ "{{ $instance }}" }} is down. + ## - alert: RedisMemoryHigh + ## expr: > + ## redis_memory_used_bytes{service="{{ template "common.names.fullname" . }}-metrics"} * 100 + ## / + ## redis_memory_max_bytes{service="{{ template "common.names.fullname" . }}-metrics"} + ## > 90 + ## for: 2m + ## labels: + ## severity: error + ## annotations: + ## summary: Redis® instance {{ "{{ $instance }}" }} is using too much memory + ## description: Redis® instance {{ "{{ $instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + ## - alert: RedisKeyEviction + ## expr: increase(redis_evicted_keys_total{service="{{ template "common.names.fullname" . }}-metrics"}[5m]) > 0 + ## for: 1s + ## labels: + ## severity: error + ## annotations: + ## summary: Redis® instance {{ "{{ $instance }}" }} has evicted keys + ## description: Redis® instance {{ "{{ $instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. + ## + rules: [] + ## @param metrics.priorityClassName Metrics exporter pod priorityClassName + ## + priorityClassName: "" + ## @param metrics.service.type Kubernetes Service type (redis metrics) + ## @param metrics.service.loadBalancerIP Use serviceLoadBalancerIP to request a specific static IP, otherwise leave blank + ## @param metrics.service.annotations Annotations for the services to monitor. + ## @param metrics.service.labels Additional labels for the metrics service + ## + service: + type: ClusterIP + ## @param metrics.service.clusterIP Service Cluster IP + ## e.g.: + ## clusterIP: None + ## + clusterIP: "" + loadBalancerIP: "" + annotations: {} + labels: {} + +## @section Sysctl Image parameters +## + +## Sysctl InitContainer +## Used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +## +sysctlImage: + ## @param sysctlImage.enabled Enable an init container to modify Kernel settings + ## + enabled: false + ## @param sysctlImage.command sysctlImage command to execute + ## + command: [] + ## @param sysctlImage.registry sysctlImage Init container registry + ## @param sysctlImage.repository sysctlImage Init container repository + ## @param sysctlImage.tag sysctlImage Init container tag + ## @param sysctlImage.pullPolicy sysctlImage Init container pull policy + ## @param sysctlImage.pullSecrets Specify docker-registry secret names as an array + ## + registry: docker.io + repository: bitnami/bitnami-shell + tag: 11-debian-11-r10 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param sysctlImage.mountHostSys Mount the host `/sys` folder to `/host-sys` + ## + mountHostSys: false + ## Container resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param sysctlImage.resources.limits The resources limits for the container + ## @param sysctlImage.resources.requests The requested resources for the container + ## + resources: + ## Example: + ## limits: + ## cpu: 100m + ## memory: 128Mi + ## + limits: {} + ## Examples: + ## requests: + ## cpu: 100m + ## memory: 128Mi + ## + requests: {} diff --git a/rds/base/charts/redis/.helmignore b/rds/base/charts/redis/.helmignore new file mode 100644 index 0000000..f0c1319 --- /dev/null +++ b/rds/base/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/rds/base/charts/redis/Chart.lock b/rds/base/charts/redis/Chart.lock new file mode 100644 index 0000000..863ab5d --- /dev/null +++ b/rds/base/charts/redis/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://charts/common + version: 1.16.0 +digest: sha256:4ada3eb952477c2d0cedb8b58c4dd4351576124c08e2b597bb8d7a85a821d0b3 +generated: "2023-02-07T10:50:01.265240461+01:00" diff --git a/rds/base/charts/redis/Chart.yaml b/rds/base/charts/redis/Chart.yaml new file mode 100644 index 0000000..19c84bb --- /dev/null +++ b/rds/base/charts/redis/Chart.yaml @@ -0,0 +1,29 @@ +annotations: + category: Database +apiVersion: v2 +appVersion: 6.2.7 +dependencies: +- name: common + repository: file://charts/common + tags: + - bitnami-common + alias: redis-common + version: 1.x.x +description: Redis(R) is an 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. +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: +- name: Bitnami + url: https://github.com/bitnami/charts +- email: cedric@desaintmartin.fr + name: desaintmartin +name: redis +sources: +- https://github.com/bitnami/bitnami-docker-redis +version: 16.13.2 diff --git a/rds/base/charts/redis/README.md b/rds/base/charts/redis/README.md new file mode 100644 index 0000000..5113c05 --- /dev/null +++ b/rds/base/charts/redis/README.md @@ -0,0 +1,898 @@ + + +# Bitnami package for Redis(R) + +Redis(R) is an 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. + +[Overview of Redis®](http://redis.io) + +Disclaimer: Redis is a registered trademark of Redis Ltd. Any rights therein are reserved to Redis Ltd. Any use by Bitnami is for referential purposes only and does not indicate any sponsorship, endorsement, or affiliation between Redis Ltd. + +## TL;DR + +```bash +$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm install my-release bitnami/redis +``` + +## Introduction + +This chart bootstraps a [Redis®](https://github.com/bitnami/bitnami-docker-redis) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. + +### 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. + +1. [Redis® Helm Chart](https://github.com/bitnami/charts/tree/master/bitnami/redis) will deploy a master-replica cluster, with the [option](https://github.com/bitnami/charts/tree/master/bitnami/redis#redis-sentinel-configuration-parameters) of enabling using Redis® Sentinel. +2. [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.19+ +- Helm 3.2.0+ +- 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 + +### Global parameters + +| Name | Description | Value | +| ------------------------- | ------------------------------------------------------ | ----- | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.storageClass` | Global StorageClass for Persistent Volume(s) | `""` | +| `global.redis.password` | Global Redis® password (overrides `auth.password`) | `""` | + + +### Common parameters + +| Name | Description | Value | +| ------------------------ | --------------------------------------------------------------------------------------- | --------------- | +| `kubeVersion` | Override Kubernetes version | `""` | +| `nameOverride` | String to partially override common.names.fullname | `""` | +| `fullnameOverride` | String to fully override common.names.fullname | `""` | +| `commonLabels` | Labels to add to all deployed objects | `{}` | +| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | +| `secretAnnotations` | Annotations to add to secret | `{}` | +| `clusterDomain` | Kubernetes cluster domain name | `cluster.local` | +| `extraDeploy` | Array of extra objects to deploy with the release | `[]` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the deployment | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the deployment | `["infinity"]` | + + +### Redis® Image parameters + +| Name | Description | Value | +| ------------------- | ----------------------------------------------------- | --------------------- | +| `image.registry` | Redis® image registry | `docker.io` | +| `image.repository` | Redis® image repository | `bitnami/redis` | +| `image.tag` | Redis® image tag (immutable tags are recommended) | `6.2.7-debian-11-r11` | +| `image.pullPolicy` | Redis® image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Redis® image pull secrets | `[]` | +| `image.debug` | Enable image debug mode | `false` | + + +### Redis® common configuration parameters + +| Name | Description | Value | +| -------------------------------- | ------------------------------------------------------------------------------------- | ------------- | +| `architecture` | Redis® architecture. Allowed values: `standalone` or `replication` | `replication` | +| `auth.enabled` | Enable password authentication | `true` | +| `auth.sentinel` | Enable password authentication on sentinels too | `true` | +| `auth.password` | Redis® password | `""` | +| `auth.existingSecret` | The name of an existing secret with Redis® credentials | `""` | +| `auth.existingSecretPasswordKey` | Password key to be retrieved from existing secret | `""` | +| `auth.usePasswordFiles` | Mount credentials as files instead of using an environment variable | `false` | +| `commonConfiguration` | Common configuration to be added into the ConfigMap | `""` | +| `existingConfigmap` | The name of an existing ConfigMap with your custom configuration for Redis® nodes | `""` | + + +### Redis® master configuration parameters + +| Name | Description | Value | +| ------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------ | +| `master.count` | Number of Redis® master instances to deploy (experimental, requires additional configuration) | `1` | +| `master.configuration` | Configuration for Redis® master nodes | `""` | +| `master.disableCommands` | Array with Redis® commands to disable on master nodes | `["FLUSHDB","FLUSHALL"]` | +| `master.command` | Override default container command (useful when using custom images) | `[]` | +| `master.args` | Override default container args (useful when using custom images) | `[]` | +| `master.preExecCmds` | Additional commands to run prior to starting Redis® master | `[]` | +| `master.extraFlags` | Array with additional command line flags for Redis® master | `[]` | +| `master.extraEnvVars` | Array with extra environment variables to add to Redis® master nodes | `[]` | +| `master.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for Redis® master nodes | `""` | +| `master.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for Redis® master nodes | `""` | +| `master.containerPorts.redis` | Container port to open on Redis® master nodes | `6379` | +| `master.startupProbe.enabled` | Enable startupProbe on Redis® master nodes | `false` | +| `master.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `20` | +| `master.startupProbe.periodSeconds` | Period seconds for startupProbe | `5` | +| `master.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `5` | +| `master.startupProbe.failureThreshold` | Failure threshold for startupProbe | `5` | +| `master.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `master.livenessProbe.enabled` | Enable livenessProbe on Redis® master nodes | `true` | +| `master.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `20` | +| `master.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `5` | +| `master.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `master.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `5` | +| `master.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `master.readinessProbe.enabled` | Enable readinessProbe on Redis® master nodes | `true` | +| `master.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `20` | +| `master.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` | +| `master.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `master.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `5` | +| `master.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `master.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `master.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `master.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `master.resources.limits` | The resources limits for the Redis® master containers | `{}` | +| `master.resources.requests` | The requested resources for the Redis® master containers | `{}` | +| `master.podSecurityContext.enabled` | Enabled Redis® master pods' Security Context | `true` | +| `master.podSecurityContext.fsGroup` | Set Redis® master pod's Security Context fsGroup | `1001` | +| `master.containerSecurityContext.enabled` | Enabled Redis® master containers' Security Context | `true` | +| `master.containerSecurityContext.runAsUser` | Set Redis® master containers' Security Context runAsUser | `1001` | +| `master.kind` | Use either Deployment or StatefulSet (default) | `StatefulSet` | +| `master.schedulerName` | Alternate scheduler for Redis® master pods | `""` | +| `master.updateStrategy.type` | Redis® master statefulset strategy type | `RollingUpdate` | +| `master.priorityClassName` | Redis® master pods' priorityClassName | `""` | +| `master.hostAliases` | Redis® master pods host aliases | `[]` | +| `master.podLabels` | Extra labels for Redis® master pods | `{}` | +| `master.podAnnotations` | Annotations for Redis® master pods | `{}` | +| `master.shareProcessNamespace` | Share a single process namespace between all of the containers in Redis® master pods | `false` | +| `master.podAffinityPreset` | Pod affinity preset. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `master.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `master.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `master.nodeAffinityPreset.key` | Node label key to match. Ignored if `master.affinity` is set | `""` | +| `master.nodeAffinityPreset.values` | Node label values to match. Ignored if `master.affinity` is set | `[]` | +| `master.affinity` | Affinity for Redis® master pods assignment | `{}` | +| `master.nodeSelector` | Node labels for Redis® master pods assignment | `{}` | +| `master.tolerations` | Tolerations for Redis® master pods assignment | `[]` | +| `master.topologySpreadConstraints` | Spread Constraints for Redis® master pod assignment | `[]` | +| `master.dnsPolicy` | DNS Policy for Redis® master pod | `""` | +| `master.dnsConfig` | DNS Configuration for Redis® master pod | `{}` | +| `master.lifecycleHooks` | for the Redis® master container(s) to automate configuration before or after startup | `{}` | +| `master.extraVolumes` | Optionally specify extra list of additional volumes for the Redis® master pod(s) | `[]` | +| `master.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Redis® master container(s) | `[]` | +| `master.sidecars` | Add additional sidecar containers to the Redis® master pod(s) | `[]` | +| `master.initContainers` | Add additional init containers to the Redis® master pod(s) | `[]` | +| `master.persistence.enabled` | Enable persistence on Redis® master nodes using Persistent Volume Claims | `true` | +| `master.persistence.medium` | Provide a medium for `emptyDir` volumes. | `""` | +| `master.persistence.sizeLimit` | Set this to enable a size limit for `emptyDir` volumes. | `""` | +| `master.persistence.path` | The path the volume will be mounted at on Redis® master containers | `/data` | +| `master.persistence.subPath` | The subdirectory of the volume to mount on Redis® master containers | `""` | +| `master.persistence.storageClass` | Persistent Volume storage class | `""` | +| `master.persistence.accessModes` | Persistent Volume access modes | `["ReadWriteOnce"]` | +| `master.persistence.size` | Persistent Volume size | `8Gi` | +| `master.persistence.annotations` | Additional custom annotations for the PVC | `{}` | +| `master.persistence.selector` | Additional labels to match for the PVC | `{}` | +| `master.persistence.dataSource` | Custom PVC data source | `{}` | +| `master.persistence.existingClaim` | Use a existing PVC which must be created manually before bound | `""` | +| `master.service.type` | Redis® master service type | `ClusterIP` | +| `master.service.ports.redis` | Redis® master service port | `6379` | +| `master.service.nodePorts.redis` | Node port for Redis® master | `""` | +| `master.service.externalTrafficPolicy` | Redis® master service external traffic policy | `Cluster` | +| `master.service.extraPorts` | Extra ports to expose (normally used with the `sidecar` value) | `[]` | +| `master.service.internalTrafficPolicy` | Redis® master service internal traffic policy (requires Kubernetes v1.22 or greater to be usable) | `Cluster` | +| `master.service.clusterIP` | Redis® master service Cluster IP | `""` | +| `master.service.loadBalancerIP` | Redis® master service Load Balancer IP | `""` | +| `master.service.loadBalancerSourceRanges` | Redis® master service Load Balancer sources | `[]` | +| `master.service.annotations` | Additional custom annotations for Redis® master service | `{}` | +| `master.service.sessionAffinity` | Session Affinity for Kubernetes service, can be "None" or "ClientIP" | `None` | +| `master.service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `master.terminationGracePeriodSeconds` | Integer setting the termination grace period for the redis-master pods | `30` | + + +### Redis® replicas configuration parameters + +| Name | Description | Value | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------ | +| `replica.replicaCount` | Number of Redis® replicas to deploy | `3` | +| `replica.configuration` | Configuration for Redis® replicas nodes | `""` | +| `replica.disableCommands` | Array with Redis® commands to disable on replicas nodes | `["FLUSHDB","FLUSHALL"]` | +| `replica.command` | Override default container command (useful when using custom images) | `[]` | +| `replica.args` | Override default container args (useful when using custom images) | `[]` | +| `replica.preExecCmds` | Additional commands to run prior to starting Redis® replicas | `[]` | +| `replica.extraFlags` | Array with additional command line flags for Redis® replicas | `[]` | +| `replica.extraEnvVars` | Array with extra environment variables to add to Redis® replicas nodes | `[]` | +| `replica.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for Redis® replicas nodes | `""` | +| `replica.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for Redis® replicas nodes | `""` | +| `replica.externalMaster.enabled` | Use external master for bootstrapping | `false` | +| `replica.externalMaster.host` | External master host to bootstrap from | `""` | +| `replica.externalMaster.port` | Port for Redis service external master host | `6379` | +| `replica.containerPorts.redis` | Container port to open on Redis® replicas nodes | `6379` | +| `replica.startupProbe.enabled` | Enable startupProbe on Redis® replicas nodes | `true` | +| `replica.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `10` | +| `replica.startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `replica.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `5` | +| `replica.startupProbe.failureThreshold` | Failure threshold for startupProbe | `22` | +| `replica.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `replica.livenessProbe.enabled` | Enable livenessProbe on Redis® replicas nodes | `true` | +| `replica.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `20` | +| `replica.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `5` | +| `replica.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `replica.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `5` | +| `replica.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `replica.readinessProbe.enabled` | Enable readinessProbe on Redis® replicas nodes | `true` | +| `replica.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `20` | +| `replica.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` | +| `replica.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `replica.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `5` | +| `replica.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `replica.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `replica.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `replica.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `replica.resources.limits` | The resources limits for the Redis® replicas containers | `{}` | +| `replica.resources.requests` | The requested resources for the Redis® replicas containers | `{}` | +| `replica.podSecurityContext.enabled` | Enabled Redis® replicas pods' Security Context | `true` | +| `replica.podSecurityContext.fsGroup` | Set Redis® replicas pod's Security Context fsGroup | `1001` | +| `replica.containerSecurityContext.enabled` | Enabled Redis® replicas containers' Security Context | `true` | +| `replica.containerSecurityContext.runAsUser` | Set Redis® replicas containers' Security Context runAsUser | `1001` | +| `replica.schedulerName` | Alternate scheduler for Redis® replicas pods | `""` | +| `replica.updateStrategy.type` | Redis® replicas statefulset strategy type | `RollingUpdate` | +| `replica.priorityClassName` | Redis® replicas pods' priorityClassName | `""` | +| `replica.podManagementPolicy` | podManagementPolicy to manage scaling operation of %%MAIN_CONTAINER_NAME%% pods | `""` | +| `replica.hostAliases` | Redis® replicas pods host aliases | `[]` | +| `replica.podLabels` | Extra labels for Redis® replicas pods | `{}` | +| `replica.podAnnotations` | Annotations for Redis® replicas pods | `{}` | +| `replica.shareProcessNamespace` | Share a single process namespace between all of the containers in Redis® replicas pods | `false` | +| `replica.podAffinityPreset` | Pod affinity preset. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `replica.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `replica.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `replica.nodeAffinityPreset.key` | Node label key to match. Ignored if `replica.affinity` is set | `""` | +| `replica.nodeAffinityPreset.values` | Node label values to match. Ignored if `replica.affinity` is set | `[]` | +| `replica.affinity` | Affinity for Redis® replicas pods assignment | `{}` | +| `replica.nodeSelector` | Node labels for Redis® replicas pods assignment | `{}` | +| `replica.tolerations` | Tolerations for Redis® replicas pods assignment | `[]` | +| `replica.topologySpreadConstraints` | Spread Constraints for Redis® replicas pod assignment | `[]` | +| `replica.dnsPolicy` | DNS Policy for Redis® replica pods | `""` | +| `replica.dnsConfig` | DNS Configuration for Redis® replica pods | `{}` | +| `replica.lifecycleHooks` | for the Redis® replica container(s) to automate configuration before or after startup | `{}` | +| `replica.extraVolumes` | Optionally specify extra list of additional volumes for the Redis® replicas pod(s) | `[]` | +| `replica.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Redis® replicas container(s) | `[]` | +| `replica.sidecars` | Add additional sidecar containers to the Redis® replicas pod(s) | `[]` | +| `replica.initContainers` | Add additional init containers to the Redis® replicas pod(s) | `[]` | +| `replica.persistence.enabled` | Enable persistence on Redis® replicas nodes using Persistent Volume Claims | `true` | +| `replica.persistence.medium` | Provide a medium for `emptyDir` volumes. | `""` | +| `replica.persistence.sizeLimit` | Set this to enable a size limit for `emptyDir` volumes. | `""` | +| `replica.persistence.path` | The path the volume will be mounted at on Redis® replicas containers | `/data` | +| `replica.persistence.subPath` | The subdirectory of the volume to mount on Redis® replicas containers | `""` | +| `replica.persistence.storageClass` | Persistent Volume storage class | `""` | +| `replica.persistence.accessModes` | Persistent Volume access modes | `["ReadWriteOnce"]` | +| `replica.persistence.size` | Persistent Volume size | `8Gi` | +| `replica.persistence.annotations` | Additional custom annotations for the PVC | `{}` | +| `replica.persistence.selector` | Additional labels to match for the PVC | `{}` | +| `replica.persistence.dataSource` | Custom PVC data source | `{}` | +| `replica.persistence.existingClaim` | Use a existing PVC which must be created manually before bound | `""` | +| `replica.service.type` | Redis® replicas service type | `ClusterIP` | +| `replica.service.ports.redis` | Redis® replicas service port | `6379` | +| `replica.service.nodePorts.redis` | Node port for Redis® replicas | `""` | +| `replica.service.externalTrafficPolicy` | Redis® replicas service external traffic policy | `Cluster` | +| `replica.service.internalTrafficPolicy` | Redis® replicas service internal traffic policy (requires Kubernetes v1.22 or greater to be usable) | `Cluster` | +| `replica.service.extraPorts` | Extra ports to expose (normally used with the `sidecar` value) | `[]` | +| `replica.service.clusterIP` | Redis® replicas service Cluster IP | `""` | +| `replica.service.loadBalancerIP` | Redis® replicas service Load Balancer IP | `""` | +| `replica.service.loadBalancerSourceRanges` | Redis® replicas service Load Balancer sources | `[]` | +| `replica.service.annotations` | Additional custom annotations for Redis® replicas service | `{}` | +| `replica.service.sessionAffinity` | Session Affinity for Kubernetes service, can be "None" or "ClientIP" | `None` | +| `replica.service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `replica.terminationGracePeriodSeconds` | Integer setting the termination grace period for the redis-replicas pods | `30` | +| `replica.autoscaling.enabled` | Enable replica autoscaling settings | `false` | +| `replica.autoscaling.minReplicas` | Minimum replicas for the pod autoscaling | `1` | +| `replica.autoscaling.maxReplicas` | Maximum replicas for the pod autoscaling | `11` | +| `replica.autoscaling.targetCPU` | Percentage of CPU to consider when autoscaling | `""` | +| `replica.autoscaling.targetMemory` | Percentage of Memory to consider when autoscaling | `""` | + + +### Redis® Sentinel configuration parameters + +| Name | Description | Value | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `sentinel.enabled` | Use Redis® Sentinel on Redis® pods. | `false` | +| `sentinel.image.registry` | Redis® Sentinel image registry | `docker.io` | +| `sentinel.image.repository` | Redis® Sentinel image repository | `bitnami/redis-sentinel` | +| `sentinel.image.tag` | Redis® Sentinel image tag (immutable tags are recommended) | `6.2.7-debian-11-r12` | +| `sentinel.image.pullPolicy` | Redis® Sentinel image pull policy | `IfNotPresent` | +| `sentinel.image.pullSecrets` | Redis® Sentinel image pull secrets | `[]` | +| `sentinel.image.debug` | Enable image debug mode | `false` | +| `sentinel.masterSet` | Master set name | `mymaster` | +| `sentinel.quorum` | Sentinel Quorum | `2` | +| `sentinel.getMasterTimeout` | Amount of time to allow before get_sentinel_master_info() times out. | `220` | +| `sentinel.automateClusterRecovery` | Automate cluster recovery in cases where the last replica is not considered a good replica and Sentinel won't automatically failover to it. | `false` | +| `sentinel.downAfterMilliseconds` | Timeout for detecting a Redis® node is down | `60000` | +| `sentinel.failoverTimeout` | Timeout for performing a election failover | `18000` | +| `sentinel.parallelSyncs` | Number of replicas that can be reconfigured in parallel to use the new master after a failover | `1` | +| `sentinel.configuration` | Configuration for Redis® Sentinel nodes | `""` | +| `sentinel.command` | Override default container command (useful when using custom images) | `[]` | +| `sentinel.args` | Override default container args (useful when using custom images) | `[]` | +| `sentinel.preExecCmds` | Additional commands to run prior to starting Redis® Sentinel | `[]` | +| `sentinel.extraEnvVars` | Array with extra environment variables to add to Redis® Sentinel nodes | `[]` | +| `sentinel.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for Redis® Sentinel nodes | `""` | +| `sentinel.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for Redis® Sentinel nodes | `""` | +| `sentinel.externalMaster.enabled` | Use external master for bootstrapping | `false` | +| `sentinel.externalMaster.host` | External master host to bootstrap from | `""` | +| `sentinel.externalMaster.port` | Port for Redis service external master host | `6379` | +| `sentinel.containerPorts.sentinel` | Container port to open on Redis® Sentinel nodes | `26379` | +| `sentinel.startupProbe.enabled` | Enable startupProbe on Redis® Sentinel nodes | `true` | +| `sentinel.startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `10` | +| `sentinel.startupProbe.periodSeconds` | Period seconds for startupProbe | `10` | +| `sentinel.startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `5` | +| `sentinel.startupProbe.failureThreshold` | Failure threshold for startupProbe | `22` | +| `sentinel.startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `sentinel.livenessProbe.enabled` | Enable livenessProbe on Redis® Sentinel nodes | `true` | +| `sentinel.livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `20` | +| `sentinel.livenessProbe.periodSeconds` | Period seconds for livenessProbe | `5` | +| `sentinel.livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `sentinel.livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `5` | +| `sentinel.livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `sentinel.readinessProbe.enabled` | Enable readinessProbe on Redis® Sentinel nodes | `true` | +| `sentinel.readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `20` | +| `sentinel.readinessProbe.periodSeconds` | Period seconds for readinessProbe | `5` | +| `sentinel.readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `sentinel.readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `5` | +| `sentinel.readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `sentinel.customStartupProbe` | Custom startupProbe that overrides the default one | `{}` | +| `sentinel.customLivenessProbe` | Custom livenessProbe that overrides the default one | `{}` | +| `sentinel.customReadinessProbe` | Custom readinessProbe that overrides the default one | `{}` | +| `sentinel.persistence.enabled` | Enable persistence on Redis® sentinel nodes using Persistent Volume Claims (Experimental) | `false` | +| `sentinel.persistence.storageClass` | Persistent Volume storage class | `""` | +| `sentinel.persistence.accessModes` | Persistent Volume access modes | `["ReadWriteOnce"]` | +| `sentinel.persistence.size` | Persistent Volume size | `100Mi` | +| `sentinel.persistence.annotations` | Additional custom annotations for the PVC | `{}` | +| `sentinel.persistence.selector` | Additional labels to match for the PVC | `{}` | +| `sentinel.persistence.dataSource` | Custom PVC data source | `{}` | +| `sentinel.persistence.medium` | Provide a medium for `emptyDir` volumes. | `""` | +| `sentinel.resources.limits` | The resources limits for the Redis® Sentinel containers | `{}` | +| `sentinel.resources.requests` | The requested resources for the Redis® Sentinel containers | `{}` | +| `sentinel.containerSecurityContext.enabled` | Enabled Redis® Sentinel containers' Security Context | `true` | +| `sentinel.containerSecurityContext.runAsUser` | Set Redis® Sentinel containers' Security Context runAsUser | `1001` | +| `sentinel.lifecycleHooks` | for the Redis® sentinel container(s) to automate configuration before or after startup | `{}` | +| `sentinel.extraVolumes` | Optionally specify extra list of additional volumes for the Redis® Sentinel | `[]` | +| `sentinel.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Redis® Sentinel container(s) | `[]` | +| `sentinel.service.type` | Redis® Sentinel service type | `ClusterIP` | +| `sentinel.service.ports.redis` | Redis® service port for Redis® | `6379` | +| `sentinel.service.ports.sentinel` | Redis® service port for Redis® Sentinel | `26379` | +| `sentinel.service.nodePorts.redis` | Node port for Redis® | `""` | +| `sentinel.service.nodePorts.sentinel` | Node port for Sentinel | `""` | +| `sentinel.service.externalTrafficPolicy` | Redis® Sentinel service external traffic policy | `Cluster` | +| `sentinel.service.extraPorts` | Extra ports to expose (normally used with the `sidecar` value) | `[]` | +| `sentinel.service.clusterIP` | Redis® Sentinel service Cluster IP | `""` | +| `sentinel.service.loadBalancerIP` | Redis® Sentinel service Load Balancer IP | `""` | +| `sentinel.service.loadBalancerSourceRanges` | Redis® Sentinel service Load Balancer sources | `[]` | +| `sentinel.service.annotations` | Additional custom annotations for Redis® Sentinel service | `{}` | +| `sentinel.service.sessionAffinity` | Session Affinity for Kubernetes service, can be "None" or "ClientIP" | `None` | +| `sentinel.service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `sentinel.terminationGracePeriodSeconds` | Integer setting the termination grace period for the redis-node pods | `30` | + + +### Other Parameters + +| Name | Description | Value | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `networkPolicy.enabled` | Enable creation of NetworkPolicy resources | `false` | +| `networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `networkPolicy.extraEgress` | Add extra egress rules to the NetworkPolicy | `[]` | +| `networkPolicy.ingressNSMatchLabels` | Labels to match to allow traffic from other namespaces | `{}` | +| `networkPolicy.ingressNSPodMatchLabels` | Pod labels to match to allow traffic from other namespaces | `{}` | +| `podSecurityPolicy.create` | Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later | `false` | +| `podSecurityPolicy.enabled` | Enable PodSecurityPolicy's RBAC rules | `false` | +| `rbac.create` | Specifies whether RBAC resources should be created | `false` | +| `rbac.rules` | Custom RBAC rules to set | `[]` | +| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` | +| `serviceAccount.name` | The name of the ServiceAccount to use. | `""` | +| `serviceAccount.automountServiceAccountToken` | Whether to auto mount the service account token | `true` | +| `serviceAccount.annotations` | Additional custom annotations for the ServiceAccount | `{}` | +| `pdb.create` | Specifies whether a PodDisruptionBudget should be created | `false` | +| `pdb.minAvailable` | Min number of pods that must still be available after the eviction | `1` | +| `pdb.maxUnavailable` | Max number of pods that can be unavailable after the eviction | `""` | +| `tls.enabled` | Enable TLS traffic | `false` | +| `tls.authClients` | Require clients to authenticate | `true` | +| `tls.autoGenerated` | Enable autogenerated certificates | `false` | +| `tls.existingSecret` | The name of the existing secret that contains the TLS certificates | `""` | +| `tls.certificatesSecret` | DEPRECATED. Use existingSecret instead. | `""` | +| `tls.certFilename` | Certificate filename | `""` | +| `tls.certKeyFilename` | Certificate Key filename | `""` | +| `tls.certCAFilename` | CA Certificate filename | `""` | +| `tls.dhParamsFilename` | File containing DH params (in order to support DH based ciphers) | `""` | + + +### Metrics Parameters + +| Name | Description | Value | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------ | +| `metrics.enabled` | Start a sidecar prometheus exporter to expose Redis® metrics | `false` | +| `metrics.image.registry` | Redis® Exporter image registry | `docker.io` | +| `metrics.image.repository` | Redis® Exporter image repository | `bitnami/redis-exporter` | +| `metrics.image.tag` | Redis® Redis® Exporter image tag (immutable tags are recommended) | `1.43.0-debian-11-r4` | +| `metrics.image.pullPolicy` | Redis® Exporter image pull policy | `IfNotPresent` | +| `metrics.image.pullSecrets` | Redis® Exporter image pull secrets | `[]` | +| `metrics.command` | Override default metrics container init command (useful when using custom images) | `[]` | +| `metrics.redisTargetHost` | A way to specify an alternative Redis® hostname | `localhost` | +| `metrics.extraArgs` | Extra arguments for Redis® exporter, for example: | `{}` | +| `metrics.extraEnvVars` | Array with extra environment variables to add to Redis® exporter | `[]` | +| `metrics.containerSecurityContext.enabled` | Enabled Redis® exporter containers' Security Context | `true` | +| `metrics.containerSecurityContext.runAsUser` | Set Redis® exporter containers' Security Context runAsUser | `1001` | +| `metrics.extraVolumes` | Optionally specify extra list of additional volumes for the Redis® metrics sidecar | `[]` | +| `metrics.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the Redis® metrics sidecar | `[]` | +| `metrics.resources.limits` | The resources limits for the Redis® exporter container | `{}` | +| `metrics.resources.requests` | The requested resources for the Redis® exporter container | `{}` | +| `metrics.podLabels` | Extra labels for Redis® exporter pods | `{}` | +| `metrics.podAnnotations` | Annotations for Redis® exporter pods | `{}` | +| `metrics.service.type` | Redis® exporter service type | `ClusterIP` | +| `metrics.service.port` | Redis® exporter service port | `9121` | +| `metrics.service.externalTrafficPolicy` | Redis® exporter service external traffic policy | `Cluster` | +| `metrics.service.extraPorts` | Extra ports to expose (normally used with the `sidecar` value) | `[]` | +| `metrics.service.loadBalancerIP` | Redis® exporter service Load Balancer IP | `""` | +| `metrics.service.loadBalancerSourceRanges` | Redis® exporter service Load Balancer sources | `[]` | +| `metrics.service.annotations` | Additional custom annotations for Redis® exporter service | `{}` | +| `metrics.serviceMonitor.enabled` | Create ServiceMonitor resource(s) for scraping metrics using PrometheusOperator | `false` | +| `metrics.serviceMonitor.namespace` | The namespace in which the ServiceMonitor will be created | `""` | +| `metrics.serviceMonitor.interval` | The interval at which metrics should be scraped | `30s` | +| `metrics.serviceMonitor.scrapeTimeout` | The timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.relabellings` | Metrics RelabelConfigs to apply to samples before scraping. | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | Metrics RelabelConfigs to apply to samples before ingestion. | `[]` | +| `metrics.serviceMonitor.honorLabels` | Specify honorLabels parameter to add the scrape endpoint | `false` | +| `metrics.serviceMonitor.additionalLabels` | Additional labels that can be used so ServiceMonitor resource(s) can be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.enabled` | Create a custom prometheusRule Resource for scraping metrics using PrometheusOperator | `false` | +| `metrics.prometheusRule.namespace` | The namespace in which the prometheusRule will be created | `""` | +| `metrics.prometheusRule.additionalLabels` | Additional labels for the prometheusRule | `{}` | +| `metrics.prometheusRule.rules` | Custom Prometheus rules | `[]` | + + +### Init Container Parameters + +| Name | Description | Value | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ----------------------- | +| `volumePermissions.enabled` | Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` | `false` | +| `volumePermissions.image.registry` | Bitnami Shell image registry | `docker.io` | +| `volumePermissions.image.repository` | Bitnami Shell image repository | `bitnami/bitnami-shell` | +| `volumePermissions.image.tag` | Bitnami Shell image tag (immutable tags are recommended) | `11-debian-11-r11` | +| `volumePermissions.image.pullPolicy` | Bitnami Shell image pull policy | `IfNotPresent` | +| `volumePermissions.image.pullSecrets` | Bitnami Shell image pull secrets | `[]` | +| `volumePermissions.resources.limits` | The resources limits for the init container | `{}` | +| `volumePermissions.resources.requests` | The requested resources for the init container | `{}` | +| `volumePermissions.containerSecurityContext.runAsUser` | Set init container's Security Context runAsUser | `0` | +| `sysctl.enabled` | Enable init container to modify Kernel settings | `false` | +| `sysctl.image.registry` | Bitnami Shell image registry | `docker.io` | +| `sysctl.image.repository` | Bitnami Shell image repository | `bitnami/bitnami-shell` | +| `sysctl.image.tag` | Bitnami Shell image tag (immutable tags are recommended) | `11-debian-11-r11` | +| `sysctl.image.pullPolicy` | Bitnami Shell image pull policy | `IfNotPresent` | +| `sysctl.image.pullSecrets` | Bitnami Shell image pull secrets | `[]` | +| `sysctl.command` | Override default init-sysctl container command (useful when using custom images) | `[]` | +| `sysctl.mountHostSys` | Mount the host `/sys` folder to `/host-sys` | `false` | +| `sysctl.resources.limits` | The resources limits for the init container | `{}` | +| `sysctl.resources.requests` | The requested resources for the init container | `{}` | + + +### useExternalDNS Parameters + +| Name | Description | Value | +| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `useExternalDNS.enabled` | Enable various syntax that would enable external-dns to work. Note this requires a working installation of `external-dns` to be usable. | `false` | +| `useExternalDNS.additionalAnnotations` | Extra annotations to be utilized when `external-dns` is enabled. | `{}` | +| `useExternalDNS.annotationKey` | The annotation key utilized when `external-dns` is enabled. | `external-dns.alpha.kubernetes.io/` | +| `useExternalDNS.suffix` | The DNS suffix utilized when `external-dns` is enabled. Note that we prepend the suffix with the full name of the release. | `""` | + + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install my-release \ + --set auth.password=secretpassword \ + bitnami/redis +``` + +The above command sets the Redis® server password to `secretpassword`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +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) + +## 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. + +### Use a different Redis® version + +To modify the application version used in this chart, specify a different version of the image using the `image.tag` parameter and/or a different repository using the `image.repository` parameter. Refer to the [chart documentation for more information on these parameters and how to use them with images from a private registry](https://docs.bitnami.com/kubernetes/infrastructure/redis/configuration/change-image-version/). + +### Bootstrapping with an External Cluster + +This chart is equipped with the ability to bring online a set of Pods that connect to an existing Redis deployment that lies outside of Kubernetes. This effectively creates a hybrid Redis Deployment where both Pods in Kubernetes and Instances such as Virtual Machines can partake in a single Redis Deployment. This is helpful in situations where one may be migrating Redis from Virtual Machines into Kubernetes, for example. To take advantage of this, use the following as an example configuration: + +```yaml +replica: + externalMaster: + enabled: true + host: external-redis-0.internal +sentinel: + externalMaster: + enabled: true + host: external-redis-0.internal +``` + +:warning: This is currently limited to clusters in which Sentinel and Redis run on the same node! :warning: + +Please also note that the external sentinel must be listening on port `26379`, and this is currently not configurable. + +Once the Kubernetes Redis Deployment is online and confirmed to be working with the existing cluster, the configuration can then be removed and the cluster will remain connected. + +### External DNS + +This chart is equipped to allow leveraging the ExternalDNS project. Doing so will enable ExternalDNS to publish the FQDN for each instance, in the format of `..`. +Example, when using the following configuration: + +```yaml +useExternalDNS: + enabled: true + suffix: prod.example.org + additionalAnnotations: + ttl: 10 +``` + +On a cluster where the name of the Helm release is `a`, the hostname of a Pod is generated as: `a-redis-node-0.a-redis.prod.example.org`. The IP of that FQDN will match that of the associated Pod. This modifies the following parameters of the Redis/Sentinel configuration using this new FQDN: + +* `replica-announce-ip` +* `known-sentinel` +* `known-replica` +* `announce-ip` + +:warning: This requires a working installation of `external-dns` to be fully functional. :warning: + +See the [official ExternalDNS documentation](https://github.com/kubernetes-sigs/external-dns) for additional configuration options. + +### Cluster topologies + +#### Default: Master-Replicas + +When installing the chart with `architecture=replication`, it will deploy a Redis® master StatefulSet and a Redis® replicas StatefulSet. The replicas 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® Replicas service: Points to the replicas, where only read operations are allowed by default. + +In case the master crashes, the replicas will wait until the master node is respawned again by the Kubernetes Controller Manager. + +#### Standalone + +When installing the chart with `architecture=standalone`, it will deploy a standalone Redis® StatefulSet. A single service will be exposed: + +- Redis® Master service: Points to the master, where read-write operations can be performed + +#### Master-Replicas with Sentinel + +When installing the chart with `architecture=replication` and `sentinel.enabled=true`, it will deploy a Redis® master StatefulSet (only one master allowed) and a Redis® replicas 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 accessing 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. + +`master.count` greater than `1` is not designed for use when `sentinel.enabled=true`. + +### Multiple masters (experimental) + +When `master.count` is greater than `1`, special care must be taken to create a consistent setup. + +An example of use case is the creation of a redundant set of standalone masters or master-replicas per Kubernetes node where you must ensure: +- No more than `1` master can be deployed per Kubernetes node +- Replicas and writers can only see the single master of their own Kubernetes node + +One way of achieving this is by setting `master.service.internalTrafficPolicy=Local` in combination with a `master.affinity.podAntiAffinity` spec to never schedule more than one master per Kubernetes node. + +It's recommended to only change `master.count` if you know what you are doing. +`master.count` greater than `1` is not designed for use when `sentinel.enabled=true`. + +### Using a password file + +To use a password file for Redis® you need to create a secret containing the password and then deploy the chart using that secret. + +Refer to the chart documentation for more information on [using a password file for Redis®](https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/use-password-file/). + +### 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.existingSecret`: 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. + +Refer to the chart documentation for more information on [creating the secret and a TLS deployment example](https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/enable-tls/). + +### 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. + +If you have enabled TLS by specifying `tls.enabled=true` you also need to specify TLS option to the metrics exporter. You can do that via `metrics.extraArgs`. You can find the metrics exporter CLI flags for TLS [here](https://github.com/oliver006/redis_exporter#command-line-flags). For example: + +You can either specify `metrics.extraArgs.skip-tls-verification=true` to skip TLS verification or providing the following values under `metrics.extraArgs` for TLS client authentication: + +```console +tls-client-key-file +tls-client-cert-file +tls-ca-cert-file +``` + +### 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. + +Refer to the chart documentation for more information on [configuring host kernel settings with an example](https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/configure-kernel-settings/). + +## Persistence + +By default, the chart mounts a [Persistent Volume](https://kubernetes.io/docs/concepts/storage/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 master.persistence.existingClaim=PVC_NAME bitnami/redis +``` + +## Backup and restore + +Refer to the chart documentation for more information on [backing up and restoring Redis® deployments](https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/backup-restore/). + +## 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`. + +Refer to the chart documenation for more information on [enabling the network policy in Redis® deployments](https://docs.bitnami.com/kubernetes/infrastructure/redis/administration/enable-network-policy/). + +### 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 + +A major chart version change (like v1.2.3 -> v2.0.0) indicates that there is an incompatible breaking change needing manual actions. + +### To 16.0.0 + +This major release renames several values in this chart and adds missing features, in order to be inline with the rest of assets in the Bitnami charts repository. + +Affected values: +- `master.service.port` renamed as `master.service.ports.redis`. +- `master.service.nodePort` renamed as `master.service.nodePorts.redis`. +- `replica.service.port` renamed as `replica.service.ports.redis`. +- `replica.service.nodePort` renamed as `replica.service.nodePorts.redis`. +- `sentinel.service.port` renamed as `sentinel.service.ports.redis`. +- `sentinel.service.sentinelPort` renamed as `sentinel.service.ports.sentinel`. +- `master.containerPort` renamed as `master.containerPorts.redis`. +- `replica.containerPort` renamed as `replica.containerPorts.redis`. +- `sentinel.containerPort` renamed as `sentinel.containerPorts.sentinel`. +- `master.spreadConstraints` renamed as `master.topologySpreadConstraints` +- `replica.spreadConstraints` renamed as `replica.topologySpreadConstraints` + +### To 15.0.0 + +The parameter to enable the usage of StaticIDs was removed. The behavior is to [always use StaticIDs](https://github.com/bitnami/charts/pull/7278). + +### To 14.8.0 + +The Redis® sentinel exporter was removed in this version because the upstream project was deprecated. The regular Redis® exporter is included in the sentinel scenario as usual. + +### To 14.0.0 + +- Several parameters were renamed or disappeared in favor of new ones on this major version: + - The term *slave* has been replaced by the term *replica*. Therefore, parameters prefixed with `slave` are now prefixed with `replicas`. + - Credentials parameter are reorganized under the `auth` parameter. + - `cluster.enabled` parameter is deprecated in favor of `architecture` parameter that accepts two values: `standalone` and `replication`. + - `securityContext.*` is deprecated in favor of `XXX.podSecurityContext` and `XXX.containerSecurityContext`. + - `sentinel.metrics.*` parameters are deprecated in favor of `metrics.sentinel.*` ones. +- New parameters to add custom command, environment variables, sidecars, init containers, etc. were added. +- Chart labels were adapted to follow the [Helm charts standard labels](https://helm.sh/docs/chart_best_practices/labels/#standard-labels). +- values.yaml metadata was adapted to follow the format supported by [Readme Generator for Helm](https://github.com/bitnami-labs/readme-generator-for-helm). + +Consequences: + +Backwards compatibility is not guaranteed. To upgrade to `14.0.0`, install a new release of the Redis® chart, and migrate the data from your previous release. You have 2 alternatives to do so: + +- Create a backup of the database, and restore it on the new release as explained in the [Backup and restore](#backup-and-restore) section. +- Reuse the PVC used to hold the master data on your previous release. To do so, use the `master.persistence.existingClaim` parameter. The following example assumes that the release name is `redis`: + +```bash +$ helm install redis bitnami/redis --set auth.password=[PASSWORD] --set master.persistence.existingClaim=[EXISTING_PVC] +``` + +| Note: you need to substitute the placeholder _[EXISTING_PVC]_ with the name of the PVC used on your previous release, and _[PASSWORD]_ with the password used in your previous release. + +### To 13.0.0 + +This major version updates the Redis® docker image version used from `6.0` to `6.2`, the new stable version. There are no major changes in the chart and there shouldn't be any breaking changes in it as `6.2` is basically a stricter superset of `6.0`. For more information, please refer to [Redis® 6.2 release notes](https://raw.githubusercontent.com/redis/redis/6.2/00-RELEASENOTES). + +### To 12.3.0 + +This version also 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. + +### To 12.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. +- 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 11.0.0 + +When deployed with sentinel enabled, only a group of nodes is deployed and the master/slave role is handled in the group. To avoid breaking the compatibility, the settings for this nodes are given through the `slave.xxxx` parameters in `values.yaml` + +### To 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). + +### To 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). + +### To 11.0.0 + +When using sentinel, a new statefulset called `-node` was introduced. This will break upgrading from a previous version where the statefulsets are called master and slave. Hence the PVC will not match the new naming and won't be reused. If you want to keep your data, you will need to perform a backup and then a restore the data in this new version. + +### 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`. + +### To 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"}]' +``` + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/rds/base/charts/redis/charts/common-1.16.0.tgz b/rds/base/charts/redis/charts/common-1.16.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..533e0e429219162594f1a2588423f891ba596656 GIT binary patch literal 14693 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
    }tZ=! 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 literal 0 HcmV?d00001 diff --git a/rds/base/charts/redis/charts/common/.helmignore b/rds/base/charts/redis/charts/common/.helmignore new file mode 100644 index 0000000..50af031 --- /dev/null +++ b/rds/base/charts/redis/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/rds/base/charts/redis/charts/common/Chart.yaml b/rds/base/charts/redis/charts/common/Chart.yaml new file mode 100644 index 0000000..bd152e3 --- /dev/null +++ b/rds/base/charts/redis/charts/common/Chart.yaml @@ -0,0 +1,23 @@ +annotations: + category: Infrastructure +apiVersion: v2 +appVersion: 1.16.0 +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: +- name: Bitnami + url: https://github.com/bitnami/charts +name: common +sources: +- https://github.com/bitnami/charts +- https://www.bitnami.com/ +type: library +version: 1.16.0 diff --git a/rds/base/charts/redis/charts/common/README.md b/rds/base/charts/redis/charts/common/README.md new file mode 100644 index 0000000..3b5e09c --- /dev/null +++ b/rds/base/charts/redis/charts/common/README.md @@ -0,0 +1,350 @@ +# 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: 1.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.19+ +- Helm 3.2.0+ + +## 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.nodes.soft` | Return a soft nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.nodes.hard` | Return a hard nodeAffinity definition | `dict "key" "FOO" "values" (list "BAR" "BAZ")` | +| `common.affinities.pods.soft` | Return a soft podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | +| `common.affinities.pods.hard` | Return a hard podAffinity/podAntiAffinity definition | `dict "component" "FOO" "context" $` | + +### Capabilities + +| Helper identifier | Description | Expected Input | +|------------------------------------------------|------------------------------------------------------------------------------------------------|-------------------| +| `common.capabilities.kubeVersion` | Return the target Kubernetes version (using client default if .Values.kubeVersion is not set). | `.` Chart context | +| `common.capabilities.cronjob.apiVersion` | Return the appropriate apiVersion for cronjob. | `.` Chart context | +| `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 | +| `common.capabilities.rbac.apiVersion` | Return the appropriate apiVersion for RBAC resources. | `.` Chart context | +| `common.capabilities.crd.apiVersion` | Return the appropriate apiVersion for CRDs. | `.` Chart context | +| `common.capabilities.policy.apiVersion` | Return the appropriate apiVersion for podsecuritypolicy. | `.` Chart context | +| `common.capabilities.networkPolicy.apiVersion` | Return the appropriate apiVersion for networkpolicy. | `.` Chart context | +| `common.capabilities.apiService.apiVersion` | Return the appropriate apiVersion for APIService. | `.` Chart context | +| `common.capabilities.hpa.apiVersion` | Return the appropriate apiVersion for Horizontal Pod Autoscaler | `.` Chart context | +| `common.capabilities.supportsHelmVersion` | Returns true if the used Helm version is 3.3+ | `.` 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 (deprecated: use common.images.renderPullSecrets instead) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "global" .Values.global` | +| `common.images.renderPullSecrets` | Return the proper Docker Image Registry Secret Names (evaluates values as templates) | `dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $` | + +### Ingress + +| Helper identifier | Description | Expected Input | +|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `common.ingress.backend` | Generate a proper Ingress backend entry depending on the API version | `dict "serviceName" "foo" "servicePort" "bar"`, see the [Ingress deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/) for the syntax differences | +| `common.ingress.supportsPathType` | Prints "true" if the pathType field is supported | `.` Chart context | +| `common.ingress.supportsIngressClassname` | Prints "true" if the ingressClassname field is supported | `.` Chart context | +| `common.ingress.certManagerRequest` | Prints "true" if required cert-manager annotations for TLS signed certificates are set in the Ingress annotations | `dict "annotations" .Values.path.to.the.ingress.annotations` | + +### Labels + +| Helper identifier | Description | Expected Input | +|-----------------------------|-----------------------------------------------------------------------------|-------------------| +| `common.labels.standard` | Return Kubernetes standard labels | `.` Chart context | +| `common.labels.matchLabels` | Labels to use on `deploy.spec.selector.matchLabels` and `svc.spec.selector` | `.` Chart context | + +### Names + +| Helper identifier | Description | Expected Input | +|-----------------------------------|-----------------------------------------------------------------------|-------------------| +| `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.namespace` | Allow the release namespace to be overridden | `.` Chart context | +| `common.names.fullname.namespace` | Create a fully qualified app name adding the installation's namespace | `.` 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. | +| `common.passwords.manage` | Generate secret password or retrieve one if already created. | `dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $`, length, strong and chartNAme fields are optional. | +| `common.secrets.exists` | Returns whether a previous generated secret already exists. | `dict "secret" "secret-name" "context" $` | + +### Storage + +| Helper identifier | Description | Expected Input | +|-------------------------------|---------------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `common.storage.class` | Return the proper Storage Class | `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" $` | +| `common.utils.getKeyFromList` | Returns first `.Values` key with a defined value or first of the list if all non-defined | `dict "keys" (list "path.to.key1" "path.to.key2") "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" "subchart" "subchart" "context" $` secret, field and subchart 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.mysql.passwords` | This helper will ensure required password for MySQL are not empty. It returns a shared error for all the values. | `dict "secret" "mysql-secret" "subchart" "true" "context" $` subchart field is optional and could be true or false it depends on where you will use mysql 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 (evaluated as templates). + +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 -d) + + '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 -d) +``` + +## 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/ + +## License + +Copyright © 2022 Bitnami + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rds/base/charts/redis/charts/common/templates/_affinities.tpl b/rds/base/charts/redis/charts/common/templates/_affinities.tpl new file mode 100644 index 0000000..189ea40 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_affinities.tpl @@ -0,0 +1,102 @@ +{{/* 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 }} + - {{ . | quote }} + {{- 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 }} + - {{ . | quote }} + {{- 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" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.soft" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 10 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + topologyKey: kubernetes.io/hostname + weight: 1 +{{- end -}} + +{{/* +Return a hard podAffinity/podAntiAffinity definition +{{ include "common.affinities.pods.hard" (dict "component" "FOO" "extraMatchLabels" .Values.extraMatchLabels "context" $) -}} +*/}} +{{- define "common.affinities.pods.hard" -}} +{{- $component := default "" .component -}} +{{- $extraMatchLabels := default (dict) .extraMatchLabels -}} +requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: {{- (include "common.labels.matchLabels" .context) | nindent 8 }} + {{- if not (empty $component) }} + {{ printf "app.kubernetes.io/component: %s" $component }} + {{- end }} + {{- range $key, $value := $extraMatchLabels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + namespaces: + - {{ .context.Release.Namespace | quote }} + 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/rds/base/charts/redis/charts/common/templates/_capabilities.tpl b/rds/base/charts/redis/charts/common/templates/_capabilities.tpl new file mode 100644 index 0000000..9d9b760 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_capabilities.tpl @@ -0,0 +1,154 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the target Kubernetes version +*/}} +{{- define "common.capabilities.kubeVersion" -}} +{{- if .Values.global }} + {{- if .Values.global.kubeVersion }} + {{- .Values.global.kubeVersion -}} + {{- else }} + {{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} + {{- end -}} +{{- else }} +{{- default .Capabilities.KubeVersion.Version .Values.kubeVersion -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for poddisruptionbudget. +*/}} +{{- define "common.capabilities.policy.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "common.capabilities.networkPolicy.apiVersion" -}} +{{- if semverCompare "<1.7-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for cronjob. +*/}} +{{- define "common.capabilities.cronjob.apiVersion" -}} +{{- if semverCompare "<1.21-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "batch/v1beta1" -}} +{{- else -}} +{{- print "batch/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "common.capabilities.deployment.apiVersion" -}} +{{- if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- 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" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apps/v1beta1" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "common.capabilities.ingress.apiVersion" -}} +{{- if .Values.ingress -}} +{{- if .Values.ingress.apiVersion -}} +{{- .Values.ingress.apiVersion -}} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end }} +{{- else if semverCompare "<1.14-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "extensions/v1beta1" -}} +{{- else if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "networking.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "networking.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for RBAC resources. +*/}} +{{- define "common.capabilities.rbac.apiVersion" -}} +{{- if semverCompare "<1.17-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "rbac.authorization.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "rbac.authorization.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for CRDs. +*/}} +{{- define "common.capabilities.crd.apiVersion" -}} +{{- if semverCompare "<1.19-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiextensions.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiextensions.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for APIService. +*/}} +{{- define "common.capabilities.apiService.apiVersion" -}} +{{- if semverCompare "<1.10-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "apiregistration.k8s.io/v1beta1" -}} +{{- else -}} +{{- print "apiregistration.k8s.io/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for Horizontal Pod Autoscaler. +*/}} +{{- define "common.capabilities.hpa.apiVersion" -}} +{{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .context) -}} +{{- if .beta2 -}} +{{- print "autoscaling/v2beta2" -}} +{{- else -}} +{{- print "autoscaling/v2beta1" -}} +{{- end -}} +{{- else -}} +{{- print "autoscaling/v2" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the used Helm version is 3.3+. +A way to check the used Helm version was not introduced until version 3.3.0 with .Capabilities.HelmVersion, which contains an additional "{}}" structure. +This check is introduced as a regexMatch instead of {{ if .Capabilities.HelmVersion }} because checking for the key HelmVersion in <3.3 results in a "interface not found" error. +**To be removed when the catalog's minimun Helm version is 3.3** +*/}} +{{- define "common.capabilities.supportsHelmVersion" -}} +{{- if regexMatch "{(v[0-9])*[^}]*}}$" (.Capabilities | toString ) }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_errors.tpl b/rds/base/charts/redis/charts/common/templates/_errors.tpl new file mode 100644 index 0000000..a79cc2e --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_errors.tpl @@ -0,0 +1,23 @@ +{{/* 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 upgrading the release." -}} + {{- $errorString = print $errorString "\n Note that even after reinstallation, old credentials may be needed as they may be kept in persistent volume claims." -}} + {{- $errorString = print $errorString "\n Further information can be obtained at https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases" -}} + {{- $errorString = print $errorString "\n%s" -}} + {{- printf $errorString $validationErrors | fail -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_images.tpl b/rds/base/charts/redis/charts/common/templates/_images.tpl new file mode 100644 index 0000000..42ffbc7 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_images.tpl @@ -0,0 +1,75 @@ +{{/* 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 -}} +{{- if $registryName }} +{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} +{{- else -}} +{{- printf "%s:%s" $repositoryName $tag -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names (deprecated: use common.images.renderPullSecrets instead) +{{ 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 -}} + +{{/* +Return the proper Docker Image Registry Secret Names evaluating values as templates +{{ include "common.images.renderPullSecrets" ( dict "images" (list .Values.path.to.the.image1, .Values.path.to.the.image2) "context" $) }} +*/}} +{{- define "common.images.renderPullSecrets" -}} + {{- $pullSecrets := list }} + {{- $context := .context }} + + {{- if $context.Values.global }} + {{- range $context.Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- range .images -}} + {{- range .pullSecrets -}} + {{- $pullSecrets = append $pullSecrets (include "common.tplvalues.render" (dict "value" . "context" $context)) -}} + {{- end -}} + {{- end -}} + + {{- if (not (empty $pullSecrets)) }} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_ingress.tpl b/rds/base/charts/redis/charts/common/templates/_ingress.tpl new file mode 100644 index 0000000..8caf73a --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_ingress.tpl @@ -0,0 +1,68 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Generate backend entry that is compatible with all Kubernetes API versions. + +Usage: +{{ include "common.ingress.backend" (dict "serviceName" "backendName" "servicePort" "backendPort" "context" $) }} + +Params: + - serviceName - String. Name of an existing service backend + - servicePort - String/Int. Port name (or number) of the service. It will be translated to different yaml depending if it is a string or an integer. + - context - Dict - Required. The context for the template evaluation. +*/}} +{{- define "common.ingress.backend" -}} +{{- $apiVersion := (include "common.capabilities.ingress.apiVersion" .context) -}} +{{- if or (eq $apiVersion "extensions/v1beta1") (eq $apiVersion "networking.k8s.io/v1beta1") -}} +serviceName: {{ .serviceName }} +servicePort: {{ .servicePort }} +{{- else -}} +service: + name: {{ .serviceName }} + port: + {{- if typeIs "string" .servicePort }} + name: {{ .servicePort }} + {{- else if or (typeIs "int" .servicePort) (typeIs "float64" .servicePort) }} + number: {{ .servicePort | int }} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Print "true" if the API pathType field is supported +Usage: +{{ include "common.ingress.supportsPathType" . }} +*/}} +{{- define "common.ingress.supportsPathType" -}} +{{- if (semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Returns true if the ingressClassname field is supported +Usage: +{{ include "common.ingress.supportsIngressClassname" . }} +*/}} +{{- define "common.ingress.supportsIngressClassname" -}} +{{- if semverCompare "<1.18-0" (include "common.capabilities.kubeVersion" .) -}} +{{- print "false" -}} +{{- else -}} +{{- print "true" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if cert-manager required annotations for TLS signed +certificates are set in the Ingress annotations +Ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations +Usage: +{{ include "common.ingress.certManagerRequest" ( dict "annotations" .Values.path.to.the.ingress.annotations ) }} +*/}} +{{- define "common.ingress.certManagerRequest" -}} +{{ if or (hasKey .annotations "cert-manager.io/cluster-issuer") (hasKey .annotations "cert-manager.io/issuer") }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_labels.tpl b/rds/base/charts/redis/charts/common/templates/_labels.tpl new file mode 100644 index 0000000..252066c --- /dev/null +++ b/rds/base/charts/redis/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/rds/base/charts/redis/charts/common/templates/_names.tpl b/rds/base/charts/redis/charts/common/templates/_names.tpl new file mode 100644 index 0000000..1bdac8b --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_names.tpl @@ -0,0 +1,70 @@ +{{/* 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 -}} + +{{/* +Create a default fully qualified dependency 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. +Usage: +{{ include "common.names.dependency.fullname" (dict "chartName" "dependency-chart-name" "chartValues" .Values.dependency-chart "context" $) }} +*/}} +{{- define "common.names.dependency.fullname" -}} +{{- if .chartValues.fullnameOverride -}} +{{- .chartValues.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .chartName .chartValues.nameOverride -}} +{{- if contains $name .context.Release.Name -}} +{{- .context.Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .context.Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts. +*/}} +{{- define "common.names.namespace" -}} +{{- if .Values.namespaceOverride -}} +{{- .Values.namespaceOverride -}} +{{- else -}} +{{- .Release.Namespace -}} +{{- end -}} +{{- end -}} + +{{/* +Create a fully qualified app name adding the installation's namespace. +*/}} +{{- define "common.names.fullname.namespace" -}} +{{- printf "%s-%s" (include "common.names.fullname" .) (include "common.names.namespace" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_secrets.tpl b/rds/base/charts/redis/charts/common/templates/_secrets.tpl new file mode 100644 index 0000000..a53fb44 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_secrets.tpl @@ -0,0 +1,140 @@ +{{/* 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" .) -}} +{{- with .name -}} +{{- $name = . -}} +{{- end -}} +{{- 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 -}} + +{{/* +Generate secret password or retrieve one if already created. + +Usage: +{{ include "common.secrets.passwords.manage" (dict "secret" "secret-name" "key" "keyName" "providedValues" (list "path.to.password1" "path.to.password2") "length" 10 "strong" false "chartName" "chartName" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - key - String - Required - Name of the key in the secret. + - providedValues - List - Required - The path to the validating value in the values.yaml, e.g: "mysql.password". Will pick first parameter with a defined value. + - length - int - Optional - Length of the generated random password. + - strong - Boolean - Optional - Whether to add symbols to the generated random password. + - chartName - String - Optional - Name of the chart used when said chart is deployed as a subchart. + - context - Context - Required - Parent context. + +The order in which this function returns a secret password: + 1. Already existing 'Secret' resource + (If a 'Secret' resource is found under the name provided to the 'secret' parameter to this function and that 'Secret' resource contains a key with the name passed as the 'key' parameter to this function then the value of this existing secret password will be returned) + 2. Password provided via the values.yaml + (If one of the keys passed to the 'providedValues' parameter to this function is a valid path to a key in the values.yaml and has a value, the value of the first key with a value will be returned) + 3. Randomly generated secret password + (A new random secret password with the length specified in the 'length' parameter will be generated and returned) + +*/}} +{{- define "common.secrets.passwords.manage" -}} + +{{- $password := "" }} +{{- $subchart := "" }} +{{- $chartName := default "" .chartName }} +{{- $passwordLength := default 10 .length }} +{{- $providedPasswordKey := include "common.utils.getKeyFromList" (dict "keys" .providedValues "context" $.context) }} +{{- $providedPasswordValue := include "common.utils.getValueFromKey" (dict "key" $providedPasswordKey "context" $.context) }} +{{- $secretData := (lookup "v1" "Secret" $.context.Release.Namespace .secret).data }} +{{- if $secretData }} + {{- if hasKey $secretData .key }} + {{- $password = index $secretData .key }} + {{- else }} + {{- printf "\nPASSWORDS ERROR: The secret \"%s\" does not contain the key \"%s\"\n" .secret .key | fail -}} + {{- end -}} +{{- else if $providedPasswordValue }} + {{- $password = $providedPasswordValue | toString | b64enc | quote }} +{{- else }} + + {{- if .context.Values.enabled }} + {{- $subchart = $chartName }} + {{- end -}} + + {{- $requiredPassword := dict "valueKey" $providedPasswordKey "secret" .secret "field" .key "subchart" $subchart "context" $.context -}} + {{- $requiredPasswordError := include "common.validations.values.single.empty" $requiredPassword -}} + {{- $passwordValidationErrors := list $requiredPasswordError -}} + {{- include "common.errors.upgrade.passwords.empty" (dict "validationErrors" $passwordValidationErrors "context" $.context) -}} + + {{- if .strong }} + {{- $subStr := list (lower (randAlpha 1)) (randNumeric 1) (upper (randAlpha 1)) | join "_" }} + {{- $password = randAscii $passwordLength }} + {{- $password = regexReplaceAllLiteral "\\W" $password "@" | substr 5 $passwordLength }} + {{- $password = printf "%s%s" $subStr $password | toString | shuffle | b64enc | quote }} + {{- else }} + {{- $password = randAlphaNum $passwordLength | b64enc | quote }} + {{- end }} +{{- end -}} +{{- printf "%s" $password -}} +{{- end -}} + +{{/* +Returns whether a previous generated secret already exists + +Usage: +{{ include "common.secrets.exists" (dict "secret" "secret-name" "context" $) }} + +Params: + - secret - String - Required - Name of the 'Secret' resource where the password is stored. + - context - Context - Required - Parent context. +*/}} +{{- define "common.secrets.exists" -}} +{{- $secret := (lookup "v1" "Secret" $.context.Release.Namespace .secret) }} +{{- if $secret }} + {{- true -}} +{{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_storage.tpl b/rds/base/charts/redis/charts/common/templates/_storage.tpl new file mode 100644 index 0000000..60e2a84 --- /dev/null +++ b/rds/base/charts/redis/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/rds/base/charts/redis/charts/common/templates/_tplvalues.tpl b/rds/base/charts/redis/charts/common/templates/_tplvalues.tpl new file mode 100644 index 0000000..2db1668 --- /dev/null +++ b/rds/base/charts/redis/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/rds/base/charts/redis/charts/common/templates/_utils.tpl b/rds/base/charts/redis/charts/common/templates/_utils.tpl new file mode 100644 index 0000000..8c22b2a --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/_utils.tpl @@ -0,0 +1,62 @@ +{{/* 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 | quote }} {{ .secret }} -o jsonpath="{.data.{{ .field }}}" | base64 -d) +{{- 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 -}} + +{{/* +Returns first .Values key with a defined value or first of the list if all non-defined +Usage: +{{ include "common.utils.getKeyFromList" (dict "keys" (list "path.to.key1" "path.to.key2") "context" $) }} +*/}} +{{- define "common.utils.getKeyFromList" -}} +{{- $key := first .keys -}} +{{- $reverseKeys := reverse .keys }} +{{- range $reverseKeys }} + {{- $value := include "common.utils.getValueFromKey" (dict "key" . "context" $.context ) }} + {{- if $value -}} + {{- $key = . }} + {{- end -}} +{{- end -}} +{{- printf "%s" $key -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/_warnings.tpl b/rds/base/charts/redis/charts/common/templates/_warnings.tpl new file mode 100644 index 0000000..ae10fa4 --- /dev/null +++ b/rds/base/charts/redis/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/rds/base/charts/redis/charts/common/templates/validations/_cassandra.tpl b/rds/base/charts/redis/charts/common/templates/validations/_cassandra.tpl new file mode 100644 index 0000000..ded1ae3 --- /dev/null +++ b/rds/base/charts/redis/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 (or (not $existingSecret) (eq $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/rds/base/charts/redis/charts/common/templates/validations/_mariadb.tpl b/rds/base/charts/redis/charts/common/templates/validations/_mariadb.tpl new file mode 100644 index 0000000..b6906ff --- /dev/null +++ b/rds/base/charts/redis/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 (or (not $existingSecret) (eq $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/rds/base/charts/redis/charts/common/templates/validations/_mongodb.tpl b/rds/base/charts/redis/charts/common/templates/validations/_mongodb.tpl new file mode 100644 index 0000000..f820ec1 --- /dev/null +++ b/rds/base/charts/redis/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 (or (not $existingSecret) (eq $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 MongoDB® 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/rds/base/charts/redis/charts/common/templates/validations/_mysql.tpl b/rds/base/charts/redis/charts/common/templates/validations/_mysql.tpl new file mode 100644 index 0000000..74472a0 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/validations/_mysql.tpl @@ -0,0 +1,103 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Validate MySQL required passwords are not empty. + +Usage: +{{ include "common.validations.values.mysql.passwords" (dict "secret" "secretName" "subchart" false "context" $) }} +Params: + - secret - String - Required. Name of the secret where MySQL values are stored, e.g: "mysql-passwords-secret" + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.validations.values.mysql.passwords" -}} + {{- $existingSecret := include "common.mysql.values.auth.existingSecret" . -}} + {{- $enabled := include "common.mysql.values.enabled" . -}} + {{- $architecture := include "common.mysql.values.architecture" . -}} + {{- $authPrefix := include "common.mysql.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 (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $requiredRootPassword := dict "valueKey" $valueKeyRootPassword "secret" .secret "field" "mysql-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" "mysql-password" -}} + {{- $requiredPasswords = append $requiredPasswords $requiredPassword -}} + {{- end -}} + + {{- if (eq $architecture "replication") -}} + {{- $requiredReplicationPassword := dict "valueKey" $valueKeyReplicationPassword "secret" .secret "field" "mysql-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.mysql.values.auth.existingSecret" (dict "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.auth.existingSecret" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.auth.existingSecret | quote -}} + {{- else -}} + {{- .context.Values.auth.existingSecret | quote -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for enabled mysql. + +Usage: +{{ include "common.mysql.values.enabled" (dict "context" $) }} +*/}} +{{- define "common.mysql.values.enabled" -}} + {{- if .subchart -}} + {{- printf "%v" .context.Values.mysql.enabled -}} + {{- else -}} + {{- printf "%v" (not .context.Values.enabled) -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for architecture + +Usage: +{{ include "common.mysql.values.architecture" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.architecture" -}} + {{- if .subchart -}} + {{- .context.Values.mysql.architecture -}} + {{- else -}} + {{- .context.Values.architecture -}} + {{- end -}} +{{- end -}} + +{{/* +Auxiliary function to get the right value for the key auth + +Usage: +{{ include "common.mysql.values.key.auth" (dict "subchart" "true" "context" $) }} +Params: + - subchart - Boolean - Optional. Whether MySQL is used as subchart or not. Default: false +*/}} +{{- define "common.mysql.values.key.auth" -}} + {{- if .subchart -}} + mysql.auth + {{- else -}} + auth + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/validations/_postgresql.tpl b/rds/base/charts/redis/charts/common/templates/validations/_postgresql.tpl new file mode 100644 index 0000000..164ec0d --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/validations/_postgresql.tpl @@ -0,0 +1,129 @@ +{{/* 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 (or (not $existingSecret) (eq $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/rds/base/charts/redis/charts/common/templates/validations/_redis.tpl b/rds/base/charts/redis/charts/common/templates/validations/_redis.tpl new file mode 100644 index 0000000..dcccfc1 --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/validations/_redis.tpl @@ -0,0 +1,76 @@ + +{{/* 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" -}} + {{- $enabled := include "common.redis.values.enabled" . -}} + {{- $valueKeyPrefix := include "common.redis.values.keys.prefix" . -}} + {{- $standarizedVersion := include "common.redis.values.standarized.version" . }} + + {{- $existingSecret := ternary (printf "%s%s" $valueKeyPrefix "auth.existingSecret") (printf "%s%s" $valueKeyPrefix "existingSecret") (eq $standarizedVersion "true") }} + {{- $existingSecretValue := include "common.utils.getValueFromKey" (dict "key" $existingSecret "context" .context) }} + + {{- $valueKeyRedisPassword := ternary (printf "%s%s" $valueKeyPrefix "auth.password") (printf "%s%s" $valueKeyPrefix "password") (eq $standarizedVersion "true") }} + {{- $valueKeyRedisUseAuth := ternary (printf "%s%s" $valueKeyPrefix "auth.enabled") (printf "%s%s" $valueKeyPrefix "usePassword") (eq $standarizedVersion "true") }} + + {{- if and (or (not $existingSecret) (eq $existingSecret "\"\"")) (eq $enabled "true") -}} + {{- $requiredPasswords := list -}} + + {{- $useAuth := include "common.utils.getValueFromKey" (dict "key" $valueKeyRedisUseAuth "context" .context) -}} + {{- if eq $useAuth "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 -}} + +{{/* +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 -}} + +{{/* +Checks whether the redis chart's includes the standarizations (version >= 14) + +Usage: +{{ include "common.redis.values.standarized.version" (dict "context" $) }} +*/}} +{{- define "common.redis.values.standarized.version" -}} + + {{- $standarizedAuth := printf "%s%s" (include "common.redis.values.keys.prefix" .) "auth" -}} + {{- $standarizedAuthValues := include "common.utils.getValueFromKey" (dict "key" $standarizedAuth "context" .context) }} + + {{- if $standarizedAuthValues -}} + {{- true -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/templates/validations/_validations.tpl b/rds/base/charts/redis/charts/common/templates/validations/_validations.tpl new file mode 100644 index 0000000..9a814cf --- /dev/null +++ b/rds/base/charts/redis/charts/common/templates/validations/_validations.tpl @@ -0,0 +1,46 @@ +{{/* 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" "subchart" "subchart" "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" + - subchart - String - Optional - Name of the subchart that the validated password is part of. +*/}} +{{- define "common.validations.values.single.empty" -}} + {{- $value := include "common.utils.getValueFromKey" (dict "key" .valueKey "context" .context) }} + {{- $subchart := ternary "" (printf "%s." .subchart) (empty .subchart) }} + + {{- 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=$%s' to the command.%s" .valueKey $subchart .valueKey $varname $getCurrentValue -}} + {{- end -}} +{{- end -}} diff --git a/rds/base/charts/redis/charts/common/values.yaml b/rds/base/charts/redis/charts/common/values.yaml new file mode 100644 index 0000000..f2df68e --- /dev/null +++ b/rds/base/charts/redis/charts/common/values.yaml @@ -0,0 +1,5 @@ +## bitnami/common +## It is required by CI/CD tools and processes. +## @skip exampleValue +## +exampleValue: common-chart diff --git a/rds/base/charts/redis/img/redis-cluster-topology.png b/rds/base/charts/redis/img/redis-cluster-topology.png new file mode 100644 index 0000000000000000000000000000000000000000..f0a02a9f8835381302731c9cb000b2835a45e7c9 GIT binary patch literal 11448 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 -- bash + +In order to replicate the container startup scripts execute this command: + +For Redis: + + /opt/bitnami/scripts/redis/entrypoint.sh /opt/bitnami/scripts/redis/run.sh + +{{- if .Values.sentinel.enabled }} + +For Redis Sentinel: + + /opt/bitnami/scripts/redis-sentinel/entrypoint.sh /opt/bitnami/scripts/redis-sentinel/run.sh + +{{- end }} +{{- else }} + +{{- if contains .Values.master.service.type "LoadBalancer" }} +{{- if not .Values.auth.enabled }} +{{ if and (not .Values.networkPolicy.enabled) (.Values.networkPolicy.allowExternal) }} + +------------------------------------------------------------------------------- + WARNING + + By specifying "master.service.type=LoadBalancer" and "auth.enabled=false" you have + most likely exposed the Redis® service externally without any authentication + mechanism. + + For security reasons, we strongly suggest that you switch to "ClusterIP" or + "NodePort". As alternative, you can also switch to "auth.enabled=true" + providing a valid password on "password" parameter. + +------------------------------------------------------------------------------- +{{- end }} +{{- end }} +{{- end }} + +{{- if eq .Values.architecture "replication" }} +{{- if .Values.sentinel.enabled }} + +Redis® can be accessed via port {{ .Values.sentinel.service.ports.redis }} on the following DNS name from within your cluster: + + {{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} for read only operations + +For read/write operations, first access the Redis® Sentinel cluster, which is available in port {{ .Values.sentinel.service.ports.sentinel }} using the same domain name above. + +{{- else }} + +Redis® can be accessed on the following DNS names from within your cluster: + + {{ printf "%s-master.%s.svc.%s" (include "common.names.fullname" .) .Release.Namespace .Values.clusterDomain }} for read/write operations (port {{ .Values.master.service.ports.redis }}) + {{ printf "%s-replicas.%s.svc.%s" (include "common.names.fullname" .) .Release.Namespace .Values.clusterDomain }} for read-only operations (port {{ .Values.replica.service.ports.redis }}) + +{{- end }} +{{- else }} + +Redis® can be accessed via port {{ .Values.master.service.ports.redis }} on the following DNS name from within your cluster: + + {{ template "common.names.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + +{{- end }} + +{{ if .Values.auth.enabled }} + +To get your password run: + + export REDIS_PASSWORD=$(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "redis.secretName" . }} -o jsonpath="{.data.redis-password}" | base64 -d) + +{{- end }} + +To connect to your Redis® server: + +1. Run a Redis® pod that you can use as a client: + + kubectl run --namespace {{ .Release.Namespace }} redis-client --restart='Never' {{ if .Values.auth.enabled }} --env REDIS_PASSWORD=$REDIS_PASSWORD {{ end }} --image {{ template "redis.image" . }} --command -- sleep infinity + +{{- if .Values.tls.enabled }} + + Copy your TLS certificates to the pod: + + kubectl cp --namespace {{ .Release.Namespace }} /path/to/client.cert redis-client:/tmp/client.cert + kubectl cp --namespace {{ .Release.Namespace }} /path/to/client.key redis-client:/tmp/client.key + kubectl cp --namespace {{ .Release.Namespace }} /path/to/CA.cert redis-client:/tmp/CA.cert + +{{- end }} + + Use the following command to attach to the pod: + + kubectl exec --tty -i redis-client \ + {{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }}--labels="{{ template "common.names.fullname" . }}-client=true" \{{- end }} + --namespace {{ .Release.Namespace }} -- bash + +2. Connect using the Redis® CLI: + +{{- if eq .Values.architecture "replication" }} + {{- if .Values.sentinel.enabled }} + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h {{ template "common.names.fullname" . }} -p {{ .Values.sentinel.service.ports.redis }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} # Read only operations + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h {{ template "common.names.fullname" . }} -p {{ .Values.sentinel.service.ports.sentinel }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} # Sentinel access + {{- else }} + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h {{ printf "%s-master" (include "common.names.fullname" .) }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h {{ printf "%s-replicas" (include "common.names.fullname" .) }}{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + {{- end }} +{{- else }} + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h {{ template "common.names.fullname" . }}-master{{ if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} +{{- end }} + +{{- if and (.Values.networkPolicy.enabled) (not .Values.networkPolicy.allowExternal) }} + +Note: Since NetworkPolicy is enabled, only pods with label {{ template "common.names.fullname" . }}-client=true" will be able to connect to redis. + +{{- else }} + +To connect to your database from outside the cluster execute the following commands: + +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled }} +{{- if contains "NodePort" .Values.sentinel.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "common.names.fullname" . }}) + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h $NODE_IP -p $NODE_PORT {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- else if contains "LoadBalancer" .Values.sentinel.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "common.names.fullname" . }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h $SERVICE_IP -p {{ .Values.sentinel.service.ports.redis }} {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- else if contains "ClusterIP" .Values.sentinel.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "common.names.fullname" . }} {{ .Values.sentinel.service.ports.redis }}:{{ .Values.sentinel.service.ports.redis }} & + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h 127.0.0.1 -p {{ .Values.sentinel.service.ports.redis }} {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- end }} +{{- else }} +{{- if contains "NodePort" .Values.master.service.type }} + + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ printf "%s-master" (include "common.names.fullname" .) }}) + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h $NODE_IP -p $NODE_PORT {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- else if contains "LoadBalancer" .Values.master.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "common.names.fullname" . }}' + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ printf "%s-master" (include "common.names.fullname" .) }} --template "{{ "{{ range (index .status.loadBalancer.ingress 0) }}{{ . }}{{ end }}" }}") + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h $SERVICE_IP -p {{ .Values.master.service.ports.redis }} {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- else if contains "ClusterIP" .Values.master.service.type }} + + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ printf "%s-master" (include "common.names.fullname" .) }} {{ .Values.master.service.ports.redis }}:{{ .Values.master.service.ports.redis }} & + {{ if .Values.auth.enabled }}REDISCLI_AUTH="$REDIS_PASSWORD" {{ end }}redis-cli -h 127.0.0.1 -p {{ .Values.master.service.ports.redis }} {{- if .Values.tls.enabled }} --tls --cert /tmp/client.cert --key /tmp/client.key --cacert /tmp/CA.cert{{ end }} + +{{- end }} +{{- end }} + +{{- end }} +{{- end }} +{{- include "redis.checkRollingTags" . }} +{{- include "common.warnings.rollingTag" .Values.volumePermissions.image }} +{{- include "common.warnings.rollingTag" .Values.sysctl.image }} +{{- include "redis.validateValues" . }} + +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled (eq .Values.sentinel.service.type "NodePort") (not .Release.IsUpgrade ) }} +{{- if $.Values.sentinel.service.nodePorts.sentinel }} +No need to upgrade, ports and nodeports have been set from values +{{- else }} +#!#!#!#!#!#!#!# IMPORTANT #!#!#!#!#!#!#!# +YOU NEED TO PERFORM AN UPGRADE FOR THE SERVICES AND WORKLOAD TO BE CREATED +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/_helpers.tpl b/rds/base/charts/redis/templates/_helpers.tpl new file mode 100644 index 0000000..f6f47d9 --- /dev/null +++ b/rds/base/charts/redis/templates/_helpers.tpl @@ -0,0 +1,291 @@ +{{/* vim: set filetype=mustache: */}} + +{{/* +Return the proper Redis image name +*/}} +{{- define "redis.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Redis Sentinel image name +*/}} +{{- define "redis.sentinel.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.sentinel.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper image name (for the metrics image) +*/}} +{{- define "redis.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 "redis.volumePermissions.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.volumePermissions.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return sysctl image +*/}} +{{- define "redis.sysctl.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.sysctl.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "redis.imagePullSecrets" -}} +{{- include "common.images.pullSecrets" (dict "images" (list .Values.image .Values.sentinel.image .Values.metrics.image .Values.volumePermissions.image .Values.sysctl.image) "global" .Values.global) -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for networkpolicy. +*/}} +{{- define "networkPolicy.apiVersion" -}} +{{- if semverCompare ">=1.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 true if a TLS secret object should be created +*/}} +{{- define "redis.createTlsSecret" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated (and (not .Values.tls.existingSecret) (not .Values.tls.certificatesSecret)) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing Redis TLS certificates +*/}} +{{- define "redis.tlsSecretName" -}} +{{- $secretName := coalesce .Values.tls.existingSecret .Values.tls.certificatesSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-crt" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert file. +*/}} +{{- define "redis.tlsCert" -}} +{{- if (include "redis.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "tls.crt" -}} +{{- else -}} + {{- required "Certificate filename is required when TLS in enabled" .Values.tls.certFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the cert key file. +*/}} +{{- define "redis.tlsCertKey" -}} +{{- if (include "redis.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "tls.key" -}} +{{- else -}} + {{- required "Certificate Key filename is required when TLS in enabled" .Values.tls.certKeyFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the path to the CA cert file. +*/}} +{{- define "redis.tlsCACert" -}} +{{- if (include "redis.createTlsSecret" . ) -}} + {{- printf "/opt/bitnami/redis/certs/%s" "ca.crt" -}} +{{- else -}} + {{- required "Certificate CA filename is required when TLS in enabled" .Values.tls.certCAFilename | printf "/opt/bitnami/redis/certs/%s" -}} +{{- end -}} +{{- 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 "common.names.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Return the configuration configmap name +*/}} +{{- define "redis.configmapName" -}} +{{- if .Values.existingConfigmap -}} + {{- printf "%s" (tpl .Values.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created +*/}} +{{- define "redis.createConfigmap" -}} +{{- if empty .Values.existingConfigmap }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password secret. +*/}} +{{- define "redis.secretName" -}} +{{- if .Values.auth.existingSecret -}} +{{- printf "%s" .Values.auth.existingSecret -}} +{{- else -}} +{{- printf "%s" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Get the password key to be retrieved from Redis® secret. +*/}} +{{- define "redis.secretPasswordKey" -}} +{{- if and .Values.auth.existingSecret .Values.auth.existingSecretPasswordKey -}} +{{- printf "%s" .Values.auth.existingSecretPasswordKey -}} +{{- else -}} +{{- printf "redis-password" -}} +{{- end -}} +{{- end -}} + + +{{/* +Returns the available value for certain key in an existing secret (if it exists), +otherwise it generates a random value. +*/}} +{{- define "getValueFromSecret" }} + {{- $len := (default 16 .Length) | int -}} + {{- $obj := (lookup "v1" "Secret" .Namespace .Name).data -}} + {{- if $obj }} + {{- index $obj .Key | b64dec -}} + {{- else -}} + {{- randAlphaNum $len -}} + {{- end -}} +{{- end }} + +{{/* +Return Redis® password +*/}} +{{- define "redis.password" -}} +{{- if not (empty .Values.global.redis.password) }} + {{- .Values.global.redis.password -}} +{{- else if not (empty .Values.auth.password) -}} + {{- .Values.auth.password -}} +{{- else -}} + {{- include "getValueFromSecret" (dict "Namespace" .Release.Namespace "Name" (include "common.names.fullname" .) "Length" 10 "Key" "redis-password") -}} +{{- end -}} +{{- end -}} + +{{/* Check if there are rolling tags in the images */}} +{{- define "redis.checkRollingTags" -}} +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.sentinel.image }} +{{- include "common.warnings.rollingTag" .Values.metrics.image }} +{{- end -}} + +{{/* +Compile all warnings into a single message, and call fail. +*/}} +{{- define "redis.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "redis.validateValues.topologySpreadConstraints" .) -}} +{{- $messages := append $messages (include "redis.validateValues.architecture" .) -}} +{{- $messages := append $messages (include "redis.validateValues.podSecurityPolicy.create" .) -}} +{{- $messages := append $messages (include "redis.validateValues.tls" .) -}} +{{- $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.topologySpreadConstraints" -}} +{{- if and (semverCompare "<1.16-0" .Capabilities.KubeVersion.GitVersion) .Values.replica.topologySpreadConstraints -}} +redis: topologySpreadConstraints + 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 -}} + +{{/* Validate values of Redis® - must provide a valid architecture */}} +{{- define "redis.validateValues.architecture" -}} +{{- if and (ne .Values.architecture "standalone") (ne .Values.architecture "replication") -}} +redis: architecture + Invalid architecture selected. Valid values are "standalone" and + "replication". Please set a valid architecture (--set architecture="xxxx") +{{- end -}} +{{- if and .Values.sentinel.enabled (not (eq .Values.architecture "replication")) }} +redis: architecture + Using redis sentinel on standalone mode is not supported. + To deploy redis sentinel, please select the "replication" mode + (--set "architecture=replication,sentinel.enabled=true") +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis® - PodSecurityPolicy create */}} +{{- define "redis.validateValues.podSecurityPolicy.create" -}} +{{- if and .Values.podSecurityPolicy.create (not .Values.podSecurityPolicy.enabled) }} +redis: podSecurityPolicy.create + In order to create PodSecurityPolicy, you also need to enable + podSecurityPolicy.enabled field +{{- end -}} +{{- end -}} + +{{/* Validate values of Redis® - TLS enabled */}} +{{- define "redis.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.tls.autoGenerated) (not .Values.tls.existingSecret) (not .Values.tls.certificatesSecret) }} +redis: tls.enabled + In order to enable TLS, you also need to provide + an existing secret containing the TLS certificates or + enable auto-generated certificates. +{{- end -}} +{{- end -}} + +{{/* Define the suffix utilized for external-dns */}} +{{- define "redis.externalDNS.suffix" -}} +{{ printf "%s.%s" (include "common.names.fullname" .) .Values.useExternalDNS.suffix }} +{{- end -}} + +{{/* Compile all annotations utilized for external-dns */}} +{{- define "redis.externalDNS.annotations" -}} +{{- if .Values.useExternalDNS.enabled }} +{{ .Values.useExternalDNS.annotationKey }}hostname: {{ include "redis.externalDNS.suffix" . }} +{{- range $key, $val := .Values.useExternalDNS.additionalAnnotations }} +{{ $.Values.useExternalDNS.annotationKey }}{{ $key }}: {{ $val | quote }} +{{- end }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/configmap.yaml b/rds/base/charts/redis/templates/configmap.yaml new file mode 100644 index 0000000..274e75b --- /dev/null +++ b/rds/base/charts/redis/templates/configmap.yaml @@ -0,0 +1,59 @@ +{{- if (include "redis.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-configuration" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + redis.conf: |- + # User-supplied common configuration: + {{- if .Values.commonConfiguration }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonConfiguration "context" $ ) | nindent 4 }} + {{- end }} + # End of common configuration + master.conf: |- + dir {{ .Values.master.persistence.path }} + # User-supplied master configuration: + {{- if .Values.master.configuration }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.configuration "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.master.disableCommands }} + {{- range .Values.master.disableCommands }} + rename-command {{ . }} "" + {{- end }} + {{- end }} + # End of master configuration + replica.conf: |- + dir {{ .Values.replica.persistence.path }} + # User-supplied replica configuration: + {{- if .Values.replica.configuration }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.configuration "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.replica.disableCommands }} + {{- range .Values.replica.disableCommands }} + rename-command {{ . }} "" + {{- end }} + {{- end }} + # End of replica configuration + {{- if .Values.sentinel.enabled }} + sentinel.conf: |- + dir "/tmp" + port {{ .Values.sentinel.containerPorts.sentinel }} + sentinel monitor {{ .Values.sentinel.masterSet }} {{ template "common.names.fullname" . }}-node-0.{{ template "common.names.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{ .Values.sentinel.service.ports.redis }} {{ .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 }} + # User-supplied sentinel configuration: + {{- if .Values.sentinel.configuration }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.configuration "context" $ ) | nindent 4 }} + {{- end }} + # End of sentinel configuration + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/extra-list.yaml b/rds/base/charts/redis/templates/extra-list.yaml new file mode 100644 index 0000000..9ac65f9 --- /dev/null +++ b/rds/base/charts/redis/templates/extra-list.yaml @@ -0,0 +1,4 @@ +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/rds/base/charts/redis/templates/headless-svc.yaml b/rds/base/charts/redis/templates/headless-svc.yaml new file mode 100644 index 0000000..e164fea --- /dev/null +++ b/rds/base/charts/redis/templates/headless-svc.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-headless" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + annotations: + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- include "redis.externalDNS.annotations" . | nindent 4 }} +spec: + type: ClusterIP + clusterIP: None + {{- if .Values.sentinel.enabled }} + publishNotReadyAddresses: true + {{- end }} + ports: + - name: tcp-redis + port: {{ if .Values.sentinel.enabled }}{{ .Values.sentinel.service.ports.redis }}{{ else }}{{ .Values.master.service.ports.redis }}{{ end }} + targetPort: redis + {{- if .Values.sentinel.enabled }} + - name: tcp-sentinel + port: {{ .Values.sentinel.service.ports.sentinel }} + targetPort: redis-sentinel + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} diff --git a/rds/base/charts/redis/templates/health-configmap.yaml b/rds/base/charts/redis/templates/health-configmap.yaml new file mode 100644 index 0000000..47cb3fd --- /dev/null +++ b/rds/base/charts/redis/templates/health-configmap.yaml @@ -0,0 +1,192 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-health" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + ping_readiness_local.sh: |- + #!/bin/bash + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + [[ -n "$REDIS_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_PASSWORD" + response=$( + timeout -s 3 $1 \ + redis-cli \ + -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 [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_local.sh: |- + #!/bin/bash + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + [[ -n "$REDIS_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_PASSWORD" + response=$( + timeout -s 3 $1 \ + redis-cli \ + -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 [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + responseFirstWord=$(echo $response | head -n1 | awk '{print $1;}') + if [ "$response" != "PONG" ] && [ "$responseFirstWord" != "LOADING" ] && [ "$responseFirstWord" != "MASTERDOWN" ]; then + echo "$response" + exit 1 + fi +{{- if .Values.sentinel.enabled }} + ping_sentinel.sh: |- + #!/bin/bash + +{{- if .Values.auth.sentinel }} + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + [[ -n "$REDIS_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_PASSWORD" +{{- end }} + response=$( + timeout -s 3 $1 \ + redis-cli \ + -h localhost \ +{{- if .Values.tls.enabled }} + -p $REDIS_SENTINEL_TLS_PORT_NUMBER \ + --tls \ + --cacert "$REDIS_SENTINEL_TLS_CA_FILE" \ + {{- if .Values.tls.authClients }} + --cert "$REDIS_SENTINEL_TLS_CERT_FILE" \ + --key "$REDIS_SENTINEL_TLS_KEY_FILE" \ + {{- end }} +{{- else }} + -p $REDIS_SENTINEL_PORT \ +{{- end }} + ping + ) + if [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + 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 + + [[ -f $REDIS_MASTER_PASSWORD_FILE ]] && export REDIS_MASTER_PASSWORD="$(< "${REDIS_MASTER_PASSWORD_FILE}")" + [[ -n "$REDIS_MASTER_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_MASTER_PASSWORD" + response=$( + timeout -s 3 $1 \ + redis-cli \ + -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 [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + ping_liveness_master.sh: |- + #!/bin/bash + + [[ -f $REDIS_MASTER_PASSWORD_FILE ]] && export REDIS_MASTER_PASSWORD="$(< "${REDIS_MASTER_PASSWORD_FILE}")" + [[ -n "$REDIS_MASTER_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_MASTER_PASSWORD" + response=$( + timeout -s 3 $1 \ + redis-cli \ + -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 [ "$?" -eq "124" ]; then + echo "Timed out" + exit 1 + fi + responseFirstWord=$(echo $response | head -n1 | awk '{print $1;}') + if [ "$response" != "PONG" ] && [ "$responseFirstWord" != "LOADING" ]; 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/rds/base/charts/redis/templates/master/application.yaml b/rds/base/charts/redis/templates/master/application.yaml new file mode 100644 index 0000000..3643b43 --- /dev/null +++ b/rds/base/charts/redis/templates/master/application.yaml @@ -0,0 +1,473 @@ +{{- if or (not (eq .Values.architecture "replication")) (not .Values.sentinel.enabled) }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: {{ .Values.master.kind }} +metadata: + name: {{ printf "%s-master" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: master + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.master.count }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: master + {{- if (eq .Values.master.kind "StatefulSet") }} + serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) }} + {{- end }} + {{- if .Values.master.updateStrategy }} + {{- if (eq .Values.master.kind "Deployment") }} + strategy: {{- toYaml .Values.master.updateStrategy | nindent 4 }} + {{- else }} + updateStrategy: {{- toYaml .Values.master.updateStrategy | nindent 4 }} + {{- end }} + {{- end }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: master + {{- if .Values.master.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.podLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podLabels "context" $ ) | nindent 8 }} + {{- end }} + annotations: + {{- if (include "redis.createConfigmap" .) }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/scripts: {{ include (print $.Template.BasePath "/scripts-configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.master.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + spec: + {{- include "redis.imagePullSecrets" . | nindent 6 }} + {{- if .Values.master.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.master.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.master.podSecurityContext.enabled }} + securityContext: {{- omit .Values.master.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "redis.serviceAccountName" . }} + {{- if .Values.master.priorityClassName }} + priorityClassName: {{ .Values.master.priorityClassName | quote }} + {{- end }} + {{- if .Values.master.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.master.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.master.podAffinityPreset "component" "master" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.master.podAntiAffinityPreset "component" "master" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.master.nodeAffinityPreset.type "key" .Values.master.nodeAffinityPreset.key "values" .Values.master.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.master.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.master.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.master.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.master.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.master.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.master.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.master.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.master.shareProcessNamespace }} + {{- end }} + {{- if .Values.master.schedulerName }} + schedulerName: {{ .Values.master.schedulerName | quote }} + {{- end }} + {{- if .Values.master.dnsPolicy }} + dnsPolicy: {{ .Values.master.dnsPolicy }} + {{- end }} + {{- if .Values.master.dnsConfig }} + dnsConfig: {{- include "common.tplvalues.render" (dict "value" .Values.master.dnsConfig "context" $) | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.master.terminationGracePeriodSeconds }} + containers: + - name: redis + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if .Values.master.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.master.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.master.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.master.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.master.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.master.command "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.master.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.master.args "context" $) | nindent 12 }} + {{- else }} + args: + - -c + - /opt/bitnami/scripts/start-scripts/start-master.sh + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: REDIS_REPLICATION_MODE + value: master + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usePasswordFiles }} + - 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 }} + {{- end }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.master.containerPorts.redis | 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.master.containerPorts.redis | quote }} + {{- end }} + {{- if .Values.master.extraEnvVars }} + {{- include "common.tplvalues.render" (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.master.containerPorts.redis }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.master.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.master.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: redis + {{- else if .Values.master.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.master.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- 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: {{- include "common.tplvalues.render" (dict "value" .Values.master.customLivenessProbe "context" $) | 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: {{- include "common.tplvalues.render" (dict "value" .Values.master.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.master.resources }} + resources: {{- toYaml .Values.master.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: start-scripts + mountPath: /opt/bitnami/scripts/start-scripts + - name: health + mountPath: /health + {{- if .Values.auth.usePasswordFiles }} + - 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/ + - name: tmp + mountPath: /tmp + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.master.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else }} + 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 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "common.names.fullname" . }} + {{- if .Values.auth.enabled }} + - name: REDIS_USER + value: default + {{- if (not .Values.auth.usePasswordFiles) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_ADDR + value: rediss://{{ .Values.metrics.redisTargetHost }}:{{ .Values.master.containerPorts.redis }} + {{- if .Values.tls.authClients }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis.tlsCert" . }} + {{- end }} + - name: REDIS_EXPORTER_TLS_CA_CERT_FILE + value: {{ template "redis.tlsCACert" . }} + {{- end }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + ports: + - name: metrics + containerPort: 9121 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.master.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.master.sidecars "context" $) | nindent 8 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled .Values.master.persistence.enabled .Values.master.podSecurityContext.enabled .Values.master.containerSecurityContext.enabled }} + {{- if or .Values.master.initContainers $needsVolumePermissions .Values.sysctl.enabled }} + initContainers: + {{- if .Values.master.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.master.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: {{ include "redis.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + - | + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` {{ .Values.master.persistence.path }} + {{- else }} + chown -R {{ .Values.master.containerSecurityContext.runAsUser }}:{{ .Values.master.podSecurityContext.fsGroup }} {{ .Values.master.persistence.path }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.containerSecurityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.containerSecurityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.master.persistence.path }} + subPath: {{ .Values.master.persistence.subPath }} + {{- end }} + {{- if .Values.sysctl.enabled }} + - name: init-sysctl + image: {{ include "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctl.image.pullPolicy | quote }} + securityContext: + privileged: true + runAsUser: 0 + {{- if .Values.sysctl.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.sysctl.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.resources }} + resources: {{- toYaml .Values.sysctl.resources | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + {{- end }} + {{- end }} + volumes: + - name: start-scripts + configMap: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + defaultMode: 0755 + - name: health + configMap: + name: {{ printf "%s-health" (include "common.names.fullname" .) }} + defaultMode: 0755 + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ include "redis.configmapName" . }} + {{- if .Values.sysctl.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: redis-tmp-conf + {{- if .Values.master.persistence.medium }} + emptyDir: + medium: {{ .Values.master.persistence.medium | quote }} + {{- if .Values.master.persistence.sizeLimit }} + sizeLimit: {{ .Values.master.persistence.sizeLimit | quote }} + {{- end }} + {{- else }} + emptyDir: {} + {{- end }} + - name: tmp + {{- if .Values.master.persistence.medium }} + emptyDir: + medium: {{ .Values.master.persistence.medium | quote }} + {{- if .Values.master.persistence.sizeLimit }} + sizeLimit: {{ .Values.master.persistence.sizeLimit | quote }} + {{- end }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ include "redis.tlsSecretName" . }} + defaultMode: 256 + {{- end }} + {{- if .Values.master.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if not .Values.master.persistence.enabled }} + - name: redis-data + {{- if .Values.master.persistence.medium }} + emptyDir: { + medium: {{ .Values.master.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + {{- else if .Values.master.persistence.existingClaim }} + - name: redis-data + persistentVolumeClaim: + claimName: {{ printf "%s" (tpl .Values.master.persistence.existingClaim .) }} + {{- else if (eq .Values.master.kind "Deployment") }} + - name: redis-data + persistentVolumeClaim: + claimName: {{ printf "redis-data-%s-master" (include "common.names.fullname" .) }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: {{- include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: master + {{- if .Values.master.persistence.annotations }} + annotations: {{- toYaml .Values.master.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.master.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.master.persistence.size | quote }} + {{- if .Values.master.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.master.persistence.selector "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.master.persistence.dataSource }} + dataSource: {{- include "common.tplvalues.render" (dict "value" .Values.master.persistence.dataSource "context" $) | nindent 10 }} + {{- end }} + {{- include "common.storage.class" (dict "persistence" .Values.master.persistence "global" .Values.global) | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/master/psp.yaml b/rds/base/charts/redis/templates/master/psp.yaml new file mode 100644 index 0000000..fc1ebf0 --- /dev/null +++ b/rds/base/charts/redis/templates/master/psp.yaml @@ -0,0 +1,46 @@ +{{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} +{{- if and $pspAvailable .Values.podSecurityPolicy.create }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ printf "%s-master" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + allowPrivilegeEscalation: false + fsGroup: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.master.podSecurityContext.fsGroup }} + max: {{ .Values.master.podSecurityContext.fsGroup }} + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: false + requiredDropCapabilities: + - ALL + runAsUser: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.master.containerSecurityContext.runAsUser }} + max: {{ .Values.master.containerSecurityContext.runAsUser }} + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: {{ .Values.master.containerSecurityContext.runAsUser }} + max: {{ .Values.master.containerSecurityContext.runAsUser }} + volumes: + - 'configMap' + - 'secret' + - 'emptyDir' + - 'persistentVolumeClaim' +{{- end }} diff --git a/rds/base/charts/redis/templates/master/pvc.yaml b/rds/base/charts/redis/templates/master/pvc.yaml new file mode 100644 index 0000000..ad45562 --- /dev/null +++ b/rds/base/charts/redis/templates/master/pvc.yaml @@ -0,0 +1,27 @@ +{{- if and (eq .Values.architecture "standalone") (eq .Values.master.kind "Deployment") (.Values.master.persistence.enabled) (not .Values.master.persistence.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ printf "redis-data-%s-master" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: master + {{- if .Values.master.persistence.annotations }} + annotations: {{- toYaml .Values.master.persistence.annotations | nindent 4 }} + {{- end }} +spec: + accessModes: + {{- range .Values.master.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.master.persistence.size | quote }} + {{- if .Values.master.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.master.persistence.selector "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.master.persistence.dataSource }} + dataSource: {{- include "common.tplvalues.render" (dict "value" .Values.master.persistence.dataSource "context" $) | nindent 4 }} + {{- end }} + {{- include "common.storage.class" (dict "persistence" .Values.master.persistence "global" .Values.global) | nindent 2 }} +{{- end }} diff --git a/rds/base/charts/redis/templates/master/service.yaml b/rds/base/charts/redis/templates/master/service.yaml new file mode 100644 index 0000000..e7e4898 --- /dev/null +++ b/rds/base/charts/redis/templates/master/service.yaml @@ -0,0 +1,58 @@ +{{- if not .Values.sentinel.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-master" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: master + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.master.service.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.master.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.master.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.master.service.type }} + {{- if or (eq .Values.master.service.type "LoadBalancer") (eq .Values.master.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.master.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if (semverCompare ">=1.22-0" (include "common.capabilities.kubeVersion" .)) }} + internalTrafficPolicy: {{ .Values.master.service.internalTrafficPolicy }} + {{- end }} + {{- if and (eq .Values.master.service.type "LoadBalancer") (not (empty .Values.master.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.master.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.master.service.type "LoadBalancer") (not (empty .Values.master.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.master.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and .Values.master.service.clusterIP (eq .Values.master.service.type "ClusterIP") }} + clusterIP: {{ .Values.master.service.clusterIP }} + {{- end }} + {{- if .Values.master.service.sessionAffinity }} + sessionAffinity: {{ .Values.master.service.sessionAffinity }} + {{- end }} + {{- if .Values.master.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.master.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: tcp-redis + port: {{ .Values.master.service.ports.redis }} + targetPort: redis + {{- if and (or (eq .Values.master.service.type "NodePort") (eq .Values.master.service.type "LoadBalancer")) .Values.master.service.nodePorts.redis}} + nodePort: {{ .Values.master.service.nodePorts.redis}} + {{- else if eq .Values.master.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- if .Values.master.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.master.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: master +{{- end }} diff --git a/rds/base/charts/redis/templates/metrics-svc.yaml b/rds/base/charts/redis/templates/metrics-svc.yaml new file mode 100644 index 0000000..5b72494 --- /dev/null +++ b/rds/base/charts/redis/templates/metrics-svc.yaml @@ -0,0 +1,41 @@ +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-metrics" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.metrics.service.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.metrics.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + {{- if eq .Values.metrics.service.type "LoadBalancer" }} + externalTrafficPolicy: {{ .Values.metrics.service.externalTrafficPolicy }} + {{- end }} + {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.metrics.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.metrics.service.type "LoadBalancer") .Values.metrics.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.metrics.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + ports: + - name: http-metrics + port: {{ .Values.metrics.service.port }} + protocol: TCP + targetPort: metrics + {{- if .Values.metrics.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} +{{- end }} diff --git a/rds/base/charts/redis/templates/networkpolicy.yaml b/rds/base/charts/redis/templates/networkpolicy.yaml new file mode 100644 index 0000000..f45cc69 --- /dev/null +++ b/rds/base/charts/redis/templates/networkpolicy.yaml @@ -0,0 +1,78 @@ +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ template "networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + policyTypes: + - Ingress + {{- if or (eq .Values.architecture "replication") .Values.networkPolicy.extraEgress }} + - Egress + egress: + {{- if eq .Values.architecture "replication" }} + # Allow dns resolution + - ports: + - port: 53 + protocol: UDP + # Allow outbound connections to other cluster pods + - ports: + - port: {{ .Values.master.containerPorts.redis }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.containerPorts.sentinel }} + {{- end }} + to: + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} + {{- end }} + {{- if .Values.networkPolicy.extraEgress }} + {{- include "common.tplvalues.render" ( dict "value" .Values.networkPolicy.extraEgress "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} + ingress: + # Allow inbound connections + - ports: + - port: {{ .Values.master.containerPorts.redis }} + {{- if .Values.sentinel.enabled }} + - port: {{ .Values.sentinel.containerPorts.sentinel }} + {{- end }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 14 }} + {{- 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 }} + {{- if .Values.networkPolicy.extraIngress }} + {{- include "common.tplvalues.render" ( dict "value" .Values.networkPolicy.extraIngress "context" $ ) | nindent 4 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/pdb.yaml b/rds/base/charts/redis/templates/pdb.yaml new file mode 100644 index 0000000..bd6e917 --- /dev/null +++ b/rds/base/charts/redis/templates/pdb.yaml @@ -0,0 +1,23 @@ +{{- if .Values.pdb.create }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +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 }} +{{- end }} diff --git a/rds/base/charts/redis/templates/prometheusrule.yaml b/rds/base/charts/redis/templates/prometheusrule.yaml new file mode 100644 index 0000000..2d82ecc --- /dev/null +++ b/rds/base/charts/redis/templates/prometheusrule.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace .Values.metrics.prometheusRule.namespace | quote }} + 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 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ template "common.names.name" $ }} + rules: {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/replicas/hpa.yaml b/rds/base/charts/redis/templates/replicas/hpa.yaml new file mode 100644 index 0000000..543a322 --- /dev/null +++ b/rds/base/charts/redis/templates/replicas/hpa.yaml @@ -0,0 +1,47 @@ +{{- if and .Values.replica.autoscaling.enabled (not .Values.sentinel.enabled) }} +apiVersion: {{ include "common.capabilities.hpa.apiVersion" ( dict "context" $ ) }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ printf "%s-replicas" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: replica + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + scaleTargetRef: + apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} + kind: StatefulSet + name: {{ printf "%s-replicas" (include "common.names.fullname" .) }} + minReplicas: {{ .Values.replica.autoscaling.minReplicas }} + maxReplicas: {{ .Values.replica.autoscaling.maxReplicas }} + metrics: + {{- if .Values.replica.autoscaling.targetCPU }} + - type: Resource + resource: + name: cpu + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.replica.autoscaling.targetCPU }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.replica.autoscaling.targetCPU }} + {{- end }} + {{- end }} + {{- if .Values.replica.autoscaling.targetMemory }} + - type: Resource + resource: + name: memory + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.replica.autoscaling.targetMemory }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.replica.autoscaling.targetMemory }} + {{- end }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/replicas/service.yaml b/rds/base/charts/redis/templates/replicas/service.yaml new file mode 100644 index 0000000..10221b1 --- /dev/null +++ b/rds/base/charts/redis/templates/replicas/service.yaml @@ -0,0 +1,58 @@ +{{- if and (eq .Values.architecture "replication") (not .Values.sentinel.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-replicas" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: replica + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.replica.service.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.replica.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.replica.service.type }} + {{- if or (eq .Values.replica.service.type "LoadBalancer") (eq .Values.replica.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.replica.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if (semverCompare ">=1.22-0" (include "common.capabilities.kubeVersion" .)) }} + internalTrafficPolicy: {{ .Values.replica.service.internalTrafficPolicy }} + {{- end }} + {{- if and (eq .Values.replica.service.type "LoadBalancer") (not (empty .Values.replica.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.replica.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.replica.service.type "LoadBalancer") (not (empty .Values.replica.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.replica.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and .Values.replica.service.clusterIP (eq .Values.replica.service.type "ClusterIP") }} + clusterIP: {{ .Values.replica.service.clusterIP }} + {{- end }} + {{- if .Values.replica.service.sessionAffinity }} + sessionAffinity: {{ .Values.replica.service.sessionAffinity }} + {{- end }} + {{- if .Values.replica.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.replica.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: tcp-redis + port: {{ .Values.replica.service.ports.redis }} + targetPort: redis + {{- if and (or (eq .Values.replica.service.type "NodePort") (eq .Values.replica.service.type "LoadBalancer")) .Values.replica.service.nodePorts.redis}} + nodePort: {{ .Values.replica.service.nodePorts.redis}} + {{- else if eq .Values.replica.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- if .Values.replica.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: replica +{{- end }} diff --git a/rds/base/charts/redis/templates/replicas/statefulset.yaml b/rds/base/charts/redis/templates/replicas/statefulset.yaml new file mode 100644 index 0000000..aa706d9 --- /dev/null +++ b/rds/base/charts/redis/templates/replicas/statefulset.yaml @@ -0,0 +1,471 @@ +{{- if and (eq .Values.architecture "replication") (not .Values.sentinel.enabled) }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ printf "%s-replicas" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: replica + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.replica.autoscaling.enabled }} + replicas: {{ .Values.replica.replicaCount }} + {{- end }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: replica + serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) }} + {{- if .Values.replica.updateStrategy }} + updateStrategy: {{- toYaml .Values.replica.updateStrategy | nindent 4 }} + {{- end }} + {{- if .Values.replica.podManagementPolicy }} + podManagementPolicy: {{ .Values.replica.podManagementPolicy | quote }} + {{- end }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: replica + {{- if .Values.replica.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.podLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podLabels "context" $ ) | nindent 8 }} + {{- end }} + annotations: + {{- if (include "redis.createConfigmap" .) }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/scripts: {{ include (print $.Template.BasePath "/scripts-configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.replica.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + spec: + {{- include "redis.imagePullSecrets" . | nindent 6 }} + {{- if .Values.replica.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.replica.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.podSecurityContext.enabled }} + securityContext: {{- omit .Values.replica.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "redis.serviceAccountName" . }} + {{- if .Values.replica.priorityClassName }} + priorityClassName: {{ .Values.replica.priorityClassName | quote }} + {{- end }} + {{- if .Values.replica.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.replica.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.replica.podAffinityPreset "component" "replica" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.replica.podAntiAffinityPreset "component" "replica" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.replica.nodeAffinityPreset.type "key" .Values.replica.nodeAffinityPreset.key "values" .Values.replica.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.replica.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.replica.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.replica.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.replica.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.replica.shareProcessNamespace }} + {{- end }} + {{- if .Values.replica.schedulerName }} + schedulerName: {{ .Values.replica.schedulerName | quote }} + {{- end }} + {{- if .Values.replica.dnsPolicy }} + dnsPolicy: {{ .Values.replica.dnsPolicy }} + {{- end }} + {{- if .Values.replica.dnsConfig }} + dnsConfig: {{- include "common.tplvalues.render" (dict "value" .Values.replica.dnsConfig "context" $) | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.replica.terminationGracePeriodSeconds }} + containers: + - name: redis + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.replica.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.replica.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.replica.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.replica.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.replica.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.replica.command "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.replica.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.replica.args "context" $) | nindent 12 }} + {{- else }} + args: + - -c + - /opt/bitnami/scripts/start-scripts/start-replica.sh + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: REDIS_REPLICATION_MODE + value: slave + - name: REDIS_MASTER_HOST + {{- if and (eq (int64 .Values.master.count) 1) (ne .Values.master.kind "Deployment") }} + value: {{ template "common.names.fullname" . }}-master-0.{{ template "common.names.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + {{- else }} + value: {{ template "common.names.fullname" . }}-master.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} + {{- end }} + - name: REDIS_MASTER_PORT_NUMBER + value: {{ .Values.master.containerPorts.redis | quote }} + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usePasswordFiles }} + - 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 }} + {{- end }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.replica.containerPorts.redis | 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.replica.containerPorts.redis | quote }} + {{- end }} + {{- if .Values.replica.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.replica.extraEnvVarsCM .Values.replica.extraEnvVarsSecret }} + envFrom: + {{- if .Values.replica.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.replica.extraEnvVarsCM }} + {{- end }} + {{- if .Values.replica.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.replica.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: redis + containerPort: {{ .Values.replica.containerPorts.redis }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.replica.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.replica.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: redis + {{- else if .Values.replica.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.replica.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.replica.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.replica.livenessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.replica.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.replica.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.replica.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_liveness_local_and_master.sh {{ .Values.replica.livenessProbe.timeoutSeconds }} + {{- else if .Values.replica.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.replica.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.replica.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.replica.readinessProbe.periodSeconds }} + timeoutSeconds: {{ add1 .Values.replica.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.replica.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.replica.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_readiness_local_and_master.sh {{ .Values.replica.readinessProbe.timeoutSeconds }} + {{- else if .Values.replica.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.replica.resources }} + resources: {{- toYaml .Values.replica.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: start-scripts + mountPath: /opt/bitnami/scripts/start-scripts + - name: health + mountPath: /health + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: /data + subPath: {{ .Values.replica.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 .Values.replica.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.metrics.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.command "context" $) | nindent 12 }} + {{- else }} + 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 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "common.names.fullname" . }} + {{- if .Values.auth.enabled }} + - name: REDIS_USER + value: default + {{- if (not .Values.auth.usePasswordFiles) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_ADDR + value: rediss://{{ .Values.metrics.redisTargetHost }}:{{ .Values.replica.containerPorts.redis }} + {{- if .Values.tls.authClients }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis.tlsCert" . }} + {{- end }} + - name: REDIS_EXPORTER_TLS_CA_CERT_FILE + value: {{ template "redis.tlsCACert" . }} + {{- end }} + ports: + - name: metrics + containerPort: 9121 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.replica.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.sidecars "context" $) | nindent 8 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled .Values.replica.persistence.enabled .Values.replica.podSecurityContext.enabled .Values.replica.containerSecurityContext.enabled }} + {{- if or .Values.replica.initContainers $needsVolumePermissions .Values.sysctl.enabled }} + initContainers: + {{- if .Values.replica.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: {{ include "redis.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + - | + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` {{ .Values.replica.persistence.path }} + {{- else }} + chown -R {{ .Values.replica.containerSecurityContext.runAsUser }}:{{ .Values.replica.podSecurityContext.fsGroup }} {{ .Values.replica.persistence.path }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.containerSecurityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.containerSecurityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.replica.persistence.path }} + subPath: {{ .Values.replica.persistence.subPath }} + {{- end }} + {{- if .Values.sysctl.enabled }} + - name: init-sysctl + image: {{ include "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctl.image.pullPolicy | quote }} + securityContext: + privileged: true + runAsUser: 0 + {{- if .Values.sysctl.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.sysctl.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.resources }} + resources: {{- toYaml .Values.sysctl.resources | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + {{- end }} + {{- end }} + volumes: + - name: start-scripts + configMap: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + defaultMode: 0755 + - name: health + configMap: + name: {{ printf "%s-health" (include "common.names.fullname" .) }} + defaultMode: 0755 + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ include "redis.configmapName" . }} + {{- if .Values.sysctl.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + - name: redis-tmp-conf + {{- if .Values.replica.persistence.medium }} + emptyDir: + medium: {{ .Values.replica.persistence.medium | quote }} + {{- if .Values.replica.persistence.sizeLimit }} + sizeLimit: {{ .Values.replica.persistence.sizeLimit | quote }} + {{- end }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ include "redis.tlsSecretName" . }} + defaultMode: 256 + {{- end }} + {{- if .Values.replica.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if not .Values.replica.persistence.enabled }} + - name: redis-data + {{- if .Values.replica.persistence.medium }} + emptyDir: { + medium: {{ .Values.replica.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + {{- else if .Values.replica.persistence.existingClaim }} + - name: redis-data + persistentVolumeClaim: + claimName: {{ printf "%s" (tpl .Values.replica.persistence.existingClaim .) }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: {{- include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: replica + {{- if .Values.replica.persistence.annotations }} + annotations: {{- toYaml .Values.replica.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.replica.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.replica.persistence.size | quote }} + {{- if .Values.replica.persistence.selector }} + selector: {{- include "common.tplvalues.render" (dict "value" .Values.replica.persistence.selector "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.replica.persistence.dataSource }} + dataSource: {{- include "common.tplvalues.render" (dict "value" .Values.replica.persistence.dataSource "context" $) | nindent 10 }} + {{- end }} + {{- include "common.storage.class" (dict "persistence" .Values.replica.persistence "global" .Values.global) | nindent 8 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/role.yaml b/rds/base/charts/redis/templates/role.yaml new file mode 100644 index 0000000..0cd806a --- /dev/null +++ b/rds/base/charts/redis/templates/role.yaml @@ -0,0 +1,28 @@ +{{- if .Values.rbac.create }} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +kind: Role +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +rules: + {{- $pspAvailable := (semverCompare "<1.25-0" (include "common.capabilities.kubeVersion" .)) -}} + {{- if and $pspAvailable .Values.podSecurityPolicy.enabled }} + - apiGroups: + - '{{ template "podSecurityPolicy.apiGroup" . }}' + resources: + - 'podsecuritypolicies' + verbs: + - 'use' + resourceNames: [{{ printf "%s-master" (include "common.names.fullname" .) }}] + {{- end }} + {{- if .Values.rbac.rules }} + {{- include "common.tplvalues.render" ( dict "value" .Values.rbac.rules "context" $ ) | nindent 2 }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/rolebinding.yaml b/rds/base/charts/redis/templates/rolebinding.yaml new file mode 100644 index 0000000..79a5987 --- /dev/null +++ b/rds/base/charts/redis/templates/rolebinding.yaml @@ -0,0 +1,21 @@ +{{- if .Values.rbac.create }} +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +kind: RoleBinding +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "common.names.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "redis.serviceAccountName" . }} +{{- end }} diff --git a/rds/base/charts/redis/templates/scripts-configmap.yaml b/rds/base/charts/redis/templates/scripts-configmap.yaml new file mode 100644 index 0000000..cab9291 --- /dev/null +++ b/rds/base/charts/redis/templates/scripts-configmap.yaml @@ -0,0 +1,627 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled }} + start-node.sh: | + #!/bin/bash + + . /opt/bitnami/scripts/libos.sh + . /opt/bitnami/scripts/liblog.sh + . /opt/bitnami/scripts/libvalidations.sh + + get_port() { + hostname="$1" + type="$2" + + port_var=$(echo "${hostname^^}_SERVICE_PORT_$type" | sed "s/-/_/g") + port=${!port_var} + + if [ -z "$port" ]; then + case $type in + "SENTINEL") + echo {{ .Values.sentinel.containerPorts.sentinel }} + ;; + "REDIS") + echo {{ .Values.master.containerPorts.redis }} + ;; + esac + else + echo $port + fi + } + + get_full_hostname() { + hostname="$1" + + {{- if .Values.useExternalDNS.enabled }} + echo "${hostname}.{{- include "redis.externalDNS.suffix" . }}" + {{- else if eq .Values.sentinel.service.type "NodePort" }} + echo "${hostname}.{{- .Release.Namespace }}" + {{- else }} + echo "${hostname}.${HEADLESS_SERVICE}" + {{- end }} + } + + REDISPORT=$(get_port "$HOSTNAME" "REDIS") + + HEADLESS_SERVICE="{{ template "common.names.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + + if [ -n "$REDIS_EXTERNAL_MASTER_HOST" ]; then + REDIS_SERVICE="$REDIS_EXTERNAL_MASTER_HOST" + else + REDIS_SERVICE="{{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + fi + + SENTINEL_SERVICE_PORT=$(get_port "{{ include "common.names.fullname" . }}" "TCP_SENTINEL") + validate_quorum() { + if is_boolean_yes "$REDIS_SENTINEL_TLS_ENABLED"; then + quorum_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT --tls --cert ${REDIS_SENTINEL_TLS_CERT_FILE} --key ${REDIS_SENTINEL_TLS_KEY_FILE} --cacert ${REDIS_SENTINEL_TLS_CA_FILE} sentinel master {{ .Values.sentinel.masterSet }}" + else + quorum_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT sentinel master {{ .Values.sentinel.masterSet }}" + fi + info "about to run the command: $quorum_info_command" + eval $quorum_info_command | grep -Fq "s_down" + } + + trigger_manual_failover() { + if is_boolean_yes "$REDIS_SENTINEL_TLS_ENABLED"; then + failover_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT --tls --cert ${REDIS_SENTINEL_TLS_CERT_FILE} --key ${REDIS_SENTINEL_TLS_KEY_FILE} --cacert ${REDIS_SENTINEL_TLS_CA_FILE} sentinel failover {{ .Values.sentinel.masterSet }}" + else + failover_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT sentinel failover {{ .Values.sentinel.masterSet }}" + fi + + info "about to run the command: $failover_command" + eval $failover_command + } + + get_sentinel_master_info() { + if is_boolean_yes "$REDIS_SENTINEL_TLS_ENABLED"; then + sentinel_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}timeout {{ .Values.sentinel.getMasterTimeout }} redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT --tls --cert ${REDIS_SENTINEL_TLS_CERT_FILE} --key ${REDIS_SENTINEL_TLS_KEY_FILE} --cacert ${REDIS_SENTINEL_TLS_CA_FILE} sentinel get-master-addr-by-name {{ .Values.sentinel.masterSet }}" + else + sentinel_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}timeout {{ .Values.sentinel.getMasterTimeout }} redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT sentinel get-master-addr-by-name {{ .Values.sentinel.masterSet }}" + fi + + info "about to run the command: $sentinel_info_command" + eval $sentinel_info_command + } + + {{- if and .Values.replica.containerSecurityContext.runAsUser (eq (.Values.replica.containerSecurityContext.runAsUser | int) 0) }} + useradd redis + chown -R redis {{ .Values.replica.persistence.path }} + {{- end }} + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + [[ -f $REDIS_MASTER_PASSWORD_FILE ]] && export REDIS_MASTER_PASSWORD="$(< "${REDIS_MASTER_PASSWORD_FILE}")" + + # check if there is a master + master_in_persisted_conf="$(get_full_hostname "$HOSTNAME")" + master_port_in_persisted_conf="$REDIS_MASTER_PORT_NUMBER" + master_in_sentinel="$(get_sentinel_master_info)" + redisRetVal=$? + + {{- if .Values.sentinel.persistence.enabled }} + if [[ -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]]; then + master_in_persisted_conf="$(awk '/monitor/ {print $4}' /opt/bitnami/redis-sentinel/etc/sentinel.conf)" + master_port_in_persisted_conf="$(awk '/monitor/ {print $5}' /opt/bitnami/redis-sentinel/etc/sentinel.conf)" + info "Found previous master ${master_in_persisted_conf}:${master_port_in_persisted_conf} in /opt/bitnami/redis-sentinel/etc/sentinel.conf" + debug "$(cat /opt/bitnami/redis-sentinel/etc/sentinel.conf | grep monitor)" + touch /opt/bitnami/redis-sentinel/etc/.node_read + fi + {{- end }} + + if [[ $redisRetVal -ne 0 ]]; then + if [[ "$master_in_persisted_conf" == "$(get_full_hostname "$HOSTNAME")" ]]; then + # Case 1: No active sentinel and in previous sentinel.conf we were the master --> MASTER + info "Configuring the node as master" + export REDIS_REPLICATION_MODE="master" + else + # Case 2: No active sentinel and in previous sentinel.conf we were not master --> REPLICA + info "Configuring the node as replica" + export REDIS_REPLICATION_MODE="slave" + REDIS_MASTER_HOST=${master_in_persisted_conf} + REDIS_MASTER_PORT_NUMBER=${master_port_in_persisted_conf} + fi + else + # Fetches current master's host and port + REDIS_SENTINEL_INFO=($(get_sentinel_master_info)) + info "Current master: REDIS_SENTINEL_INFO=(${REDIS_SENTINEL_INFO[0]},${REDIS_SENTINEL_INFO[1]})" + REDIS_MASTER_HOST=${REDIS_SENTINEL_INFO[0]} + REDIS_MASTER_PORT_NUMBER=${REDIS_SENTINEL_INFO[1]} + + if [[ "$REDIS_MASTER_HOST" == "$(get_full_hostname "$HOSTNAME")" ]]; then + # Case 3: Active sentinel and master it is this node --> MASTER + info "Configuring the node as master" + export REDIS_REPLICATION_MODE="master" + else + # Case 4: Active sentinel and master is not this node --> REPLICA + info "Configuring the node as replica" + export REDIS_REPLICATION_MODE="slave" + + {{- if and .Values.sentinel.automateClusterRecovery (le (int .Values.sentinel.downAfterMilliseconds) 2000) }} + retry_count=1 + while validate_quorum + do + info "sleeping, waiting for Redis master to come up" + sleep 1s + if ! ((retry_count % 11)); then + info "Trying to manually failover" + failover_result=$(trigger_manual_failover) + + debug "Failover result: $failover_result" + fi + + ((retry_count+=1)) + done + info "Redis master is up now" + {{- end }} + fi + fi + + if [[ -n "$REDIS_EXTERNAL_MASTER_HOST" ]]; then + REDIS_MASTER_HOST="$REDIS_EXTERNAL_MASTER_HOST" + REDIS_MASTER_PORT_NUMBER="${REDIS_EXTERNAL_MASTER_PORT}" + 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 + + echo "" >> /opt/bitnami/redis/etc/replica.conf + echo "replica-announce-port $REDISPORT" >> /opt/bitnami/redis/etc/replica.conf + echo "replica-announce-ip $(get_full_hostname "$HOSTNAME")" >> /opt/bitnami/redis/etc/replica.conf + + {{- 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 }} + + if [[ "$REDIS_REPLICATION_MODE" = "slave" ]]; then + ARGS+=("--replicaof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}") + fi + + {{- if .Values.auth.enabled }} + ARGS+=("--requirepass" "${REDIS_PASSWORD}") + ARGS+=("--masterauth" "${REDIS_MASTER_PASSWORD}") + {{- else }} + ARGS+=("--protected-mode" "no") + {{- end }} + ARGS+=("--include" "/opt/bitnami/redis/etc/replica.conf") + ARGS+=("--include" "/opt/bitnami/redis/etc/redis.conf") + {{- if .Values.replica.extraFlags }} + {{- range .Values.replica.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + + {{- if .Values.replica.preExecCmds }} + {{- .Values.replica.preExecCmds | nindent 4 }} + {{- end }} + + {{- if .Values.replica.command }} + exec {{ .Values.replica.command }} "${ARGS[@]}" + {{- else }} + exec redis-server "${ARGS[@]}" + {{- end }} + + start-sentinel.sh: | + #!/bin/bash + + . /opt/bitnami/scripts/libos.sh + . /opt/bitnami/scripts/libvalidations.sh + . /opt/bitnami/scripts/libfile.sh + + HEADLESS_SERVICE="{{ template "common.names.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + REDIS_SERVICE="{{ template "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + + get_port() { + hostname="$1" + type="$2" + + port_var=$(echo "${hostname^^}_SERVICE_PORT_$type" | sed "s/-/_/g") + port=${!port_var} + + if [ -z "$port" ]; then + case $type in + "SENTINEL") + echo {{ .Values.sentinel.containerPorts.sentinel }} + ;; + "REDIS") + echo {{ .Values.master.containerPorts.redis }} + ;; + esac + else + echo $port + fi + } + + get_full_hostname() { + hostname="$1" + + {{- if .Values.useExternalDNS.enabled }} + echo "${hostname}.{{- include "redis.externalDNS.suffix" . }}" + {{- else if eq .Values.sentinel.service.type "NodePort" }} + echo "${hostname}.{{- .Release.Namespace }}" + {{- else }} + echo "${hostname}.${HEADLESS_SERVICE}" + {{- end }} + } + + SERVPORT=$(get_port "$HOSTNAME" "SENTINEL") + REDISPORT=$(get_port "$HOSTNAME" "REDIS") + SENTINEL_SERVICE_PORT=$(get_port "{{ include "common.names.fullname" . }}" "TCP_SENTINEL") + + sentinel_conf_set() { + local -r key="${1:?missing key}" + local value="${2:-}" + + # Sanitize inputs + value="${value//\\/\\\\}" + value="${value//&/\\&}" + value="${value//\?/\\?}" + [[ "$value" = "" ]] && value="\"$value\"" + + replace_in_file "/opt/bitnami/redis-sentinel/etc/sentinel.conf" "^#*\s*${key} .*" "${key} ${value}" false + } + sentinel_conf_add() { + echo $'\n'"$@" >> "/opt/bitnami/redis-sentinel/etc/sentinel.conf" + } + host_id() { + echo "$1" | openssl sha1 | awk '{print $2}' + } + get_sentinel_master_info() { + if is_boolean_yes "$REDIS_SENTINEL_TLS_ENABLED"; then + sentinel_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT --tls --cert ${REDIS_SENTINEL_TLS_CERT_FILE} --key ${REDIS_SENTINEL_TLS_KEY_FILE} --cacert ${REDIS_SENTINEL_TLS_CA_FILE} sentinel get-master-addr-by-name {{ .Values.sentinel.masterSet }}" + else + sentinel_info_command="{{- if and .Values.auth.enabled .Values.auth.sentinel }}REDISCLI_AUTH="\$REDIS_PASSWORD" {{ end }}redis-cli -h $REDIS_SERVICE -p $SENTINEL_SERVICE_PORT sentinel get-master-addr-by-name {{ .Values.sentinel.masterSet }}" + fi + info "about to run the command: $sentinel_info_command" + eval $sentinel_info_command + } + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + + master_in_persisted_conf="$(get_full_hostname "$HOSTNAME")" + + {{- if .Values.sentinel.persistence.enabled }} + if [[ -f /opt/bitnami/redis-sentinel/etc/sentinel.conf ]]; then + check_lock_file() { + [[ -f /opt/bitnami/redis-sentinel/etc/.node_read ]] + } + retry_while "check_lock_file" + rm -f /opt/bitnami/redis-sentinel/etc/.node_read + master_in_persisted_conf="$(awk '/monitor/ {print $4}' /opt/bitnami/redis-sentinel/etc/sentinel.conf)" + info "Found previous master $master_in_persisted_conf in /opt/bitnami/redis-sentinel/etc/sentinel.conf" + debug "$(cat /opt/bitnami/redis-sentinel/etc/sentinel.conf | grep monitor)" + fi + {{- end }} + if ! get_sentinel_master_info && [[ "$master_in_persisted_conf" == "$(get_full_hostname "$HOSTNAME")" ]]; then + # No master found, lets create a master node + export REDIS_REPLICATION_MODE="master" + + REDIS_MASTER_HOST=$(get_full_hostname "$HOSTNAME") + REDIS_MASTER_PORT_NUMBER="$REDISPORT" + else + export REDIS_REPLICATION_MODE="slave" + + # Fetches current master's host and port + REDIS_SENTINEL_INFO=($(get_sentinel_master_info)) + info "printing REDIS_SENTINEL_INFO=(${REDIS_SENTINEL_INFO[0]},${REDIS_SENTINEL_INFO[1]})" + REDIS_MASTER_HOST=${REDIS_SENTINEL_INFO[0]} + REDIS_MASTER_PORT_NUMBER=${REDIS_SENTINEL_INFO[1]} + fi + + if [[ -n "$REDIS_EXTERNAL_MASTER_HOST" ]]; then + REDIS_MASTER_HOST="$REDIS_EXTERNAL_MASTER_HOST" + REDIS_MASTER_PORT_NUMBER="${REDIS_EXTERNAL_MASTER_PORT}" + fi + + cp /opt/bitnami/redis-sentinel/mounted-etc/sentinel.conf /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if .Values.auth.enabled }} + printf "\nsentinel auth-pass %s %s" "{{ .Values.sentinel.masterSet }}" "$REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- if and .Values.auth.enabled .Values.auth.sentinel }} + printf "\nrequirepass %s" "$REDIS_PASSWORD" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + {{- end }} + {{- end }} + printf "\nsentinel myid %s" "$(host_id "$HOSTNAME")" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + + sentinel_conf_set "sentinel monitor" "{{ .Values.sentinel.masterSet }} "$REDIS_MASTER_HOST" "$REDIS_MASTER_PORT_NUMBER" {{ .Values.sentinel.quorum }}" + + add_known_sentinel() { + hostname="$1" + ip="$2" + + if [[ -n "$hostname" && -n "$ip" && "$hostname" != "$HOSTNAME" ]]; then + sentinel_conf_add "sentinel known-sentinel {{ .Values.sentinel.masterSet }} $(get_full_hostname "$hostname") $(get_port "$hostname" "SENTINEL") $(host_id "$hostname")" + fi + } + add_known_replica() { + hostname="$1" + ip="$2" + + if [[ -n "$ip" && "$(get_full_hostname "$hostname")" != "$REDIS_MASTER_HOST" ]]; then + sentinel_conf_add "sentinel known-replica {{ .Values.sentinel.masterSet }} $(get_full_hostname "$hostname") $(get_port "$hostname" "REDIS")" + fi + } + + # Add available hosts on the network as known replicas & sentinels + for node in $(seq 0 $(({{ .Values.replica.replicaCount }}-1))); do + hostname="{{ template "common.names.fullname" . }}-node-$node" + ip="$(getent hosts "$hostname.$HEADLESS_SERVICE" | awk '{ print $1 }')" + add_known_sentinel "$hostname" "$ip" + add_known_replica "$hostname" "$ip" + done + + echo "" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + echo "sentinel announce-hostnames yes" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + echo "sentinel resolve-hostnames yes" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + echo "sentinel announce-port $SERVPORT" >> /opt/bitnami/redis-sentinel/etc/sentinel.conf + echo "sentinel announce-ip $(get_full_hostname "$HOSTNAME")" >> /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-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 }} + {{- if .Values.sentinel.preExecCmds }} + {{ .Values.sentinel.preExecCmds | nindent 4 }} + {{- end }} + exec redis-server /opt/bitnami/redis-sentinel/etc/sentinel.conf --sentinel {{- if .Values.tls.enabled }} "${ARGS[@]}" {{- end }} + prestop-sentinel.sh: | + #!/bin/bash + + . /opt/bitnami/scripts/libvalidations.sh + . /opt/bitnami/scripts/libos.sh + + HEADLESS_SERVICE="{{ template "common.names.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + SENTINEL_SERVICE_ENV_NAME={{ printf "%s%s" (upper (include "common.names.fullname" .)| replace "-" "_") "_SERVICE_PORT_TCP_SENTINEL" }} + SENTINEL_SERVICE_PORT=${!SENTINEL_SERVICE_ENV_NAME} + + get_full_hostname() { + hostname="$1" + + {{- if .Values.useExternalDNS.enabled }} + echo "${hostname}.{{- include "redis.externalDNS.suffix" . }}" + {{- else if eq .Values.sentinel.service.type "NodePort" }} + echo "${hostname}.{{- .Release.Namespace }}" + {{- else }} + echo "${hostname}.${HEADLESS_SERVICE}" + {{- end }} + } + run_sentinel_command() { + if is_boolean_yes "$REDIS_SENTINEL_TLS_ENABLED"; then + redis-cli -h "$REDIS_SERVICE" -p "$SENTINEL_SERVICE_PORT" --tls --cert "$REDIS_SENTINEL_TLS_CERT_FILE" --key "$REDIS_SENTINEL_TLS_KEY_FILE" --cacert "$REDIS_SENTINEL_TLS_CA_FILE" sentinel "$@" + else + redis-cli -h "$REDIS_SERVICE" -p "$SENTINEL_SERVICE_PORT" sentinel "$@" + fi + } + failover_finished() { + REDIS_SENTINEL_INFO=($(run_sentinel_command get-master-addr-by-name "{{ .Values.sentinel.masterSet }}")) + REDIS_MASTER_HOST="${REDIS_SENTINEL_INFO[0]}" + [[ "$REDIS_MASTER_HOST" != "$(get_full_hostname $HOSTNAME)" ]] + } + + REDIS_SERVICE="{{ include "common.names.fullname" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + + {{ if .Values.auth.sentinel -}} + # redis-cli automatically consumes credentials from the REDISCLI_AUTH variable + [[ -n "$REDIS_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_PASSWORD" + [[ -f "$REDIS_PASSWORD_FILE" ]] && export REDISCLI_AUTH="$(< "${REDIS_PASSWORD_FILE}")" + {{- end }} + + if ! failover_finished; then + echo "I am the master pod and you are stopping me. Starting sentinel failover" + # if I am the master, issue a command to failover once and then wait for the failover to finish + run_sentinel_command failover "{{ .Values.sentinel.masterSet }}" + if retry_while "failover_finished" "{{ sub .Values.sentinel.terminationGracePeriodSeconds 10 }}" 1; then + echo "Master has been successfuly failed over to a different pod." + exit 0 + else + echo "Master failover failed" + exit 1 + fi + else + exit 0 + fi + prestop-redis.sh: | + #!/bin/bash + + . /opt/bitnami/scripts/libvalidations.sh + . /opt/bitnami/scripts/libos.sh + + run_redis_command() { + if is_boolean_yes "$REDIS_TLS_ENABLED"; then + redis-cli -h 127.0.0.1 -p "$REDIS_TLS_PORT" --tls --cert "$REDIS_TLS_CERT_FILE" --key "$REDIS_TLS_KEY_FILE" --cacert "$REDIS_TLS_CA_FILE" "$@" + else + redis-cli -h 127.0.0.1 -p ${REDIS_PORT} "$@" + fi + } + failover_finished() { + REDIS_ROLE=$(run_redis_command role | head -1) + [[ "$REDIS_ROLE" != "master" ]] + } + + # redis-cli automatically consumes credentials from the REDISCLI_AUTH variable + [[ -n "$REDIS_PASSWORD" ]] && export REDISCLI_AUTH="$REDIS_PASSWORD" + [[ -f "$REDIS_PASSWORD_FILE" ]] && export REDISCLI_AUTH="$(< "${REDIS_PASSWORD_FILE}")" + + if ! failover_finished; then + echo "Waiting for sentinel to run failover for up to {{ sub .Values.sentinel.terminationGracePeriodSeconds 10 }}s" + retry_while "failover_finished" "{{ sub .Values.sentinel.terminationGracePeriodSeconds 10 }}" 1 + else + exit 0 + fi + +{{- else }} + start-master.sh: | + #!/bin/bash + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + {{- if and .Values.master.containerSecurityContext.runAsUser (eq (.Values.master.containerSecurityContext.runAsUser | int) 0) }} + useradd redis + chown -R redis {{ .Values.master.persistence.path }} + {{- end }} + 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.auth.enabled }} + 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.preExecCmds }} + {{ .Values.master.preExecCmds | nindent 4 }} + {{- end }} + {{- if .Values.master.command }} + exec {{ .Values.master.command }} "${ARGS[@]}" + {{- else }} + exec redis-server "${ARGS[@]}" + {{- end }} + {{- if eq .Values.architecture "replication" }} + start-replica.sh: | + #!/bin/bash + + get_port() { + hostname="$1" + type="$2" + + port_var=$(echo "${hostname^^}_SERVICE_PORT_$type" | sed "s/-/_/g") + port=${!port_var} + + if [ -z "$port" ]; then + case $type in + "SENTINEL") + echo {{ .Values.sentinel.containerPorts.sentinel }} + ;; + "REDIS") + echo {{ .Values.master.containerPorts.redis }} + ;; + esac + else + echo $port + fi + } + + get_full_hostname() { + hostname="$1" + + {{- if .Values.useExternalDNS.enabled }} + echo "${hostname}.{{- include "redis.externalDNS.suffix" . }}" + {{- else if eq .Values.sentinel.service.type "NodePort" }} + echo "${hostname}.{{- .Release.Namespace }}" + {{- else }} + echo "${hostname}.${HEADLESS_SERVICE}" + {{- end }} + } + + REDISPORT=$(get_port "$HOSTNAME" "REDIS") + + [[ -f $REDIS_PASSWORD_FILE ]] && export REDIS_PASSWORD="$(< "${REDIS_PASSWORD_FILE}")" + [[ -f $REDIS_MASTER_PASSWORD_FILE ]] && export REDIS_MASTER_PASSWORD="$(< "${REDIS_MASTER_PASSWORD_FILE}")" + {{- if and .Values.replica.containerSecurityContext.runAsUser (eq (.Values.replica.containerSecurityContext.runAsUser | int) 0) }} + useradd redis + chown -R redis {{ .Values.replica.persistence.path }} + {{- end }} + 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 + + echo "" >> /opt/bitnami/redis/etc/replica.conf + echo "replica-announce-port $REDISPORT" >> /opt/bitnami/redis/etc/replica.conf + echo "replica-announce-ip $(get_full_hostname "$HOSTNAME")" >> /opt/bitnami/redis/etc/replica.conf + + {{- 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+=("--replicaof" "${REDIS_MASTER_HOST}" "${REDIS_MASTER_PORT_NUMBER}") + {{- if .Values.auth.enabled }} + 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.replica.extraFlags }} + {{- range .Values.replica.extraFlags }} + ARGS+=({{ . | quote }}) + {{- end }} + {{- end }} + {{- if .Values.replica.preExecCmds }} + {{ .Values.replica.preExecCmds | nindent 4 }} + {{- end }} + {{- if .Values.replica.command }} + exec {{ .Values.replica.command }} "${ARGS[@]}" + {{- else }} + exec redis-server "${ARGS[@]}" + {{- end }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/secret.yaml b/rds/base/charts/redis/templates/secret.yaml new file mode 100644 index 0000000..e97a727 --- /dev/null +++ b/rds/base/charts/redis/templates/secret.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.auth.enabled (not .Values.auth.existingSecret) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.secretAnnotations .Values.commonAnnotations }} + annotations: + {{- if .Values.secretAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.secretAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +type: Opaque +data: + redis-password: {{ include "redis.password" . | b64enc | quote }} +{{- end -}} diff --git a/rds/base/charts/redis/templates/sentinel/hpa.yaml b/rds/base/charts/redis/templates/sentinel/hpa.yaml new file mode 100644 index 0000000..ef57b5a --- /dev/null +++ b/rds/base/charts/redis/templates/sentinel/hpa.yaml @@ -0,0 +1,47 @@ +{{- if and .Values.replica.autoscaling.enabled .Values.sentinel.enabled }} +apiVersion: {{ include "common.capabilities.hpa.apiVersion" ( dict "context" $ ) }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ printf "%s-node" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: replica + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.commonLabels "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + scaleTargetRef: + apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} + kind: StatefulSet + name: {{ printf "%s-node" (include "common.names.fullname" .) }} + minReplicas: {{ .Values.replica.autoscaling.minReplicas }} + maxReplicas: {{ .Values.replica.autoscaling.maxReplicas }} + metrics: + {{- if .Values.replica.autoscaling.targetCPU }} + - type: Resource + resource: + name: cpu + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.replica.autoscaling.targetCPU }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.replica.autoscaling.targetCPU }} + {{- end }} + {{- end }} + {{- if .Values.replica.autoscaling.targetMemory }} + - type: Resource + resource: + name: memory + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.replica.autoscaling.targetMemory }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.replica.autoscaling.targetMemory }} + {{- end }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/sentinel/node-services.yaml b/rds/base/charts/redis/templates/sentinel/node-services.yaml new file mode 100644 index 0000000..d3e635e --- /dev/null +++ b/rds/base/charts/redis/templates/sentinel/node-services.yaml @@ -0,0 +1,70 @@ +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled (eq .Values.sentinel.service.type "NodePort") (or .Release.IsUpgrade .Values.sentinel.service.nodePorts.redis ) }} + +{{- range $i := until (int .Values.replica.replicaCount) }} + +{{ $portsmap := (lookup "v1" "ConfigMap" $.Release.Namespace (printf "%s-%s" ( include "common.names.fullname" $ ) "ports-configmap")).data }} + +{{ $sentinelport := 0}} +{{ $redisport := 0}} +{{- if $portsmap }} +{{ $sentinelport = index $portsmap (printf "%s-node-%s-%s" (include "common.names.fullname" $) (toString $i) "sentinel") }} +{{ $redisport = index $portsmap (printf "%s-node-%s-%s" (include "common.names.fullname" $) (toString $i) "redis") }} +{{- else }} +{{- end }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" $ }}-node-{{ $i }} + namespace: {{ $.Release.Namespace | quote }} + labels: {{- include "common.labels.standard" $ | nindent 4 }} + app.kubernetes.io/component: node + {{- if $.Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or $.Values.sentinel.service.annotations $.Values.commonAnnotations }} + annotations: + {{- if $.Values.sentinel.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.sentinel.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if $.Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: NodePort + ports: + - name: sentinel + {{- if $.Values.sentinel.service.nodePorts.sentinel }} + nodePort: {{ (add $.Values.sentinel.service.nodePorts.sentinel $i 1) }} + port: {{ (add $.Values.sentinel.service.nodePorts.sentinel $i 1) }} + {{- else }} + nodePort: {{ $sentinelport }} + port: {{ $sentinelport }} + {{- end }} + protocol: TCP + targetPort: {{ $.Values.sentinel.containerPorts.sentinel }} + - name: redis + {{- if $.Values.sentinel.service.nodePorts.redis }} + nodePort: {{ (add $.Values.sentinel.service.nodePorts.redis $i 1) }} + port: {{ (add $.Values.sentinel.service.nodePorts.redis $i 1) }} + {{- else }} + nodePort: {{ $redisport }} + port: {{ $redisport }} + {{- end }} + protocol: TCP + targetPort: {{ $.Values.replica.containerPorts.redis }} + - name: sentinel-internal + nodePort: null + port: {{ $.Values.sentinel.containerPorts.sentinel }} + protocol: TCP + targetPort: {{ $.Values.sentinel.containerPorts.sentinel }} + - name: redis-internal + nodePort: null + port: {{ $.Values.replica.containerPorts.redis }} + protocol: TCP + targetPort: {{ $.Values.replica.containerPorts.redis }} + selector: + statefulset.kubernetes.io/pod-name: {{ template "common.names.fullname" $ }}-node-{{ $i }} +--- +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/sentinel/ports-configmap.yaml b/rds/base/charts/redis/templates/sentinel/ports-configmap.yaml new file mode 100644 index 0000000..5d032db --- /dev/null +++ b/rds/base/charts/redis/templates/sentinel/ports-configmap.yaml @@ -0,0 +1,100 @@ +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled (eq .Values.sentinel.service.type "NodePort") (not .Values.sentinel.service.nodePorts.redis ) }} +{{- /* create a list to keep track of ports we choose to use */}} +{{ $chosenports := (list ) }} + +{{- /* Get list of all used nodeports */}} +{{ $usedports := (list ) }} +{{- range $index, $service := (lookup "v1" "Service" "" "").items }} + {{- range.spec.ports }} + {{- if .nodePort }} + {{- $usedports = (append $usedports .nodePort) }} + {{- end }} + {{- end }} +{{- end }} + +{{- /* +comments that start with # are rendered in the output when you debug, so you can less and search for them +Vars in the comment will be rendered out, so you can check their value this way. +https://helm.sh/docs/chart_best_practices/templates/#comments-yaml-comments-vs-template-comments + +remove the template comments and leave the yaml comments to help debug +*/}} + +{{- /* Sort the list */}} +{{ $usedports = $usedports | sortAlpha }} +#usedports {{ $usedports }} + +{{- /* How many nodeports per service do we want to create, except for the main service which is always two */}} +{{ $numberofPortsPerNodeService := 2 }} + +{{- /* for every nodeport we want, loop though the used ports to get an unused port */}} +{{- range $j := until (int (add (mul (int .Values.replica.replicaCount) $numberofPortsPerNodeService) 2)) }} + {{- /* #j={{ $j }} */}} + {{- $nodeport := (add $j 30000) }} + {{- $nodeportfound := false }} + {{- range $i := $usedports }} + {{- /* #i={{ $i }} + #nodeport={{ $nodeport }} + #usedports={{ $usedports }} */}} + {{- if and (has (toString $nodeport) $usedports) (eq $nodeportfound false) }} + {{- /* nodeport conflicts with in use */}} + {{- $nodeport = (add $nodeport 1) }} + {{- else if and ( has $nodeport $chosenports) (eq $nodeportfound false) }} + {{- /* nodeport already chosen, try another */}} + {{- $nodeport = (add $nodeport 1) }} + {{- else if (eq $nodeportfound false) }} + {{- /* nodeport free to use: not already claimed and not in use */}} + {{- /* select nodeport, and place into usedports */}} + {{- $chosenports = (append $chosenports $nodeport) }} + {{- $nodeportfound = true }} + {{- else }} + {{- /* nodeport has already been chosen and locked in, just work through the rest of the list to get to the next nodeport selection */}} + {{- end }} + {{- end }} + {{- if (eq $nodeportfound false) }} + {{- $chosenports = (append $chosenports $nodeport) }} + {{- end }} + +{{- end }} + +{{- /* print the usedports and chosenports for debugging */}} +#usedports {{ $usedports }} +#chosenports {{ $chosenports }}}} + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "common.names.fullname" . }}-ports-configmap + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{ $portsmap := (lookup "v1" "ConfigMap" $.Release.Namespace (printf "%s-%s" ( include "common.names.fullname" . ) "ports-configmap")).data }} +{{- if $portsmap }} +{{- /* configmap already exists, do not install again */ -}} + {{- range $name, $value := $portsmap }} + "{{ $name }}": "{{ $value }}" + {{- end }} +{{- else }} +{{- /* configmap being set for first time */ -}} + {{- range $index, $port := $chosenports }} + {{- $nodenumber := (floor (div $index 2)) }} + {{- if (eq $index 0) }} + "{{ template "common.names.fullname" $ }}-sentinel": "{{ $port }}" + {{- else if (eq $index 1) }} + "{{ template "common.names.fullname" $ }}-redis": "{{ $port }}" + {{- else if (eq (mod $index 2) 0) }} + "{{ template "common.names.fullname" $ }}-node-{{ (sub $nodenumber 1) }}-sentinel": "{{ $port }}" + {{- else if (eq (mod $index 2) 1) }} + "{{ template "common.names.fullname" $ }}-node-{{ (sub $nodenumber 1) }}-redis": "{{ $port }}" + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/sentinel/service.yaml b/rds/base/charts/redis/templates/sentinel/service.yaml new file mode 100644 index 0000000..f193730 --- /dev/null +++ b/rds/base/charts/redis/templates/sentinel/service.yaml @@ -0,0 +1,103 @@ +{{- if or .Release.IsUpgrade (ne .Values.sentinel.service.type "NodePort") .Values.sentinel.service.nodePorts.redis -}} +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled }} +{{ $portsmap := (lookup "v1" "ConfigMap" $.Release.Namespace (printf "%s-%s" ( include "common.names.fullname" . ) "ports-configmap")).data }} + +{{ $sentinelport := 0}} +{{ $redisport := 0}} +{{- if $portsmap }} +{{ $sentinelport = index $portsmap (printf "%s-%s" (include "common.names.fullname" $) "sentinel") }} +{{ $redisport = index $portsmap (printf "%s-%s" (include "common.names.fullname" $) "redis") }} +{{- else }} +{{- end }} + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: node + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.sentinel.service.annotations .Values.commonAnnotations }} + annotations: + {{- if .Values.sentinel.service.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.service.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +spec: + type: {{ .Values.sentinel.service.type }} + {{- if or (eq .Values.sentinel.service.type "LoadBalancer") (eq .Values.sentinel.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.sentinel.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.sentinel.service.type "LoadBalancer") (not (empty .Values.sentinel.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.sentinel.service.loadBalancerIP }} + {{- end }} + {{- if and (eq .Values.sentinel.service.type "LoadBalancer") (not (empty .Values.sentinel.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.sentinel.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and .Values.sentinel.service.clusterIP (eq .Values.sentinel.service.type "ClusterIP") }} + clusterIP: {{ .Values.sentinel.service.clusterIP }} + {{- end }} + {{- if .Values.sentinel.service.sessionAffinity }} + sessionAffinity: {{ .Values.sentinel.service.sessionAffinity }} + {{- end }} + {{- if .Values.sentinel.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: tcp-redis + {{- if and (or (eq .Values.sentinel.service.type "NodePort") (eq .Values.sentinel.service.type "LoadBalancer")) .Values.sentinel.service.nodePorts.redis }} + port: {{ .Values.sentinel.service.nodePorts.redis }} + {{- else if eq .Values.sentinel.service.type "NodePort" }} + port: {{ $redisport }} + {{- else}} + port: {{ .Values.sentinel.service.ports.redis }} + {{- end }} + targetPort: {{ .Values.replica.containerPorts.redis }} + {{- if and (or (eq .Values.sentinel.service.type "NodePort") (eq .Values.sentinel.service.type "LoadBalancer")) .Values.sentinel.service.nodePorts.redis }} + nodePort: {{ .Values.sentinel.service.nodePorts.redis }} + {{- else if eq .Values.sentinel.service.type "ClusterIP" }} + nodePort: null + {{- else if eq .Values.sentinel.service.type "NodePort" }} + nodePort: {{ $redisport }} + {{- end }} + - name: tcp-sentinel + {{- if and (or (eq .Values.sentinel.service.type "NodePort") (eq .Values.sentinel.service.type "LoadBalancer")) .Values.sentinel.service.nodePorts.sentinel }} + port: {{ .Values.sentinel.service.nodePorts.sentinel }} + {{- else if eq .Values.sentinel.service.type "NodePort" }} + port: {{ $sentinelport }} + {{- else }} + port: {{ .Values.sentinel.service.ports.sentinel }} + {{- end }} + targetPort: {{ .Values.sentinel.containerPorts.sentinel }} + {{- if and (or (eq .Values.sentinel.service.type "NodePort") (eq .Values.sentinel.service.type "LoadBalancer")) .Values.sentinel.service.nodePorts.sentinel }} + nodePort: {{ .Values.sentinel.service.nodePorts.sentinel }} + {{- else if eq .Values.sentinel.service.type "ClusterIP" }} + nodePort: null + {{- else if eq .Values.sentinel.service.type "NodePort" }} + nodePort: {{ $sentinelport }} + {{- end }} + {{- if eq .Values.sentinel.service.type "NodePort" }} + - name: sentinel-internal + nodePort: null + port: {{ .Values.sentinel.containerPorts.sentinel }} + protocol: TCP + targetPort: {{ .Values.sentinel.containerPorts.sentinel }} + - name: redis-internal + nodePort: null + port: {{ .Values.replica.containerPorts.redis }} + protocol: TCP + targetPort: {{ .Values.replica.containerPorts.redis }} + {{- end }} + {{- if .Values.sentinel.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "common.labels.matchLabels" . | nindent 4 }} + app.kubernetes.io/component: node +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/sentinel/statefulset.yaml b/rds/base/charts/redis/templates/sentinel/statefulset.yaml new file mode 100644 index 0000000..6b301c1 --- /dev/null +++ b/rds/base/charts/redis/templates/sentinel/statefulset.yaml @@ -0,0 +1,688 @@ +{{- if or .Release.IsUpgrade (ne .Values.sentinel.service.type "NodePort") .Values.sentinel.service.nodePorts.redis -}} +{{- if and (eq .Values.architecture "replication") .Values.sentinel.enabled }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ printf "%s-node" (include "common.names.fullname" .) }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + app.kubernetes.io/component: node + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replica.replicaCount }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: node + serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) }} + {{- if .Values.replica.updateStrategy }} + updateStrategy: {{- toYaml .Values.replica.updateStrategy | nindent 4 }} + {{- end }} + {{- if .Values.replica.podManagementPolicy }} + podManagementPolicy: {{ .Values.replica.podManagementPolicy | quote }} + {{- end }} + template: + metadata: + labels: {{- include "common.labels.standard" . | nindent 8 }} + app.kubernetes.io/component: node + {{- if .Values.replica.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.podLabels "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podLabels "context" $ ) | nindent 8 }} + {{- end }} + annotations: + {{- if (include "redis.createConfigmap" .) }} + checksum/configmap: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + checksum/health: {{ include (print $.Template.BasePath "/health-configmap.yaml") . | sha256sum }} + checksum/scripts: {{ include (print $.Template.BasePath "/scripts-configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- if .Values.replica.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.podAnnotations "context" $ ) | nindent 8 }} + {{- end }} + spec: + {{- include "redis.imagePullSecrets" . | nindent 6 }} + {{- if .Values.replica.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.replica.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.podSecurityContext.enabled }} + securityContext: {{- omit .Values.replica.podSecurityContext "enabled" | toYaml | nindent 8 }} + {{- end }} + serviceAccountName: {{ template "redis.serviceAccountName" . }} + {{- if .Values.replica.priorityClassName }} + priorityClassName: {{ .Values.replica.priorityClassName | quote }} + {{- end }} + {{- if .Values.replica.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.replica.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.replica.podAffinityPreset "component" "node" "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.replica.podAntiAffinityPreset "component" "node" "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.replica.nodeAffinityPreset.type "key" .Values.replica.nodeAffinityPreset.key "values" .Values.replica.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.replica.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.replica.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.replica.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.replica.topologySpreadConstraints "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.replica.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.replica.shareProcessNamespace }} + {{- end }} + {{- if .Values.replica.schedulerName }} + schedulerName: {{ .Values.replica.schedulerName | quote }} + {{- end }} + {{- if .Values.replica.dnsPolicy }} + dnsPolicy: {{ .Values.replica.dnsPolicy }} + {{- end }} + {{- if .Values.replica.dnsConfig }} + dnsConfig: {{- include "common.tplvalues.render" (dict "value" .Values.replica.dnsConfig "context" $) | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.sentinel.terminationGracePeriodSeconds }} + containers: + - name: redis + image: {{ template "redis.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.replica.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.replica.lifecycleHooks "context" $) | nindent 12 }} + {{- else }} + lifecycle: + preStop: + exec: + command: + - /bin/bash + - -c + - /opt/bitnami/scripts/start-scripts/prestop-redis.sh + {{- end }} + {{- end }} + {{- if .Values.replica.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.replica.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.replica.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.replica.command "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.replica.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.replica.args "context" $) | nindent 12 }} + {{- else }} + args: + - -c + - /opt/bitnami/scripts/start-scripts/start-node.sh + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.image.debug .Values.diagnosticMode.enabled) | quote }} + - name: REDIS_MASTER_PORT_NUMBER + value: {{ .Values.replica.containerPorts.redis | quote }} + - name: ALLOW_EMPTY_PASSWORD + value: {{ ternary "no" "yes" .Values.auth.enabled | quote }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usePasswordFiles }} + - 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 }} + {{- end }} + - name: REDIS_TLS_ENABLED + value: {{ ternary "yes" "no" .Values.tls.enabled | quote }} + {{- if .Values.tls.enabled }} + - name: REDIS_TLS_PORT + value: {{ .Values.replica.containerPorts.redis | 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.replica.containerPorts.redis | quote }} + {{- end }} + - name: REDIS_DATA_DIR + value: {{ .Values.replica.persistence.path }} + {{- if .Values.replica.externalMaster.enabled }} + - name: REDIS_EXTERNAL_MASTER_HOST + value: {{ .Values.replica.externalMaster.host | quote }} + - name: REDIS_EXTERNAL_MASTER_PORT + value: {{ .Values.replica.externalMaster.port | quote }} + {{- end }} + {{- if .Values.replica.extraEnvVars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.extraEnvVars "context" $ ) | nindent 12 }} + {{- end }} + {{- if or .Values.replica.extraEnvVarsCM .Values.replica.extraEnvVarsSecret }} + envFrom: + {{- if .Values.replica.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.replica.extraEnvVarsCM }} + {{- end }} + {{- if .Values.replica.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.replica.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: redis + containerPort: {{ .Values.replica.containerPorts.redis }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.replica.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.replica.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: redis + {{- else if .Values.replica.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.replica.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.replica.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.replica.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.replica.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.replica.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.replica.livenessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_liveness_local.sh {{ .Values.replica.livenessProbe.timeoutSeconds }} + {{- else if .Values.replica.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.replica.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.replica.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.replica.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.replica.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.replica.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.replica.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_readiness_local.sh {{ .Values.replica.readinessProbe.timeoutSeconds }} + {{- else if .Values.replica.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.replica.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.replica.resources }} + resources: {{- toYaml .Values.replica.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: start-scripts + mountPath: /opt/bitnami/scripts/start-scripts + - name: health + mountPath: /health + {{- if .Values.sentinel.persistence.enabled }} + - name: sentinel-data + mountPath: /opt/bitnami/redis-sentinel/etc + {{- end }} + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.replica.persistence.path }} + subPath: {{ .Values.replica.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis/mounted-etc + - name: redis-tmp-conf + mountPath: /opt/bitnami/redis/etc + - name: tmp + mountPath: /tmp + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.replica.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + - name: sentinel + image: {{ template "redis.sentinel.image" . }} + imagePullPolicy: {{ .Values.sentinel.image.pullPolicy | quote }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.sentinel.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.lifecycleHooks "context" $) | nindent 12 }} + {{- else }} + lifecycle: + preStop: + exec: + command: + - /bin/bash + - -c + - /opt/bitnami/scripts/start-scripts/prestop-sentinel.sh + {{- end }} + {{- end }} + {{- if .Values.sentinel.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.sentinel.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.sentinel.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.command "context" $) | nindent 12 }} + {{- else }} + command: + - /bin/bash + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.sentinel.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.args "context" $) | nindent 12 }} + {{- else }} + args: + - -c + - /opt/bitnami/scripts/start-scripts/start-sentinel.sh + {{- end }} + env: + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" (or .Values.sentinel.image.debug .Values.diagnosticMode.enabled) | quote }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usePasswordFiles }} + - 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.containerPorts.sentinel | 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.tls.dhParamsFilename" . }} + {{- end }} + {{- else }} + - name: REDIS_SENTINEL_PORT + value: {{ .Values.sentinel.containerPorts.sentinel | quote }} + {{- end }} + {{- if .Values.sentinel.externalMaster.enabled }} + - name: REDIS_EXTERNAL_MASTER_HOST + value: {{ .Values.sentinel.externalMaster.host | quote }} + - name: REDIS_EXTERNAL_MASTER_PORT + value: {{ .Values.sentinel.externalMaster.port | quote }} + {{- end }} + {{- if .Values.sentinel.extraEnvVars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.extraEnvVars "context" $ ) | nindent 12 }} + {{- end }} + {{- if or .Values.sentinel.extraEnvVarsCM .Values.sentinel.extraEnvVarsSecret }} + envFrom: + {{- if .Values.sentinel.extraEnvVarsCM }} + - configMapRef: + name: {{ .Values.sentinel.extraEnvVarsCM }} + {{- end }} + {{- if .Values.sentinel.extraEnvVarsSecret }} + - secretRef: + name: {{ .Values.sentinel.extraEnvVarsSecret }} + {{- end }} + {{- end }} + ports: + - name: redis-sentinel + containerPort: {{ .Values.sentinel.containerPorts.sentinel }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.sentinel.startupProbe.enabled }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" (omit .Values.sentinel.startupProbe "enabled") "context" $) | nindent 12 }} + tcpSocket: + port: redis-sentinel + {{- else if .Values.sentinel.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.customStartupProbe "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sentinel.livenessProbe.enabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.sentinel.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .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: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.customLivenessProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.sentinel.readinessProbe.enabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.sentinel.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.sentinel.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.sentinel.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.sentinel.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.sentinel.readinessProbe.failureThreshold }} + exec: + command: + - sh + - -c + - /health/ping_sentinel.sh {{ .Values.sentinel.livenessProbe.timeoutSeconds }} + {{- else if .Values.sentinel.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.customReadinessProbe "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.sentinel.resources }} + resources: {{- toYaml .Values.sentinel.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: start-scripts + mountPath: /opt/bitnami/scripts/start-scripts + - name: health + mountPath: /health + - name: sentinel-data + mountPath: /opt/bitnami/redis-sentinel/etc + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /opt/bitnami/redis/secrets/ + {{- end }} + - name: redis-data + mountPath: {{ .Values.replica.persistence.path }} + subPath: {{ .Values.replica.persistence.subPath }} + - name: config + mountPath: /opt/bitnami/redis-sentinel/mounted-etc + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.sentinel.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ template "redis.metrics.image" . }} + imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} + {{- if .Values.metrics.containerSecurityContext.enabled }} + securityContext: {{- omit .Values.metrics.containerSecurityContext "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else }} + 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 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ template "common.names.fullname" . }} + {{- if .Values.auth.enabled }} + - name: REDIS_USER + value: default + {{- if (not .Values.auth.usePasswordFiles) }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "redis.secretName" . }} + key: {{ template "redis.secretPasswordKey" . }} + {{- end }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: REDIS_ADDR + value: rediss://{{ .Values.metrics.redisTargetHost }}:{{ .Values.replica.containerPorts.redis }} + {{- if .Values.tls.authClients }} + - name: REDIS_EXPORTER_TLS_CLIENT_KEY_FILE + value: {{ template "redis.tlsCertKey" . }} + - name: REDIS_EXPORTER_TLS_CLIENT_CERT_FILE + value: {{ template "redis.tlsCert" . }} + {{- end }} + - name: REDIS_EXPORTER_TLS_CA_CERT_FILE + value: {{ template "redis.tlsCACert" . }} + {{- end }} + {{- if .Values.metrics.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + ports: + - name: metrics + containerPort: 9121 + {{- if .Values.metrics.resources }} + resources: {{- toYaml .Values.metrics.resources | nindent 12 }} + {{- end }} + volumeMounts: + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + mountPath: /secrets/ + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + mountPath: /opt/bitnami/redis/certs + readOnly: true + {{- end }} + {{- if .Values.metrics.extraVolumeMounts }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumeMounts "context" $ ) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.replica.sidecars }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.sidecars "context" $) | nindent 8 }} + {{- end }} + {{- $needsVolumePermissions := and .Values.volumePermissions.enabled .Values.replica.persistence.enabled .Values.replica.podSecurityContext.enabled .Values.replica.containerSecurityContext.enabled }} + {{- if or .Values.replica.initContainers $needsVolumePermissions .Values.sysctl.enabled }} + initContainers: + {{- if .Values.replica.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.replica.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- if $needsVolumePermissions }} + - name: volume-permissions + image: {{ include "redis.volumePermissions.image" . }} + imagePullPolicy: {{ .Values.volumePermissions.image.pullPolicy | quote }} + command: + - /bin/bash + - -ec + - | + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + chown -R `id -u`:`id -G | cut -d " " -f2` {{ .Values.replica.persistence.path }} + {{- else }} + chown -R {{ .Values.replica.containerSecurityContext.runAsUser }}:{{ .Values.replica.podSecurityContext.fsGroup }} {{ .Values.replica.persistence.path }} + {{- end }} + {{- if eq ( toString ( .Values.volumePermissions.containerSecurityContext.runAsUser )) "auto" }} + securityContext: {{- omit .Values.volumePermissions.containerSecurityContext "runAsUser" | toYaml | nindent 12 }} + {{- else }} + securityContext: {{- .Values.volumePermissions.containerSecurityContext | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.volumePermissions.resources }} + resources: {{- toYaml .Values.volumePermissions.resources | nindent 12 }} + {{- end }} + volumeMounts: + - name: redis-data + mountPath: {{ .Values.replica.persistence.path }} + subPath: {{ .Values.replica.persistence.subPath }} + {{- end }} + {{- if .Values.sysctl.enabled }} + - name: init-sysctl + image: {{ include "redis.sysctl.image" . }} + imagePullPolicy: {{ default "" .Values.sysctl.image.pullPolicy | quote }} + securityContext: + privileged: true + runAsUser: 0 + {{- if .Values.sysctl.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.sysctl.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.resources }} + resources: {{- toYaml .Values.sysctl.resources | nindent 12 }} + {{- end }} + {{- if .Values.sysctl.mountHostSys }} + volumeMounts: + - name: host-sys + mountPath: /host-sys + {{- end }} + {{- end }} + {{- end }} + volumes: + - name: start-scripts + configMap: + name: {{ printf "%s-scripts" (include "common.names.fullname" .) }} + defaultMode: 0755 + - name: health + configMap: + name: {{ printf "%s-health" (include "common.names.fullname" .) }} + defaultMode: 0755 + {{- if .Values.auth.usePasswordFiles }} + - name: redis-password + secret: + secretName: {{ template "redis.secretName" . }} + items: + - key: {{ template "redis.secretPasswordKey" . }} + path: redis-password + {{- end }} + - name: config + configMap: + name: {{ include "redis.configmapName" . }} + {{- if .Values.sysctl.mountHostSys }} + - name: host-sys + hostPath: + path: /sys + {{- end }} + {{- if not .Values.sentinel.persistence.enabled }} + - name: sentinel-data + {{- if .Values.sentinel.persistence.medium }} + emptyDir: { + medium: {{ .Values.sentinel.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} + - name: redis-tmp-conf + {{- if .Values.replica.persistence.medium }} + emptyDir: { + medium: {{ .Values.replica.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + - name: tmp + {{- if .Values.replica.persistence.medium }} + emptyDir: { + medium: {{ .Values.replica.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.replica.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.replica.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.metrics.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.metrics.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.sentinel.extraVolumes }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.extraVolumes "context" $ ) | nindent 8 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: redis-certificates + secret: + secretName: {{ include "redis.tlsSecretName" . }} + defaultMode: 256 + {{- end }} + {{- if not .Values.replica.persistence.enabled }} + - name: redis-data + {{- if .Values.replica.persistence.medium }} + emptyDir: { + medium: {{ .Values.replica.persistence.medium | quote }} + } + {{- else }} + emptyDir: {} + {{- end }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: redis-data + labels: {{- include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: node + {{- if .Values.replica.persistence.annotations }} + annotations: {{- toYaml .Values.replica.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.replica.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.replica.persistence.size | quote }} + {{- if .Values.replica.persistence.selector }} + selector: {{- include "common.tplvalues.render" ( dict "value" .Values.replica.persistence.selector "context" $) | nindent 10 }} + {{- end }} + {{- include "common.storage.class" (dict "persistence" .Values.replica.persistence "global" .Values.global) | nindent 8 }} + {{- if .Values.sentinel.persistence.enabled }} + - metadata: + name: sentinel-data + labels: {{- include "common.labels.matchLabels" . | nindent 10 }} + app.kubernetes.io/component: node + {{- if .Values.sentinel.persistence.annotations }} + annotations: {{- toYaml .Values.sentinel.persistence.annotations | nindent 10 }} + {{- end }} + spec: + accessModes: + {{- range .Values.sentinel.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.sentinel.persistence.size | quote }} + {{- if .Values.sentinel.persistence.selector }} + selector: {{- include "common.tplvalues.render" ( dict "value" .Values.sentinel.persistence.selector "context" $) | nindent 10 }} + {{- end }} + {{- if .Values.sentinel.persistence.dataSource }} + dataSource: {{- include "common.tplvalues.render" (dict "value" .Values.sentinel.persistence.dataSource "context" $) | nindent 10 }} + {{- end }} + {{- include "common.storage.class" (dict "persistence" .Values.sentinel.persistence "global" .Values.global) | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/serviceaccount.yaml b/rds/base/charts/redis/templates/serviceaccount.yaml new file mode 100644 index 0000000..b3e59d9 --- /dev/null +++ b/rds/base/charts/redis/templates/serviceaccount.yaml @@ -0,0 +1,21 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +metadata: + name: {{ template "redis.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if or .Values.commonAnnotations .Values.serviceAccount.annotations }} + annotations: + {{- if or .Values.commonAnnotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.serviceAccount.annotations }} + {{- include "common.tplvalues.render" ( dict "value" .Values.serviceAccount.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/rds/base/charts/redis/templates/servicemonitor.yaml b/rds/base/charts/redis/templates/servicemonitor.yaml new file mode 100644 index 0000000..c3bf13d --- /dev/null +++ b/rds/base/charts/redis/templates/servicemonitor.yaml @@ -0,0 +1,41 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ .Release.Namespace .Values.metrics.serviceMonitor.namespace | quote }} + 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 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "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 }} + {{- if .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ .Values.metrics.serviceMonitor.honorLabels }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.relabellings }} + relabelings: {{- toYaml .Values.metrics.serviceMonitor.relabellings | nindent 6 }} + {{- end }} + {{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: {{- toYaml .Values.metrics.serviceMonitor.metricRelabelings | nindent 6 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: {{- include "common.labels.matchLabels" . | nindent 6 }} + app.kubernetes.io/component: metrics +{{- end }} diff --git a/rds/base/charts/redis/templates/tls-secret.yaml b/rds/base/charts/redis/templates/tls-secret.yaml new file mode 100644 index 0000000..5afd4ef --- /dev/null +++ b/rds/base/charts/redis/templates/tls-secret.yaml @@ -0,0 +1,29 @@ +{{- if (include "redis.createTlsSecret" .) }} +{{- $secretName := printf "%s-crt" (include "common.names.fullname" .) }} +{{- $existingCerts := (lookup "v1" "Secret" .Release.Namespace $secretName).data | default dict }} +{{- $ca := genCA "redis-ca" 365 }} +{{- $releaseNamespace := .Release.Namespace }} +{{- $clusterDomain := .Values.clusterDomain }} +{{- $fullname := include "common.names.fullname" . }} +{{- $serviceName := include "common.names.fullname" . }} +{{- $headlessServiceName := printf "%s-headless" (include "common.names.fullname" .) }} +{{- $altNames := list (printf "*.%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $serviceName $releaseNamespace $clusterDomain) (printf "*.%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) (printf "%s.%s.svc.%s" $headlessServiceName $releaseNamespace $clusterDomain) "127.0.0.1" "localhost" $fullname }} +{{- $crt := genSignedCert $fullname nil $altNames 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ .Release.Namespace }} + labels: {{- include "common.labels.standard" . | nindent 4 }} + {{- if .Values.commonLabels }} + {{- include "common.tplvalues.render" ( dict "value" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + ca.crt: {{ (get $existingCerts "ca.crt") | default ($ca.Cert | b64enc | quote ) }} + tls.crt: {{ (get $existingCerts "tls.crt") | default ($crt.Cert | b64enc | quote) }} + tls.key: {{ (get $existingCerts "tls.key") | default ($crt.Key | b64enc | quote) }} +{{- end }} diff --git a/rds/base/charts/redis/values.schema.json b/rds/base/charts/redis/values.schema.json new file mode 100644 index 0000000..d6e226b --- /dev/null +++ b/rds/base/charts/redis/values.schema.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "architecture": { + "type": "string", + "title": "Redis architecture", + "form": true, + "description": "Allowed values: `standalone` or `replication`", + "enum": ["standalone", "replication"] + }, + "auth": { + "type": "object", + "title": "Authentication configuration", + "form": true, + "properties": { + "enabled": { + "type": "boolean", + "form": true, + "title": "Use password authentication" + }, + "password": { + "type": "string", + "title": "Redis password", + "form": true, + "description": "Defaults to a random 10-character alphanumeric string if not set", + "hidden": { + "value": false, + "path": "auth/enabled" + } + } + } + }, + "master": { + "type": "object", + "title": "Master replicas settings", + "form": true, + "properties": { + "kind": { + "type": "string", + "title": "Workload Kind", + "form": true, + "description": "Allowed values: `Deployment` or `StatefulSet`", + "enum": ["Deployment", "StatefulSet"] + }, + "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" + } + } + } + } + } + }, + "replica": { + "type": "object", + "title": "Redis replicas settings", + "form": true, + "hidden": { + "value": "standalone", + "path": "architecture" + }, + "properties": { + "replicaCount": { + "type": "integer", + "form": true, + "title": "Number of Redis replicas" + }, + "persistence": { + "type": "object", + "title": "Persistence for Redis 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": "replica/persistence/enabled" + } + } + } + } + } + }, + "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/rds/base/charts/redis/values.yaml b/rds/base/charts/redis/values.yaml new file mode 100644 index 0000000..c25a3b2 --- /dev/null +++ b/rds/base/charts/redis/values.yaml @@ -0,0 +1,1621 @@ +## @section Global parameters +## 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, imagePullSecrets and storageClass +## + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.storageClass Global StorageClass for Persistent Volume(s) +## @param global.redis.password Global Redis® password (overrides `auth.password`) +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + storageClass: "" + redis: + password: "" + +## @section Common parameters +## + +## @param kubeVersion Override Kubernetes version +## +kubeVersion: "" +## @param nameOverride String to partially override common.names.fullname +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname +## +fullnameOverride: "" +## @param commonLabels Labels to add to all deployed objects +## +commonLabels: {} +## @param commonAnnotations Annotations to add to all deployed objects +## +commonAnnotations: {} +## @param secretAnnotations Annotations to add to secret +## +secretAnnotations: {} +## @param clusterDomain Kubernetes cluster domain name +## +clusterDomain: cluster.local +## @param extraDeploy Array of extra objects to deploy with the release +## +extraDeploy: [] + +## Enable diagnostic mode in the deployment +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the deployment + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the deployment + ## + args: + - infinity + +## @section Redis® Image parameters +## + +## Bitnami Redis® image +## ref: https://hub.docker.com/r/bitnami/redis/tags/ +## @param image.registry Redis® image registry +## @param image.repository Redis® image repository +## @param image.tag Redis® image tag (immutable tags are recommended) +## @param image.pullPolicy Redis® image pull policy +## @param image.pullSecrets Redis® image pull secrets +## @param image.debug Enable image debug mode +## +image: + registry: docker.io + repository: bitnami/redis + tag: 6.2.7-debian-11-r11 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Enable debug mode + ## + debug: false + +## @section Redis® common configuration parameters +## https://github.com/bitnami/bitnami-docker-redis#configuration +## + +## @param architecture Redis® architecture. Allowed values: `standalone` or `replication` +## +architecture: replication +## Redis® Authentication parameters +## ref: https://github.com/bitnami/bitnami-docker-redis#setting-the-server-password-on-first-run +## +auth: + ## @param auth.enabled Enable password authentication + ## + enabled: true + ## @param auth.sentinel Enable password authentication on sentinels too + ## + sentinel: true + ## @param auth.password Redis® password + ## Defaults to a random 10-character alphanumeric string if not set + ## + password: "" + ## @param auth.existingSecret The name of an existing secret with Redis® credentials + ## NOTE: When it's set, the previous `auth.password` parameter is ignored + ## + existingSecret: "" + ## @param auth.existingSecretPasswordKey Password key to be retrieved from existing secret + ## NOTE: ignored unless `auth.existingSecret` parameter is set + ## + existingSecretPasswordKey: "" + ## @param auth.usePasswordFiles Mount credentials as files instead of using an environment variable + ## + usePasswordFiles: false + +## @param commonConfiguration [string] Common configuration to be added into the ConfigMap +## ref: https://redis.io/topics/config +## +commonConfiguration: |- + # Enable AOF https://redis.io/topics/persistence#append-only-file + appendonly yes + # Disable RDB persistence, AOF persistence already enabled. + save "" +## @param existingConfigmap The name of an existing ConfigMap with your custom configuration for Redis® nodes +## +existingConfigmap: "" + +## @section Redis® master configuration parameters +## + +master: + ## @param master.count Number of Redis® master instances to deploy (experimental, requires additional configuration) + ## + count: 1 + ## @param master.configuration Configuration for Redis® master nodes + ## ref: https://redis.io/topics/config + ## + configuration: "" + ## @param master.disableCommands Array with Redis® commands to disable on master nodes + ## 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 + ## @param master.command Override default container command (useful when using custom images) + ## + command: [] + ## @param master.args Override default container args (useful when using custom images) + ## + args: [] + ## @param master.preExecCmds Additional commands to run prior to starting Redis® master + ## + preExecCmds: [] + ## @param master.extraFlags Array with additional command line flags for Redis® master + ## e.g: + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + ## + extraFlags: [] + ## @param master.extraEnvVars Array with extra environment variables to add to Redis® master nodes + ## e.g: + ## extraEnvVars: + ## - name: FOO + ## value: "bar" + ## + extraEnvVars: [] + ## @param master.extraEnvVarsCM Name of existing ConfigMap containing extra env vars for Redis® master nodes + ## + extraEnvVarsCM: "" + ## @param master.extraEnvVarsSecret Name of existing Secret containing extra env vars for Redis® master nodes + ## + extraEnvVarsSecret: "" + ## @param master.containerPorts.redis Container port to open on Redis® master nodes + ## + containerPorts: + redis: 6379 + ## Configure extra options for Redis® containers' liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param master.startupProbe.enabled Enable startupProbe on Redis® master nodes + ## @param master.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param master.startupProbe.periodSeconds Period seconds for startupProbe + ## @param master.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param master.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param master.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: false + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + ## @param master.livenessProbe.enabled Enable livenessProbe on Redis® master nodes + ## @param master.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param master.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param master.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param master.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param master.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + ## @param master.readinessProbe.enabled Enable readinessProbe on Redis® master nodes + ## @param master.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param master.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param master.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param master.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param master.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## @param master.customStartupProbe Custom startupProbe that overrides the default one + ## + customStartupProbe: {} + ## @param master.customLivenessProbe Custom livenessProbe that overrides the default one + ## + customLivenessProbe: {} + ## @param master.customReadinessProbe Custom readinessProbe that overrides the default one + ## + customReadinessProbe: {} + ## Redis® master resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param master.resources.limits The resources limits for the Redis® master containers + ## @param master.resources.requests The requested resources for the Redis® master containers + ## + resources: + limits: {} + requests: {} + ## Configure Pods Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param master.podSecurityContext.enabled Enabled Redis® master pods' Security Context + ## @param master.podSecurityContext.fsGroup Set Redis® master pod's Security Context fsGroup + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param master.containerSecurityContext.enabled Enabled Redis® master containers' Security Context + ## @param master.containerSecurityContext.runAsUser Set Redis® master containers' Security Context runAsUser + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## @param master.kind Use either Deployment or StatefulSet (default) + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ + ## + kind: StatefulSet + ## @param master.schedulerName Alternate scheduler for Redis® master pods + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + schedulerName: "" + ## @param master.updateStrategy.type Redis® master statefulset strategy type + ## @skip master.updateStrategy.rollingUpdate + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + ## + updateStrategy: + ## StrategyType + ## Can be set to RollingUpdate or OnDelete + ## + type: RollingUpdate + rollingUpdate: {} + ## @param master.priorityClassName Redis® master pods' priorityClassName + ## + priorityClassName: "" + ## @param master.hostAliases Redis® master pods host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param master.podLabels Extra labels for Redis® master pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param master.podAnnotations Annotations for Redis® master pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param master.shareProcessNamespace Share a single process namespace between all of the containers in Redis® master pods + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + ## + shareProcessNamespace: false + ## @param master.podAffinityPreset Pod affinity preset. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param master.podAntiAffinityPreset Pod anti-affinity preset. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## Node master.affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param master.nodeAffinityPreset.type Node affinity preset type. Ignored if `master.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param master.nodeAffinityPreset.key Node label key to match. Ignored if `master.affinity` is set + ## + key: "" + ## @param master.nodeAffinityPreset.values Node label values to match. Ignored if `master.affinity` is set + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param master.affinity Affinity for Redis® master pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## NOTE: `master.podAffinityPreset`, `master.podAntiAffinityPreset`, and `master.nodeAffinityPreset` will be ignored when it's set + ## + affinity: {} + ## @param master.nodeSelector Node labels for Redis® master pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param master.tolerations Tolerations for Redis® master pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param master.topologySpreadConstraints Spread Constraints for Redis® master pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## E.g. + ## topologySpreadConstraints: + ## - maxSkew: 1 + ## topologyKey: node + ## whenUnsatisfiable: DoNotSchedule + ## + topologySpreadConstraints: [] + ## @param master.dnsPolicy DNS Policy for Redis® master pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ + ## E.g. + ## dnsPolicy: ClusterFirst + dnsPolicy: "" + ## @param master.dnsConfig DNS Configuration for Redis® master pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ + ## E.g. + ## dnsConfig: + ## options: + ## - name: ndots + ## value: "4" + ## - name: single-request-reopen + dnsConfig: {} + ## @param master.lifecycleHooks for the Redis® master container(s) to automate configuration before or after startup + ## + lifecycleHooks: {} + ## @param master.extraVolumes Optionally specify extra list of additional volumes for the Redis® master pod(s) + ## + extraVolumes: [] + ## @param master.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Redis® master container(s) + ## + extraVolumeMounts: [] + ## @param master.sidecars Add additional sidecar containers to the Redis® master pod(s) + ## e.g: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## @param master.initContainers Add additional init containers to the Redis® master pod(s) + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + ## e.g: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## command: ['sh', '-c', 'echo "hello world"'] + ## + initContainers: [] + ## Persistence parameters + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + ## @param master.persistence.enabled Enable persistence on Redis® master nodes using Persistent Volume Claims + ## + enabled: true + ## @param master.persistence.medium Provide a medium for `emptyDir` volumes. + ## + medium: "" + ## @param master.persistence.sizeLimit Set this to enable a size limit for `emptyDir` volumes. + ## + sizeLimit: "" + ## @param master.persistence.path The path the volume will be mounted at on Redis® master containers + ## NOTE: Useful when using different Redis® images + ## + path: /data + ## @param master.persistence.subPath The subdirectory of the volume to mount on Redis® master containers + ## NOTE: Useful in dev environments + ## + subPath: "" + ## @param master.persistence.storageClass 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 + ## + storageClass: "" + ## @param master.persistence.accessModes Persistent Volume access modes + ## + accessModes: + - ReadWriteOnce + ## @param master.persistence.size Persistent Volume size + ## + size: 8Gi + ## @param master.persistence.annotations Additional custom annotations for the PVC + ## + annotations: {} + ## @param master.persistence.selector Additional labels to match for the PVC + ## e.g: + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param master.persistence.dataSource Custom PVC data source + ## + dataSource: {} + ## @param master.persistence.existingClaim Use a existing PVC which must be created manually before bound + ## NOTE: requires master.persistence.enabled: true + ## + existingClaim: "" + ## Redis® master service parameters + ## + service: + ## @param master.service.type Redis® master service type + ## + type: ClusterIP + ## @param master.service.ports.redis Redis® master service port + ## + ports: + redis: 6379 + ## @param master.service.nodePorts.redis Node port for Redis® master + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## NOTE: choose port between <30000-32767> + ## + nodePorts: + redis: "" + ## @param master.service.externalTrafficPolicy Redis® master service external traffic policy + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param master.service.extraPorts Extra ports to expose (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param master.service.internalTrafficPolicy Redis® master service internal traffic policy (requires Kubernetes v1.22 or greater to be usable) + ## ref: https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/ + ## + internalTrafficPolicy: Cluster + ## @param master.service.clusterIP Redis® master service Cluster IP + ## + clusterIP: "" + ## @param master.service.loadBalancerIP Redis® master service Load Balancer IP + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param master.service.loadBalancerSourceRanges Redis® master service Load Balancer sources + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param master.service.annotations Additional custom annotations for Redis® master service + ## + annotations: {} + ## @param master.service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP" + ## If "ClientIP", consecutive client requests will be directed to the same Pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param master.service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param master.terminationGracePeriodSeconds Integer setting the termination grace period for the redis-master pods + ## + terminationGracePeriodSeconds: 30 + +## @section Redis® replicas configuration parameters +## + +replica: + ## @param replica.replicaCount Number of Redis® replicas to deploy + ## + replicaCount: 3 + ## @param replica.configuration Configuration for Redis® replicas nodes + ## ref: https://redis.io/topics/config + ## + configuration: "" + ## @param replica.disableCommands Array with Redis® commands to disable on replicas nodes + ## 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 + ## @param replica.command Override default container command (useful when using custom images) + ## + command: [] + ## @param replica.args Override default container args (useful when using custom images) + ## + args: [] + ## @param replica.preExecCmds Additional commands to run prior to starting Redis® replicas + ## + preExecCmds: [] + ## @param replica.extraFlags Array with additional command line flags for Redis® replicas + ## e.g: + ## extraFlags: + ## - "--maxmemory-policy volatile-ttl" + ## - "--repl-backlog-size 1024mb" + ## + extraFlags: [] + ## @param replica.extraEnvVars Array with extra environment variables to add to Redis® replicas nodes + ## e.g: + ## extraEnvVars: + ## - name: FOO + ## value: "bar" + ## + extraEnvVars: [] + ## @param replica.extraEnvVarsCM Name of existing ConfigMap containing extra env vars for Redis® replicas nodes + ## + extraEnvVarsCM: "" + ## @param replica.extraEnvVarsSecret Name of existing Secret containing extra env vars for Redis® replicas nodes + ## + extraEnvVarsSecret: "" + ## @param replica.externalMaster.enabled Use external master for bootstrapping + ## @param replica.externalMaster.host External master host to bootstrap from + ## @param replica.externalMaster.port Port for Redis service external master host + ## + externalMaster: + enabled: false + host: "" + port: 6379 + ## @param replica.containerPorts.redis Container port to open on Redis® replicas nodes + ## + containerPorts: + redis: 6379 + ## Configure extra options for Redis® containers' liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param replica.startupProbe.enabled Enable startupProbe on Redis® replicas nodes + ## @param replica.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param replica.startupProbe.periodSeconds Period seconds for startupProbe + ## @param replica.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param replica.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param replica.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 22 + ## @param replica.livenessProbe.enabled Enable livenessProbe on Redis® replicas nodes + ## @param replica.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param replica.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param replica.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param replica.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param replica.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + ## @param replica.readinessProbe.enabled Enable readinessProbe on Redis® replicas nodes + ## @param replica.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param replica.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param replica.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param replica.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param replica.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## @param replica.customStartupProbe Custom startupProbe that overrides the default one + ## + customStartupProbe: {} + ## @param replica.customLivenessProbe Custom livenessProbe that overrides the default one + ## + customLivenessProbe: {} + ## @param replica.customReadinessProbe Custom readinessProbe that overrides the default one + ## + customReadinessProbe: {} + ## Redis® replicas resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param replica.resources.limits The resources limits for the Redis® replicas containers + ## @param replica.resources.requests The requested resources for the Redis® replicas containers + ## + 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: 250m + # memory: 256Mi + requests: {} + # cpu: 250m + # memory: 256Mi + ## Configure Pods Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param replica.podSecurityContext.enabled Enabled Redis® replicas pods' Security Context + ## @param replica.podSecurityContext.fsGroup Set Redis® replicas pod's Security Context fsGroup + ## + podSecurityContext: + enabled: true + fsGroup: 1001 + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param replica.containerSecurityContext.enabled Enabled Redis® replicas containers' Security Context + ## @param replica.containerSecurityContext.runAsUser Set Redis® replicas containers' Security Context runAsUser + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## @param replica.schedulerName Alternate scheduler for Redis® replicas pods + ## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ + ## + schedulerName: "" + ## @param replica.updateStrategy.type Redis® replicas statefulset strategy type + ## @skip replica.updateStrategy.rollingUpdate + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + ## + updateStrategy: + ## StrategyType + ## Can be set to RollingUpdate or OnDelete + ## + type: RollingUpdate + rollingUpdate: {} + ## @param replica.priorityClassName Redis® replicas pods' priorityClassName + ## + priorityClassName: "" + ## @param replica.podManagementPolicy podManagementPolicy to manage scaling operation of %%MAIN_CONTAINER_NAME%% pods + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies + ## + podManagementPolicy: "" + ## @param replica.hostAliases Redis® replicas pods host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## @param replica.podLabels Extra labels for Redis® replicas pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param replica.podAnnotations Annotations for Redis® replicas pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param replica.shareProcessNamespace Share a single process namespace between all of the containers in Redis® replicas pods + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ + ## + shareProcessNamespace: false + ## @param replica.podAffinityPreset Pod affinity preset. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAffinityPreset: "" + ## @param replica.podAntiAffinityPreset Pod anti-affinity preset. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity + ## + podAntiAffinityPreset: soft + ## Node affinity preset + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + ## + nodeAffinityPreset: + ## @param replica.nodeAffinityPreset.type Node affinity preset type. Ignored if `replica.affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param replica.nodeAffinityPreset.key Node label key to match. Ignored if `replica.affinity` is set + ## + key: "" + ## @param replica.nodeAffinityPreset.values Node label values to match. Ignored if `replica.affinity` is set + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] + ## @param replica.affinity Affinity for Redis® replicas pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## NOTE: `replica.podAffinityPreset`, `replica.podAntiAffinityPreset`, and `replica.nodeAffinityPreset` will be ignored when it's set + ## + affinity: {} + ## @param replica.nodeSelector Node labels for Redis® replicas pods assignment + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + ## @param replica.tolerations Tolerations for Redis® replicas pods assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + ## @param replica.topologySpreadConstraints Spread Constraints for Redis® replicas pod assignment + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + ## E.g. + ## topologySpreadConstraints: + ## - maxSkew: 1 + ## topologyKey: node + ## whenUnsatisfiable: DoNotSchedule + ## + topologySpreadConstraints: [] + ## @param replica.dnsPolicy DNS Policy for Redis® replica pods + ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ + ## E.g. + ## dnsPolicy: ClusterFirst + dnsPolicy: "" + ## @param replica.dnsConfig DNS Configuration for Redis® replica pods + ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ + ## E.g. + ## dnsConfig: + ## options: + ## - name: ndots + ## value: "4" + ## - name: single-request-reopen + dnsConfig: {} + ## @param replica.lifecycleHooks for the Redis® replica container(s) to automate configuration before or after startup + ## + lifecycleHooks: {} + ## @param replica.extraVolumes Optionally specify extra list of additional volumes for the Redis® replicas pod(s) + ## + extraVolumes: [] + ## @param replica.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Redis® replicas container(s) + ## + extraVolumeMounts: [] + ## @param replica.sidecars Add additional sidecar containers to the Redis® replicas pod(s) + ## e.g: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## @param replica.initContainers Add additional init containers to the Redis® replicas pod(s) + ## ref: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + ## e.g: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## command: ['sh', '-c', 'echo "hello world"'] + ## + initContainers: [] + ## Persistence Parameters + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + ## @param replica.persistence.enabled Enable persistence on Redis® replicas nodes using Persistent Volume Claims + ## + enabled: true + ## @param replica.persistence.medium Provide a medium for `emptyDir` volumes. + ## + medium: "" + ## @param replica.persistence.sizeLimit Set this to enable a size limit for `emptyDir` volumes. + ## + sizeLimit: "" + ## @param replica.persistence.path The path the volume will be mounted at on Redis® replicas containers + ## NOTE: Useful when using different Redis® images + ## + path: /data + ## @param replica.persistence.subPath The subdirectory of the volume to mount on Redis® replicas containers + ## NOTE: Useful in dev environments + ## + subPath: "" + ## @param replica.persistence.storageClass 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 + ## + storageClass: "" + ## @param replica.persistence.accessModes Persistent Volume access modes + ## + accessModes: + - ReadWriteOnce + ## @param replica.persistence.size Persistent Volume size + ## + size: 8Gi + ## @param replica.persistence.annotations Additional custom annotations for the PVC + ## + annotations: {} + ## @param replica.persistence.selector Additional labels to match for the PVC + ## e.g: + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param replica.persistence.dataSource Custom PVC data source + ## + dataSource: {} + ## @param replica.persistence.existingClaim Use a existing PVC which must be created manually before bound + ## NOTE: requires replica.persistence.enabled: true + ## + existingClaim: "" + ## Redis® replicas service parameters + ## + service: + ## @param replica.service.type Redis® replicas service type + ## + type: ClusterIP + ## @param replica.service.ports.redis Redis® replicas service port + ## + ports: + redis: 6379 + ## @param replica.service.nodePorts.redis Node port for Redis® replicas + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## NOTE: choose port between <30000-32767> + ## + nodePorts: + redis: "" + ## @param replica.service.externalTrafficPolicy Redis® replicas service external traffic policy + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param replica.service.internalTrafficPolicy Redis® replicas service internal traffic policy (requires Kubernetes v1.22 or greater to be usable) + ## ref: https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/ + ## + internalTrafficPolicy: Cluster + ## @param replica.service.extraPorts Extra ports to expose (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param replica.service.clusterIP Redis® replicas service Cluster IP + ## + clusterIP: "" + ## @param replica.service.loadBalancerIP Redis® replicas service Load Balancer IP + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param replica.service.loadBalancerSourceRanges Redis® replicas service Load Balancer sources + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param replica.service.annotations Additional custom annotations for Redis® replicas service + ## + annotations: {} + ## @param replica.service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP" + ## If "ClientIP", consecutive client requests will be directed to the same Pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param replica.service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param replica.terminationGracePeriodSeconds Integer setting the termination grace period for the redis-replicas pods + ## + terminationGracePeriodSeconds: 30 + ## Autoscaling configuration + ## + autoscaling: + ## @param replica.autoscaling.enabled Enable replica autoscaling settings + ## + enabled: false + ## @param replica.autoscaling.minReplicas Minimum replicas for the pod autoscaling + ## + minReplicas: 1 + ## @param replica.autoscaling.maxReplicas Maximum replicas for the pod autoscaling + ## + maxReplicas: 11 + ## @param replica.autoscaling.targetCPU Percentage of CPU to consider when autoscaling + ## + targetCPU: "" + ## @param replica.autoscaling.targetMemory Percentage of Memory to consider when autoscaling + ## + targetMemory: "" + +## @section Redis® Sentinel configuration parameters +## + +sentinel: + ## @param sentinel.enabled Use Redis® Sentinel on Redis® pods. + ## IMPORTANT: this will disable the master and replicas services and + ## create a single Redis® service exposing both the Redis and Sentinel ports + ## + enabled: false + ## Bitnami Redis® Sentinel image version + ## ref: https://hub.docker.com/r/bitnami/redis-sentinel/tags/ + ## @param sentinel.image.registry Redis® Sentinel image registry + ## @param sentinel.image.repository Redis® Sentinel image repository + ## @param sentinel.image.tag Redis® Sentinel image tag (immutable tags are recommended) + ## @param sentinel.image.pullPolicy Redis® Sentinel image pull policy + ## @param sentinel.image.pullSecrets Redis® Sentinel image pull secrets + ## @param sentinel.image.debug Enable image debug mode + ## + image: + registry: docker.io + repository: bitnami/redis-sentinel + tag: 6.2.7-debian-11-r12 + ## Specify a imagePullPolicy + ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' + ## ref: https://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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Enable debug mode + ## + debug: false + ## @param sentinel.masterSet Master set name + ## + masterSet: mymaster + ## @param sentinel.quorum Sentinel Quorum + ## + quorum: 2 + ## @param sentinel.getMasterTimeout Amount of time to allow before get_sentinel_master_info() times out. + ## NOTE: This is directly related to the startupProbes which are configured to run every 10 seconds for a total of 22 failures. If adjusting this value, also adjust the startupProbes. + getMasterTimeout: 220 + ## @param sentinel.automateClusterRecovery Automate cluster recovery in cases where the last replica is not considered a good replica and Sentinel won't automatically failover to it. + ## This also prevents any new replica from starting until the last remaining replica is elected as master to guarantee that it is the one to be elected by Sentinel, and not a newly started replica with no data. + ## NOTE: This feature requires a "downAfterMilliseconds" value less or equal to 2000. + ## + automateClusterRecovery: false + ## Sentinel timing restrictions + ## @param sentinel.downAfterMilliseconds Timeout for detecting a Redis® node is down + ## @param sentinel.failoverTimeout Timeout for performing a election failover + ## + downAfterMilliseconds: 60000 + failoverTimeout: 18000 + ## @param sentinel.parallelSyncs Number of replicas that can be reconfigured in parallel to use the new master after a failover + ## + parallelSyncs: 1 + ## @param sentinel.configuration Configuration for Redis® Sentinel nodes + ## ref: https://redis.io/topics/sentinel + ## + configuration: "" + ## @param sentinel.command Override default container command (useful when using custom images) + ## + command: [] + ## @param sentinel.args Override default container args (useful when using custom images) + ## + args: [] + ## @param sentinel.preExecCmds Additional commands to run prior to starting Redis® Sentinel + ## + preExecCmds: [] + ## @param sentinel.extraEnvVars Array with extra environment variables to add to Redis® Sentinel nodes + ## e.g: + ## extraEnvVars: + ## - name: FOO + ## value: "bar" + ## + extraEnvVars: [] + ## @param sentinel.extraEnvVarsCM Name of existing ConfigMap containing extra env vars for Redis® Sentinel nodes + ## + extraEnvVarsCM: "" + ## @param sentinel.extraEnvVarsSecret Name of existing Secret containing extra env vars for Redis® Sentinel nodes + ## + extraEnvVarsSecret: "" + ## @param sentinel.externalMaster.enabled Use external master for bootstrapping + ## @param sentinel.externalMaster.host External master host to bootstrap from + ## @param sentinel.externalMaster.port Port for Redis service external master host + ## + externalMaster: + enabled: false + host: "" + port: 6379 + ## @param sentinel.containerPorts.sentinel Container port to open on Redis® Sentinel nodes + ## + containerPorts: + sentinel: 26379 + ## Configure extra options for Redis® containers' liveness and readiness probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + ## @param sentinel.startupProbe.enabled Enable startupProbe on Redis® Sentinel nodes + ## @param sentinel.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param sentinel.startupProbe.periodSeconds Period seconds for startupProbe + ## @param sentinel.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param sentinel.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param sentinel.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 22 + ## @param sentinel.livenessProbe.enabled Enable livenessProbe on Redis® Sentinel nodes + ## @param sentinel.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param sentinel.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param sentinel.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param sentinel.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param sentinel.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 5 + ## @param sentinel.readinessProbe.enabled Enable readinessProbe on Redis® Sentinel nodes + ## @param sentinel.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param sentinel.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param sentinel.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param sentinel.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param sentinel.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 5 + ## @param sentinel.customStartupProbe Custom startupProbe that overrides the default one + ## + customStartupProbe: {} + ## @param sentinel.customLivenessProbe Custom livenessProbe that overrides the default one + ## + customLivenessProbe: {} + ## @param sentinel.customReadinessProbe Custom readinessProbe that overrides the default one + ## + customReadinessProbe: {} + ## Persistence parameters + ## ref: https://kubernetes.io/docs/user-guide/persistent-volumes/ + ## + persistence: + ## @param sentinel.persistence.enabled Enable persistence on Redis® sentinel nodes using Persistent Volume Claims (Experimental) + ## + enabled: false + ## @param sentinel.persistence.storageClass 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 + ## + storageClass: "" + ## @param sentinel.persistence.accessModes Persistent Volume access modes + ## + accessModes: + - ReadWriteOnce + ## @param sentinel.persistence.size Persistent Volume size + ## + size: 100Mi + ## @param sentinel.persistence.annotations Additional custom annotations for the PVC + ## + annotations: {} + ## @param sentinel.persistence.selector Additional labels to match for the PVC + ## e.g: + ## selector: + ## matchLabels: + ## app: my-app + ## + selector: {} + ## @param sentinel.persistence.dataSource Custom PVC data source + ## + dataSource: {} + ## @param sentinel.persistence.medium Provide a medium for `emptyDir` volumes. + ## + medium: "" + ## Redis® Sentinel resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param sentinel.resources.limits The resources limits for the Redis® Sentinel containers + ## @param sentinel.resources.requests The requested resources for the Redis® Sentinel containers + ## + resources: + limits: {} + requests: {} + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param sentinel.containerSecurityContext.enabled Enabled Redis® Sentinel containers' Security Context + ## @param sentinel.containerSecurityContext.runAsUser Set Redis® Sentinel containers' Security Context runAsUser + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## @param sentinel.lifecycleHooks for the Redis® sentinel container(s) to automate configuration before or after startup + ## + lifecycleHooks: {} + ## @param sentinel.extraVolumes Optionally specify extra list of additional volumes for the Redis® Sentinel + ## + extraVolumes: [] + ## @param sentinel.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Redis® Sentinel container(s) + ## + extraVolumeMounts: [] + ## Redis® Sentinel service parameters + ## + service: + ## @param sentinel.service.type Redis® Sentinel service type + ## + type: ClusterIP + ## @param sentinel.service.ports.redis Redis® service port for Redis® + ## @param sentinel.service.ports.sentinel Redis® service port for Redis® Sentinel + ## + ports: + redis: 6379 + sentinel: 26379 + ## @param sentinel.service.nodePorts.redis Node port for Redis® + ## @param sentinel.service.nodePorts.sentinel Node port for Sentinel + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## NOTE: choose port between <30000-32767> + ## NOTE: By leaving these values blank, they will be generated by ports-configmap + ## If setting manually, please leave at least replica.replicaCount + 1 in between sentinel.service.nodePorts.redis and sentinel.service.nodePorts.sentinel to take into account the ports that will be created while incrementing that base port + ## + nodePorts: + redis: "" + sentinel: "" + ## @param sentinel.service.externalTrafficPolicy Redis® Sentinel service external traffic policy + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param sentinel.service.extraPorts Extra ports to expose (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param sentinel.service.clusterIP Redis® Sentinel service Cluster IP + ## + clusterIP: "" + ## @param sentinel.service.loadBalancerIP Redis® Sentinel service Load Balancer IP + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param sentinel.service.loadBalancerSourceRanges Redis® Sentinel service Load Balancer sources + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param sentinel.service.annotations Additional custom annotations for Redis® Sentinel service + ## + annotations: {} + ## @param sentinel.service.sessionAffinity Session Affinity for Kubernetes service, can be "None" or "ClientIP" + ## If "ClientIP", consecutive client requests will be directed to the same Pod + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param sentinel.service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param sentinel.terminationGracePeriodSeconds Integer setting the termination grace period for the redis-node pods + ## + terminationGracePeriodSeconds: 30 + +## @section Other Parameters +## + +## Network Policy configuration +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Enable creation of NetworkPolicy resources + ## + enabled: false + ## @param networkPolicy.allowExternal Don't require client label for connections + ## When set to false, only pods with the correct client label will have network access to the ports + ## Redis® is listening on. When true, Redis® will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.extraIngress Add extra ingress rules to the NetworkPolicy + ## e.g: + ## extraIngress: + ## - ports: + ## - port: 1234 + ## from: + ## - podSelector: + ## - matchLabels: + ## - role: frontend + ## - podSelector: + ## - matchExpressions: + ## - key: role + ## operator: In + ## values: + ## - frontend + ## + extraIngress: [] + ## @param networkPolicy.extraEgress Add extra egress rules to the NetworkPolicy + ## e.g: + ## extraEgress: + ## - ports: + ## - port: 1234 + ## to: + ## - podSelector: + ## - matchLabels: + ## - role: frontend + ## - podSelector: + ## - matchExpressions: + ## - key: role + ## operator: In + ## values: + ## - frontend + ## + extraEgress: [] + ## @param networkPolicy.ingressNSMatchLabels Labels to match to allow traffic from other namespaces + ## @param networkPolicy.ingressNSPodMatchLabels Pod labels to match to allow traffic from other namespaces + ## + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} +## PodSecurityPolicy configuration +## ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/ +## +podSecurityPolicy: + ## @param podSecurityPolicy.create Whether to create a PodSecurityPolicy. WARNING: PodSecurityPolicy is deprecated in Kubernetes v1.21 or later, unavailable in v1.25 or later + ## + create: false + ## @param podSecurityPolicy.enabled Enable PodSecurityPolicy's RBAC rules + ## + enabled: false +## RBAC configuration +## +rbac: + ## @param rbac.create Specifies whether RBAC resources should be created + ## + create: false + ## @param rbac.rules Custom RBAC rules to set + ## e.g: + ## rules: + ## - apiGroups: + ## - "" + ## resources: + ## - pods + ## verbs: + ## - get + ## - list + ## + rules: [] +## ServiceAccount configuration +## +serviceAccount: + ## @param serviceAccount.create Specifies whether a ServiceAccount should be created + ## + create: true + ## @param serviceAccount.name The name of the ServiceAccount to use. + ## If not set and create is true, a name is generated using the common.names.fullname template + ## + name: "" + ## @param serviceAccount.automountServiceAccountToken Whether to auto mount the service account token + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server + ## + automountServiceAccountToken: true + ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount + ## + annotations: {} +## Redis® Pod Disruption Budget configuration +## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +## +pdb: + ## @param pdb.create Specifies whether a PodDisruptionBudget should be created + ## + create: false + ## @param pdb.minAvailable Min number of pods that must still be available after the eviction + ## + minAvailable: 1 + ## @param pdb.maxUnavailable Max number of pods that can be unavailable after the eviction + ## + maxUnavailable: "" +## TLS configuration +## +tls: + ## @param tls.enabled Enable TLS traffic + ## + enabled: false + ## @param tls.authClients Require clients to authenticate + ## + authClients: true + ## @param tls.autoGenerated Enable autogenerated certificates + ## + autoGenerated: false + ## @param tls.existingSecret The name of the existing secret that contains the TLS certificates + ## + existingSecret: "" + ## @param tls.certificatesSecret DEPRECATED. Use existingSecret instead. + ## + certificatesSecret: "" + ## @param tls.certFilename Certificate filename + ## + certFilename: "" + ## @param tls.certKeyFilename Certificate Key filename + ## + certKeyFilename: "" + ## @param tls.certCAFilename CA Certificate filename + ## + certCAFilename: "" + ## @param tls.dhParamsFilename File containing DH params (in order to support DH based ciphers) + ## + dhParamsFilename: "" + +## @section Metrics Parameters +## + +metrics: + ## @param metrics.enabled Start a sidecar prometheus exporter to expose Redis® metrics + ## + enabled: false + ## Bitnami Redis® Exporter image + ## ref: https://hub.docker.com/r/bitnami/redis-exporter/tags/ + ## @param metrics.image.registry Redis® Exporter image registry + ## @param metrics.image.repository Redis® Exporter image repository + ## @param metrics.image.tag Redis® Redis® Exporter image tag (immutable tags are recommended) + ## @param metrics.image.pullPolicy Redis® Exporter image pull policy + ## @param metrics.image.pullSecrets Redis® Exporter image pull secrets + ## + image: + registry: docker.io + repository: bitnami/redis-exporter + tag: 1.43.0-debian-11-r4 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param metrics.command Override default metrics container init command (useful when using custom images) + ## + command: [] + ## @param metrics.redisTargetHost A way to specify an alternative Redis® hostname + ## Useful for certificate CN/SAN matching + ## + redisTargetHost: "localhost" + ## @param metrics.extraArgs Extra arguments for Redis® exporter, for example: + ## e.g.: + ## extraArgs: + ## check-keys: myKey,myOtherKey + ## + extraArgs: {} + ## @param metrics.extraEnvVars Array with extra environment variables to add to Redis® exporter + ## e.g: + ## extraEnvVars: + ## - name: FOO + ## value: "bar" + ## + extraEnvVars: [] + ## Configure Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param metrics.containerSecurityContext.enabled Enabled Redis® exporter containers' Security Context + ## @param metrics.containerSecurityContext.runAsUser Set Redis® exporter containers' Security Context runAsUser + ## + containerSecurityContext: + enabled: true + runAsUser: 1001 + ## @param metrics.extraVolumes Optionally specify extra list of additional volumes for the Redis® metrics sidecar + ## + extraVolumes: [] + ## @param metrics.extraVolumeMounts Optionally specify extra list of additional volumeMounts for the Redis® metrics sidecar + ## + extraVolumeMounts: [] + ## Redis® exporter resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param metrics.resources.limits The resources limits for the Redis® exporter container + ## @param metrics.resources.requests The requested resources for the Redis® exporter container + ## + resources: + limits: {} + requests: {} + ## @param metrics.podLabels Extra labels for Redis® exporter pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param metrics.podAnnotations [object] Annotations for Redis® exporter pods + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9121" + ## Redis® exporter service parameters + ## + service: + ## @param metrics.service.type Redis® exporter service type + ## + type: ClusterIP + ## @param metrics.service.port Redis® exporter service port + ## + port: 9121 + ## @param metrics.service.externalTrafficPolicy Redis® exporter service external traffic policy + ## ref: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param metrics.service.extraPorts Extra ports to expose (normally used with the `sidecar` value) + ## + extraPorts: [] + ## @param metrics.service.loadBalancerIP Redis® exporter service Load Balancer IP + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#internal-load-balancer + ## + loadBalancerIP: "" + ## @param metrics.service.loadBalancerSourceRanges Redis® exporter service Load Balancer sources + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## e.g. + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param metrics.service.annotations Additional custom annotations for Redis® exporter service + ## + annotations: {} + ## Prometheus Service Monitor + ## ref: https://github.com/coreos/prometheus-operator + ## https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#endpoint + ## + serviceMonitor: + ## @param metrics.serviceMonitor.enabled Create ServiceMonitor resource(s) for scraping metrics using PrometheusOperator + ## + enabled: false + ## @param metrics.serviceMonitor.namespace The namespace in which the ServiceMonitor will be created + ## + namespace: "" + ## @param metrics.serviceMonitor.interval The interval at which metrics should be scraped + ## + interval: 30s + ## @param metrics.serviceMonitor.scrapeTimeout The timeout after which the scrape is ended + ## + scrapeTimeout: "" + ## @param metrics.serviceMonitor.relabellings Metrics RelabelConfigs to apply to samples before scraping. + ## + relabellings: [] + ## @param metrics.serviceMonitor.metricRelabelings Metrics RelabelConfigs to apply to samples before ingestion. + ## + metricRelabelings: [] + ## @param metrics.serviceMonitor.honorLabels Specify honorLabels parameter to add the scrape endpoint + ## + honorLabels: false + ## @param metrics.serviceMonitor.additionalLabels Additional labels that can be used so ServiceMonitor resource(s) can be discovered by Prometheus + ## + additionalLabels: {} + ## Custom PrometheusRule to be defined + ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions + ## + prometheusRule: + ## @param metrics.prometheusRule.enabled Create a custom prometheusRule Resource for scraping metrics using PrometheusOperator + ## + enabled: false + ## @param metrics.prometheusRule.namespace The namespace in which the prometheusRule will be created + ## + namespace: "" + ## @param metrics.prometheusRule.additionalLabels Additional labels for the prometheusRule + ## + additionalLabels: {} + ## @param metrics.prometheusRule.rules Custom Prometheus rules + ## e.g: + ## rules: + ## - alert: RedisDown + ## expr: redis_up{service="{{ template "common.names.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 "common.names.fullname" . }}-metrics"} * 100 + ## / + ## redis_memory_max_bytes{service="{{ template "common.names.fullname" . }}-metrics"} + ## > 90 + ## 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 "common.names.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: [] + +## @section Init Container Parameters +## + +## 'volumePermissions' init container parameters +## Changes the owner and group of the persistent volume mount point to runAsUser:fsGroup values +## based on the *podSecurityContext/*containerSecurityContext parameters +## +volumePermissions: + ## @param volumePermissions.enabled Enable init container that changes the owner/group of the PV mount point to `runAsUser:fsGroup` + ## + enabled: false + ## Bitnami Shell image + ## ref: https://hub.docker.com/r/bitnami/bitnami-shell/tags/ + ## @param volumePermissions.image.registry Bitnami Shell image registry + ## @param volumePermissions.image.repository Bitnami Shell image repository + ## @param volumePermissions.image.tag Bitnami Shell image tag (immutable tags are recommended) + ## @param volumePermissions.image.pullPolicy Bitnami Shell image pull policy + ## @param volumePermissions.image.pullSecrets Bitnami Shell image pull secrets + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 11-debian-11-r11 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Init container's resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param volumePermissions.resources.limits The resources limits for the init container + ## @param volumePermissions.resources.requests The requested resources for the init container + ## + resources: + limits: {} + requests: {} + ## Init container Container Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param volumePermissions.containerSecurityContext.runAsUser Set init container's Security Context runAsUser + ## NOTE: when runAsUser is set to special value "auto", init container will try to chown the + ## data folder to auto-determined user&group, using commands: `id -u`:`id -G | cut -d" " -f2` + ## "auto" is especially useful for OpenShift which has scc with dynamic user ids (and 0 is not allowed) + ## + containerSecurityContext: + runAsUser: 0 + +## init-sysctl container parameters +## used to perform sysctl operation to modify Kernel settings (needed sometimes to avoid warnings) +## +sysctl: + ## @param sysctl.enabled Enable init container to modify Kernel settings + ## + enabled: false + ## Bitnami Shell image + ## ref: https://hub.docker.com/r/bitnami/bitnami-shell/tags/ + ## @param sysctl.image.registry Bitnami Shell image registry + ## @param sysctl.image.repository Bitnami Shell image repository + ## @param sysctl.image.tag Bitnami Shell image tag (immutable tags are recommended) + ## @param sysctl.image.pullPolicy Bitnami Shell image pull policy + ## @param sysctl.image.pullSecrets Bitnami Shell image pull secrets + ## + image: + registry: docker.io + repository: bitnami/bitnami-shell + tag: 11-debian-11-r11 + 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/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param sysctl.command Override default init-sysctl container command (useful when using custom images) + ## + command: [] + ## @param sysctl.mountHostSys Mount the host `/sys` folder to `/host-sys` + ## + mountHostSys: false + ## Init container's resource requests and limits + ## ref: https://kubernetes.io/docs/user-guide/compute-resources/ + ## @param sysctl.resources.limits The resources limits for the init container + ## @param sysctl.resources.requests The requested resources for the init container + ## + resources: + limits: {} + requests: {} + +## @section useExternalDNS Parameters +## +## @param useExternalDNS.enabled Enable various syntax that would enable external-dns to work. Note this requires a working installation of `external-dns` to be usable. +## @param useExternalDNS.additionalAnnotations Extra annotations to be utilized when `external-dns` is enabled. +## @param useExternalDNS.annotationKey The annotation key utilized when `external-dns` is enabled. +## @param useExternalDNS.suffix The DNS suffix utilized when `external-dns` is enabled. Note that we prepend the suffix with the full name of the release. +## +useExternalDNS: + enabled: false + suffix: "" + annotationKey: external-dns.alpha.kubernetes.io/ + additionalAnnotations: {}

+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 delta 5326 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 diff --git a/rds/base/charts/all/charts/layer1-port-openscienceframework-0.2.3.tgz b/rds/base/charts/all/charts/layer1-port-openscienceframework-0.2.3.tgz index 1bee089c7fd3a311f37d96a10836379722f24398..29f68d416fba84a881e2e68c01f00e269bfb0540 100644 GIT binary patch literal 3085 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 WLk%_5P(ux09sCcwq!V2LcmM!9k!+{{ diff --git a/rds/base/charts/all/charts/layer1-port-owncloud-0.3.3.tgz b/rds/base/charts/all/charts/layer1-port-owncloud-0.3.3.tgz index 2c11272d55bb2e919ec4e16a2779216315319b7c..0df6d8eaa8b3c1a243ba621fd7ff18fe2fd2d3a3 100644 GIT binary patch delta 3286 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 UZP`%%KL7y#|19*_MF2_w06+ht4FCWD delta 3743 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 JKfeH6006?4b>jd4 diff --git a/rds/base/charts/all/charts/layer1-port-reva-0.2.0.tgz b/rds/base/charts/all/charts/layer1-port-reva-0.2.0.tgz index b397b6e2e4d41c95bee9199ca77991499b446342..798786df179585cb65820b2c75b56b7c65c53a26 100644 GIT binary patch delta 2925 zcmV-z3zGEa8jBZ@JAXQBZyUF={=C0p?z)AOG+M3R_7(+hVCUWjZ5qSMNl_F%2E`po zthwZt(N$qO2l3#J)oB-wrJxI=QIPW3J$r4qkl}lw>3Kyh(JSWOTOOo-u z&(-2Mj)(oe`;Ozd`5li&gS}3FFc@`u{ZXgC7kB!-?r0C<&wob2HLcKwsJ-~!Yc)6b zl{AvGcc?UzB7sFWAbDQ>4LeaB1}Ww!QWTV-PJ$K?&WKK+sW&YuvrLKrK#&X*sQxO< zrDjH|Wdc*iF=@A>s9g;&%GLfEN1Z4RQq)wj+_=6M@FVgJs8varN(Da^6I22X>R>MI zGIL{cowVDRmw%U$LXAYxc`GG`v^1&(qo^aPX6=Aci|>}RSvG5H%5Wm1T+D(CTwY3* zy4{yhK?^Kg(1H@u4K7HK5he^_0+j>6j4+-+js{gUN2c}v7J{|10)@6+DON1|(T)cG zcayz=mW85T0_84d%*=~PL}k_-rn97 z_Rl4~_-s0Mll}KP!!7%d`;Yej5Upw6pJ=_-c)LN$W~fa9dQQ57VM6F(fbMxzKTlT*oyg>a~6M!4+ziW-X zVgG|c@6rArqV2;8PD#NHc)`0c>y)B*ZsY}ub8Vy|GmL_Lcspkrm<9sBy?Fg|YbsSn zjKS2o20P&dDJKeHK@=ksjv9=F2|`Y^#uS(^5`UJmP+&02IWefCAW-N6)UhmtNuU!1 zEF&{a8s0}{f-U6x_Btd27&5b=(x5g0KwfZuDmkOe1YYnuDmj;zOw1g1&V-#p(mxCB#xuKsK!1u_ z6SaKq_OCFLZubbZP<#m!MI3LY1{EBvS$z<#kY>y{N$mpw&5Hy&ah$FGXP7wy>U0lZ zv(*;=Dp21RA`mjgF>Lp0}*!MvZ(pIU2@s9e?Z}7m>zlI^*Z-w`=-1q~;(z6t z+vjuSIVv5Q{C=sx4e`Gpce{=FKODu6_5XvkzyEGOg9XbH&}dv7x8df8%(8{fF@b08 zj~@d&D|q=nciwr9u1Wwo^)AX4jKOMN%ldRC5JIn6=T@`Mt!70QD1_E}ntyZskuNxH zY=P*VPd@9x5k7!Xg`hC(xt}Z>7t<+w4`FMK%!U^GJGh<#mspfgHcap$MF@R4Q;t4K z88r~Pe`ivfup-NKK{(@bLioDd|9=WiS{&1k6%vC$s{PvFzl6UE!r7EzYO@Q+&M5d5 z{Z8F{WA|%Wt6_qa6dFOx41a4UAYKSlhCJ0^lP(tq(1lMIIG;?GPCzHWjO|<|W-eMi zje=KGP{?hF^hl_5M;5C%>HC<0OUAj)-U^$$xiD;rUr~FryvsCaEhaXtnAXgJsySCq zxRoAoy~gy7+eh-l?NzNtXT=haB|7u`^1v0X8@+cXFjq{NDTLqZ)_=D;Y+&>2-bMMZ z`n{ouH3?f|UKQ|Gx=zX~mGQG&@_ZXfyXoPT`>M0u|6ZLj!Uvn;b3zfq??MRA!@F6# z-Dh^N+fBxWbP`*52pHvq;a|jsZ7-t(fHCw%27s8=I>-B%7L0 zGak>q7u6x{Zd}!=jDJP71@Zy@Dx|@>yV{8*BZAB@ZB3S&G>v^_0^2t2IqL_&$lplD zcaB}5rq_$j7b-h_iFd%za3H>S1-EH{YzlLtf5K%s8Nw2M1IaGvj~~OTS_-c*x@$JG zuBWcqkmqA#JFDs}GF;cNz$SflxElbrN&nIr$zyxSc1`|IrGJ>RSw`~v9tPfY{@)*t z8u|aI+Z{aS{}0hN?*BT$1ruok$4>IENgiZqNJVNm+NBe(>R=0nW=ek`vfs;!7JG{WPneMvqi@eR<%0&Q_hxOMYFVpu3 zJ^&Lb3Ilx$ZcE$ModIW*=nb>3UorT#ba|lZ&bKpS==|qTTz=>=mWH#!SYw%OpSrh0 zj~t-fpKVMqb1c`-418~Sd22^o?(SxFb+Z}=@E0pEDSz~GBHt&SsC!tAQJH1L%Ki5P zJwJF32Q7N=9Ku2v#zAN<CRRUz6w%hc`7a$Mbwvyrm3w} z1yOfhu9>2g%?{{Im{LM>WAX;FN}#pdwR+2yG=Gvxp1|AV)7ltk3lymJsge`iT+OcX z|7eEY(5kR#o}t^6qbK zWu5L?UAL^cejl}-wsA{!Yb)NeQ+I`x3nffK$r>u0l_dqvrdM;i&Ui|2;_CS^uq$_%`ysEu}5hqxRsyYhmLBLhUTDk+L-m z?OBUEi>u=hR5-i_n>Q|T|Q zyN~Vu?Hd1YP#gc*TD@~(QhZ7@xas_F*ne%D|8+Zq-tf`?KSbN<|4(JQqvj9q;F{gb zo>uuJF1)V`-tnw0EbrdtQeK1!xCM1VDQaE0>}E|?X8V^jH1!Iws)?&wt}2G@)Pq0D z)KATH0`U969axcE9RVC(g;oPiS;GB-3sev1TDR;!vV3JTAr*eEHEy#1L3iu@zkls| zwEqWa`*2E(K_%Q%T`zaIoTGqA!FbBV401v*$P9J$0$3NhOLRcbk#kq6`x7)KX3s$( zCx$K1m6Wx9BGO{Z&{_l>*kTRu%XvR$2eR}ePXlC*+)pnm{$9&edALU=Y5bZUh385E7?%qD>;8~F*AB> XkL|I21?@ip00960v}*VB06YKyB8afU delta 3404 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 diff --git a/rds/base/charts/all/charts/layer1-port-zenodo-0.2.2.tgz b/rds/base/charts/all/charts/layer1-port-zenodo-0.2.2.tgz index 91504697a1b1ccb9519ead49efc814536e559732..8d5cf43344f519604291acc09414d603480a9fbb 100644 GIT binary patch delta 3008 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 V+yBG%zX1RM|NqDllvn^%000@|7=r)+ diff --git a/rds/base/charts/all/charts/layer2-exporter-service-0.2.3.tgz b/rds/base/charts/all/charts/layer2-exporter-service-0.2.3.tgz index 33b3b0aab4c3415d4581e078ddcca652dbf5621c..171828520adb2ebe65e23cd36a31ec1026c7c100 100644 GIT binary patch delta 2701 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 H06qW!i+f^* delta 3174 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 delta 3145 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 diff --git a/rds/base/charts/all/charts/layer2-port-service-0.2.5.tgz b/rds/base/charts/all/charts/layer2-port-service-0.2.5.tgz index f2ce825dbfaee97d5bc6ec018bd15a9d43e1b4a2..2f913d93734fc79c06b183fcfcab18cccf4b6088 100644 GIT binary patch delta 2803 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 R{{;X5|Nqxbbm;&<003D*2p9kW delta 3556 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 diff --git a/rds/base/charts/all/charts/redis-16.13.2.tgz b/rds/base/charts/all/charts/redis-16.13.2.tgz index a6ac9c37b2075bdd3a5094f6dd2aa1b90462fb38..adda01235e63954c560d00d5411b5adcd95ad76b 100644 GIT binary patch delta 86859 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 N|No4r?Uewg2LSc0=Ys$M delta 101009 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�P3iR(6Hn%M-VyO(U+ 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�xz6Y_HLe^% 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 diff --git a/rds/base/charts/all/charts/redis-cluster-7.6.4.tgz b/rds/base/charts/all/charts/redis-cluster-7.6.4.tgz index 743daa46b28be3bea3260323b3b8555c2a3db20d..c50cecf3996ccd901768ffde7f308a763ec91c28 100644 GIT binary patch delta 89644 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 fzoJ`IzCGWbZ_l^q>wEq`00960@oAJ}9PshI- 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