From 66ffd85393a51538eb76238f015760e6bf4482f0 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Tue, 17 Mar 2026 08:32:10 -0400 Subject: [PATCH 01/13] mongodb operator, rabbitmq operator, valkey --- .gitignore | 2 + Chart.yaml | 20 +- README.md | 139 ++++++++++- templates/_helpers.tpl | 58 +++-- templates/configmaps_st2-conf.yaml | 19 +- templates/deployments.yaml | 78 ++++++ templates/mongodb-community.yaml | 106 +++++++++ templates/rabbitmq-cluster.yaml | 172 +++++++++++++ templates/secrets_rabbitmq.yaml | 16 -- values.yaml | 371 ++++++++++++++++++++++------- 10 files changed, 827 insertions(+), 154 deletions(-) create mode 100644 templates/mongodb-community.yaml create mode 100644 templates/rabbitmq-cluster.yaml delete mode 100644 templates/secrets_rabbitmq.yaml diff --git a/.gitignore b/.gitignore index 56674120..32d4e736 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ charts *.lock .DS_Store +*.swp +docs/ diff --git a/Chart.yaml b/Chart.yaml index a418bdd5..f3ccc2a2 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -25,22 +25,14 @@ maintainers: url: https://github.com/StackStorm details: This Helm chart is a fully installable app that codifies StackStorm cluster deployment optimized for HA and K8s environment. - RabbitMQ-HA, MongoDB-HA clusters and Redis coordination backend st2 relies on will be deployed as 3rd party chart dependencies. - For configuration details please check default values.yaml and README. + MongoDB-HA is deployed using the MongoDB Community Operator (must be pre-installed), val-key operator, and rabbitmq operator + For configuration details please check default values.yaml and README. dependencies: - - name: rabbitmq - version: 8.0.2 - repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami - condition: rabbitmq.enabled - - name: mongodb - version: 10.0.1 - repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami - condition: mongodb.enabled - name: external-dns version: 4.0.0 repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami condition: external-dns.enabled - - name: redis - version: 12.3.2 - repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami - condition: redis.enabled + - name: valkey + version: 0.9.3 + repository: https://valkey.io/valkey-helm/ + condition: valkey.enabled diff --git a/README.md b/README.md index 90c812e4..8cdcc362 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ It's more than welcome to fine-tune each component settings to fit specific avai ## Requirements * [Supported](https://kubernetes.io/releases/) [Kubernetes](https://kubernetes.io/docs/setup/) cluster * [Helm](https://docs.helm.sh/using_helm/#install-helm) `v3.5` or greater +* [RabbitMQ Cluster Operator](https://www.rabbitmq.com/kubernetes/operator/operator-overview.html) installed in the cluster (if using in-cluster RabbitMQ) +* RabbitMQ credentials secret created before installation (see [RabbitMQ Credentials Secret](#rabbitmq-credentials-secret) below) ## Usage 1) Edit `values.yaml` with configuration for the StackStorm HA K8s cluster. @@ -174,19 +176,136 @@ Helm chart settings, which might be fine-tuned via `values.yaml`. The deployment of MongoDB to the k8s cluster can be disabled by setting the mongodb-ha.enabled key in values.yaml to false. *Note: Stackstorm relies heavily on connections to a MongoDB instance. If the in-cluster deployment of MongoDB is disabled, a connection to an external instance of MongoDB must be configured. The st2.config key in values.yaml provides a way to configure stackstorm. See [Configure MongoDB](https://docs.stackstorm.com/install/config/config.html#configure-mongodb) for configuration details.* -### [RabbitMQ HA Cluster](https://docs.stackstorm.com/latest/reference/ha.html#rabbitmq) +### [RabbitMQ Cluster](https://docs.stackstorm.com/latest/reference/ha.html#rabbitmq) RabbitMQ is a message bus StackStorm relies on for inter-process communication and load distribution. -External Helm Chart is used to deploy [RabbitMQ cluster](https://www.rabbitmq.com/clustering.html) in Highly Available mode. -By default `3` nodes of RabbitMQ are deployed via K8s StatefulSet. -For more advanced RabbitMQ configuration, please refer to bitnami [rabbitmq](https://github.com/bitnami/charts/tree/master/bitnami/rabbitmq) -Helm chart repository, - all settings could be overridden via `values.yaml`. +This chart uses the [RabbitMQ Cluster Operator](https://www.rabbitmq.com/kubernetes/operator/operator-overview.html) to deploy a RabbitMQ cluster in Highly Available mode. -The deployment of RabbitMQ to the k8s cluster can be disabled by setting the rabbitmq-ha.enabled key in values.yaml to false. *Note: Stackstorm relies heavily on connections to a RabbitMQ instance. If the in-cluster deployment of RabbitMQ is disabled, a connection to an external instance of RabbitMQ must be configured. The st2.config key in values.yaml provides a way to configure stackstorm. See [Configure RabbitMQ](https://docs.stackstorm.com/install/config/config.html#configure-rabbitmq) for configuration details.* +**Prerequisites:** +1. The RabbitMQ Cluster Operator must be installed in your Kubernetes cluster before installing this chart. See the [operator installation guide](https://www.rabbitmq.com/kubernetes/operator/install-operator.html). +2. You must create a Kubernetes Secret containing RabbitMQ credentials before installing this chart (see [RabbitMQ Credentials Secret](#rabbitmq-credentials-secret) below). -### [redis](https://docs.stackstorm.com/latest/reference/ha.html#zookeeper-redis) -StackStorm employs redis sentinel as a distributed coordination backend, required for st2 cluster components to work properly in HA scenario. -`3` node Redis cluster with Sentinel enabled is deployed via external bitnami Helm chart dependency [redis](https://github.com/bitnami/charts/tree/master/bitnami/redis). -As any other Helm dependency, it's possible to further configure it for specific scaling needs via `values.yaml`. +By default, a `3`-node RabbitMQ cluster is deployed via the RabbitmqCluster custom resource. The cluster configuration can be customized via the `rabbitmq` section in `values.yaml`. + +The deployment of RabbitMQ to the k8s cluster can be disabled by setting the `rabbitmq.enabled` key in values.yaml to false. **Note:** StackStorm relies heavily on connections to a RabbitMQ instance. If the in-cluster deployment of RabbitMQ is disabled, a connection to an external instance of RabbitMQ must be configured. The `st2.config` key in values.yaml provides a way to configure StackStorm. See [Configure RabbitMQ](https://docs.stackstorm.com/install/config/config.html#configure-rabbitmq) for configuration details. + +#### RabbitMQ Credentials Secret +**IMPORTANT:** You must create a Kubernetes Secret containing RabbitMQ credentials **before** installing this Helm chart. The chart will fail to install if this secret does not exist. + +The secret must contain the following keys: +- `username`: RabbitMQ username (e.g., `st2admin`) +- `password`: RabbitMQ password (strong password recommended) +- `erlangCookie`: Erlang cookie for RabbitMQ cluster inter-node authentication (alphanumeric string, typically 20-40 characters) + +**Example: Creating the RabbitMQ credentials secret** + +```bash +# Generate a strong password and erlang cookie +RABBITMQ_PASSWORD=$(openssl rand -base64 32) +ERLANG_COOKIE=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32) + +# Create the secret +kubectl create secret generic rabbitmq-credentials \ + --from-literal=username=st2admin \ + --from-literal=password="${RABBITMQ_PASSWORD}" \ + --from-literal=erlangCookie="${ERLANG_COOKIE}" +``` + +**Example: Creating the secret from a YAML file** + +```yaml +# rabbitmq-credentials.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rabbitmq-credentials +type: Opaque +stringData: + username: st2admin + password: "your-strong-password-here" + erlangCookie: "your-alphanumeric-erlang-cookie-here" +``` + +```bash +kubectl apply -f rabbitmq-credentials.yaml +``` + +**Configuring the secret name in values.yaml:** + +After creating the secret, reference it in your `values.yaml`: + +```yaml +rabbitmq: + enabled: true + auth: + existingSecret: "rabbitmq-credentials" # Name of the secret you created + usernameKey: "username" # Key in the secret for username + passwordKey: "password" # Key in the secret for password + erlangCookieKey: "erlangCookie" # Key in the secret for erlang cookie +``` + +**Security Best Practices:** +- Use strong, randomly generated passwords (minimum 32 characters recommended) +- The erlangCookie must be an alphanumeric string (letters and numbers only, no special characters) +- Store credentials securely and never commit them to version control +- Consider using a secrets management solution like HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault +- Rotate credentials regularly according to your security policies +- Use Kubernetes RBAC to restrict access to the secret + +**Troubleshooting:** +- If the chart fails to install with an error about missing secrets, verify the secret exists: `kubectl get secret rabbitmq-credentials` +- Verify the secret contains the required keys: `kubectl describe secret rabbitmq-credentials` +- Ensure the secret is in the same namespace where you're installing the chart +- Check that the `rabbitmq.auth.existingSecret` value in your `values.yaml` matches the actual secret name + +### [Valkey](https://docs.stackstorm.com/latest/reference/ha.html#zookeeper-redis) +StackStorm employs Valkey (Redis-compatible) as a distributed coordination backend, required for st2 cluster components to work properly in HA scenario. +This chart uses the [Valkey Helm Chart](https://github.com/valkey-io/valkey-helm) to deploy a Valkey cluster in Highly Available mode. + +By default, a Valkey cluster with `1` master and `2` replicas (3 total nodes) is deployed. The cluster configuration can be customized via the `valkey` section in `values.yaml`. + +**Authentication:** +Valkey authentication is enabled by default using ACL (Access Control Lists). You can configure authentication in two ways: + +1. **Using Kubernetes Secrets (Recommended for Production):** + ```bash + # Create a secret with Valkey credentials + kubectl create secret generic valkey-credentials \ + --from-literal=default-password=YOUR_SECURE_PASSWORD + ``` + + Then reference it in your `values.yaml`: + ```yaml + valkey: + auth: + enabled: true + usersExistingSecret: "valkey-credentials" + aclUsers: + default: + permissions: "~* &* +@all" + passwordKey: "default-password" + ``` + +2. **Using inline passwords (for testing only):** + ```yaml + valkey: + auth: + enabled: true + aclUsers: + default: + permissions: "~* &* +@all" + password: "your-password-here" + ``` + +**Disabling In-Cluster Valkey:** +The deployment of Valkey to the k8s cluster can be disabled by setting the `valkey.enabled` key in values.yaml to false. **Note:** StackStorm HA mode relies on a coordination backend. If the in-cluster deployment of Valkey is disabled, a connection to an external Redis/Valkey instance must be configured. The `st2.config` key in values.yaml provides a way to configure StackStorm. See [Configure Coordination Backend](https://docs.stackstorm.com/install/config/config.html) for configuration details. + +**Configuration Options:** +- **Replica Mode:** Enabled by default with 2 replicas for high availability +- **Persistence:** 10Gi storage per replica (configurable) +- **Resources:** 512Mi/500m requests, 1Gi/1000m limits (configurable) +- **Pod Placement:** Supports affinity, nodeSelector, and tolerations + +For more advanced Valkey configuration options, refer to the [Valkey Helm Chart documentation](https://github.com/valkey-io/valkey-helm). ## Install custom st2 packs in the cluster There are two ways to install st2 packs in the k8s cluster. diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 9cadeeab..0e5370a5 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -115,6 +115,14 @@ Usage: "{{ include "stackstorm-ha.nested" (list . "mongodb" "mongodb.fullname") Generate comma-separated list of nodes for MongoDB-HA connection string, based on number of replicas and service name */}} {{- define "stackstorm-ha.mongodb-nodes" -}} +{{- if index .Values "mongodbCommunity" "enabled" }} +{{- $replicas := (int (index .Values "mongodbCommunity" "members")) }} +{{- $mongo_name := print .Release.Name "-mongodb" }} +{{- range $index0 := until $replicas -}} + {{- $index1 := $index0 | add1 -}} + {{- $mongo_name }}-{{ $index0 }}.{{ $mongo_name }}-svc.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }}{{ if ne $index1 $replicas }},{{ end }} +{{- end -}} +{{- else if index .Values "mongodb" "enabled" }} {{- $replicas := (int (index .Values "mongodb" "replicaCount")) }} {{- $architecture := (index .Values "mongodb" "architecture" ) }} {{- $mongo_fullname := include "stackstorm-ha.nested" (list $ "mongodb" "mongodb.fullname") }} @@ -127,31 +135,23 @@ Generate comma-separated list of nodes for MongoDB-HA connection string, based o {{- end -}} {{- end -}} {{- end -}} +{{- end -}} {{/* -Generate list of nodes for Redis with Sentinel connection string, based on number of replicas and service name +Generate connection string for Valkey cluster */}} -{{- define "stackstorm-ha.redis-nodes" -}} -{{- if not .Values.redis.sentinel.enabled }} -{{- fail "value for redis.sentinel.enabled MUST be true" }} +{{- define "stackstorm-ha.valkey-connection" -}} +{{- if .Values.valkey.enabled }} +{{- $valkeyName := print .Release.Name "-valkey" }} +{{- $password := "" }} +{{- if .Values.valkey.auth.existingSecret }} +{{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.valkey.auth.existingSecret }} +{{- if $secret }} +{{- $password = index $secret.data .Values.valkey.auth.passwordKey | b64dec }} {{- end }} -{{- $replicas := (int (index .Values "redis" "cluster" "slaveCount")) }} -{{- $master_name := (index .Values "redis" "sentinel" "masterSet") }} -{{- $sentinel_port := (index .Values "redis" "sentinel" "port") }} -{{- range $index0 := until $replicas -}} - {{- if eq $index0 0 -}} - {{ $.Release.Name }}-redis-node-{{ $index0 }}.{{ $.Release.Name }}-redis-headless.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }}:{{ $sentinel_port }}?sentinel={{ $master_name }} - {{- else -}} - &sentinel_fallback={{ $.Release.Name }}-redis-node-{{ $index0 }}.{{ $.Release.Name }}-redis-headless.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }}:{{ $sentinel_port }} - {{- end -}} -{{- end -}} -{{- end -}} - -{{- define "stackstorm-ha.redis-password" -}} -{{- if not .Values.redis.sentinel.enabled }} -{{- fail "value for redis.sentinel.enabled MUST be true" }} {{- end }} -{{- if not (empty .Values.redis.password)}}:{{ .Values.redis.password }}@{{- end }} +{{- if $password }}:{{ $password }}@{{ end }}{{ $valkeyName }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:6379 +{{- end }} {{- end -}} {{/* @@ -198,7 +198,23 @@ Reduce duplication of the st2.*.conf volume details {{- end -}} {{- define "stackstorm-ha.init-containers-wait-for-db" -}} -{{- if index .Values "mongodb" "enabled" }} +{{- if index .Values "mongodbCommunity" "enabled" }} +- name: wait-for-db + image: {{ template "stackstorm-ha.utilityImage" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - 'sh' + - '-c' + - > + until nc -z -w 2 {{ $.Release.Name }}-mongodb-svc 27017 && echo mongodb ok; + do + echo 'Waiting for MongoDB Connection...' + sleep 2; + done + {{- with .Values.securityContext }} + securityContext: {{- toYaml . | nindent 8 }} + {{- end }} +{{- else if index .Values "mongodb" "enabled" }} {{- $mongodb_port := (int (index .Values "mongodb" "service" "port")) }} - name: wait-for-db image: {{ template "stackstorm-ha.utilityImage" . }} diff --git a/templates/configmaps_st2-conf.yaml b/templates/configmaps_st2-conf.yaml index c2473b3a..5c2aa28d 100644 --- a/templates/configmaps_st2-conf.yaml +++ b/templates/configmaps_st2-conf.yaml @@ -15,15 +15,26 @@ data: [system_user] user = {{ .Values.st2.system_user.user }} ssh_key_file = {{ tpl .Values.st2.system_user.ssh_key_file . }} - {{- if index .Values "redis" "enabled" }} + {{- if index .Values "valkey" "enabled" }} [coordination] - url = redis://{{ template "stackstorm-ha.redis-password" $ }}{{ template "stackstorm-ha.redis-nodes" $ }} + url = redis://{{ template "stackstorm-ha.valkey-connection" $ }} {{- end }} {{- if index .Values "rabbitmq" "enabled" }} [messaging] - url = amqp://{{ required "rabbitmq.auth.username is required!" (index .Values "rabbitmq" "auth" "username") }}:{{ required "rabbitmq.auth.password is required!" (index .Values "rabbitmq" "auth" "password") }}@{{ .Release.Name }}-rabbitmq:5672{{ required "rabbitmq.ingress.path is required!" (index .Values "rabbitmq" "ingress" "path") }} + url = amqp://${RABBITMQ_USERNAME}:${RABBITMQ_PASSWORD}@{{ .Release.Name }}-rabbitmq:5672/ {{- end }} - {{- if index .Values "mongodb" "enabled" }} + {{- if index .Values "mongodbCommunity" "enabled" }} + [database] + {{- if index .Values "mongodbCommunity" "auth" "enabled" }} + host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }}?authSource={{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }}&replicaSet={{ index .Values "mongodbCommunity" "replicaSetName" }} + username = {{ required "mongodbCommunity.auth.username is required!" (index .Values "mongodbCommunity" "auth" "username") }} + password = ${MONGODB_PASSWORD} + db_name = {{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }} + {{- else }} + host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/?replicaSet={{ index .Values "mongodbCommunity" "replicaSetName" }} + {{- end }} + port = 27017 + {{- else if index .Values "mongodb" "enabled" }} [database] {{- if index .Values "mongodb" "auth" "enabled" }} host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}?authSource={{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}&replicaSet={{ index .Values "mongodb" "replicaSetName" }} diff --git a/templates/deployments.yaml b/templates/deployments.yaml index ee6d9cb6..bdff8cdf 100644 --- a/templates/deployments.yaml +++ b/templates/deployments.yaml @@ -215,6 +215,14 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- if ne "disable" (default "" .Values.st2.datastore_crypto_key) }} @@ -600,6 +608,14 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- if ne "disable" (default "" .Values.st2.datastore_crypto_key) }} @@ -718,6 +734,14 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- range .Values.st2timersengine.extra_volumes }} @@ -826,6 +850,15 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- if ne "disable" (default "" .Values.st2.datastore_crypto_key) }} @@ -945,6 +978,15 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- if ne "disable" (default "" .Values.st2.datastore_crypto_key) }} @@ -1062,6 +1104,15 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- range .Values.st2notifier.extra_volumes }} @@ -1245,6 +1296,15 @@ spec: envFrom: - configMapRef: name: {{ $.Release.Name }}-st2-urls + {{- if and $.Values.rabbitmq.enabled $.Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ $.Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and $.Values.mongodbCommunity.enabled $.Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ $.Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + {{- range $sensor.envFromSecrets }} - secretRef: name: {{ . }} @@ -1401,6 +1461,15 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + {{- range .Values.st2actionrunner.envFromSecrets }} - secretRef: name: {{ . }} @@ -1536,6 +1605,15 @@ spec: envFrom: - configMapRef: name: {{ .Release.Name }}-st2-urls + {{- if and .Values.rabbitmq.enabled .Values.rabbitmq.auth.existingSecret }} + - secretRef: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- if and .Values.mongodbCommunity.enabled .Values.mongodbCommunity.auth.existingSecret }} + - secretRef: + name: {{ .Values.mongodbCommunity.auth.existingSecret }} + {{- end }} + volumeMounts: {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} {{- range .Values.st2garbagecollector.extra_volumes }} diff --git a/templates/mongodb-community.yaml b/templates/mongodb-community.yaml new file mode 100644 index 00000000..69f35b6d --- /dev/null +++ b/templates/mongodb-community.yaml @@ -0,0 +1,106 @@ +{{- if index .Values "mongodbCommunity" "enabled" }} +{{- $existingSecret := index .Values "mongodbCommunity" "auth" "existingSecret" | default (printf "%s-mongodb-auth" .Release.Name) }} +--- +apiVersion: mongodbcommunity.mongodb.com/v1 +kind: MongoDBCommunity +metadata: + name: {{ .Release.Name }}-mongodb + labels: {{- include "stackstorm-ha.labels" (list $ "mongodb") | nindent 4 }} +spec: + members: {{ index .Values "mongodbCommunity" "members" }} + type: ReplicaSet + version: {{ index .Values "mongodbCommunity" "version" | quote }} + {{- if index .Values "mongodbCommunity" "auth" "enabled" }} + security: + authentication: + modes: ["SCRAM"] + users: + - name: {{ index .Values "mongodbCommunity" "auth" "username" }} + db: {{ index .Values "mongodbCommunity" "auth" "database" }} + passwordSecretRef: + name: {{ $existingSecret }} + key: {{ index .Values "mongodbCommunity" "auth" "passwordKey" }} + roles: + - name: readWrite + db: {{ index .Values "mongodbCommunity" "auth" "database" }} + - name: clusterAdmin + db: admin + scramCredentialsSecretName: {{ .Release.Name }}-mongodb-scram + - name: admin + db: admin + passwordSecretRef: + name: {{ $existingSecret }} + key: {{ index .Values "mongodbCommunity" "auth" "rootPasswordKey" }} + roles: + - name: root + db: admin + scramCredentialsSecretName: {{ .Release.Name }}-mongodb-admin-scram + {{- end }} + {{- if index .Values "mongodbCommunity" "tls" "enabled" }} + tls: + enabled: true + {{- if index .Values "mongodbCommunity" "tls" "certificateKeySecretRef" }} + certificateKeySecretRef: + name: {{ index .Values "mongodbCommunity" "tls" "certificateKeySecretRef" }} + {{- end }} + {{- if index .Values "mongodbCommunity" "tls" "caCertificateSecretRef" }} + caCertificateSecretRef: + name: {{ index .Values "mongodbCommunity" "tls" "caCertificateSecretRef" }} + {{- end }} + {{- end }} + additionalMongodConfig: + storage.wiredTiger.engineConfig.journalCompressor: snappy + net.compression.compressors: snappy + {{- if index .Values "mongodbCommunity" "replicaSetName" }} + replication.replSetName: {{ index .Values "mongodbCommunity" "replicaSetName" }} + {{- end }} + statefulSet: + spec: + {{- if or (index .Values "mongodbCommunity" "nodeSelector") (index .Values "mongodbCommunity" "affinity") (index .Values "mongodbCommunity" "tolerations") (index .Values "mongodbCommunity" "resources") }} + template: + spec: + {{- with index .Values "mongodbCommunity" "nodeSelector" }} + nodeSelector: {{- toYaml . | nindent 12 }} + {{- end }} + {{- with index .Values "mongodbCommunity" "affinity" }} + affinity: {{- toYaml . | nindent 12 }} + {{- end }} + {{- with index .Values "mongodbCommunity" "tolerations" }} + tolerations: {{- toYaml . | nindent 12 }} + {{- end }} + {{- if index .Values "mongodbCommunity" "resources" }} + containers: + - name: mongod + {{- with index .Values "mongodbCommunity" "resources" }} + resources: {{- toYaml . | nindent 16 }} + {{- end }} + - name: mongodb-agent + {{- with index .Values "mongodbCommunity" "resources" }} + resources: {{- toYaml . | nindent 16 }} + {{- end }} + {{- end }} + {{- end }} + {{- if index .Values "mongodbCommunity" "persistence" "enabled" }} + volumeClaimTemplates: + - metadata: + name: data-volume + spec: + accessModes: [ "ReadWriteOnce" ] + {{- if index .Values "mongodbCommunity" "persistence" "storageClassName" }} + storageClassName: {{ index .Values "mongodbCommunity" "persistence" "storageClassName" | quote }} + {{- end }} + resources: + requests: + storage: {{ index .Values "mongodbCommunity" "persistence" "storage" }} + - metadata: + name: logs-volume + spec: + accessModes: [ "ReadWriteOnce" ] + {{- if index .Values "mongodbCommunity" "persistence" "storageClassName" }} + storageClassName: {{ index .Values "mongodbCommunity" "persistence" "storageClassName" | quote }} + {{- end }} + resources: + requests: + storage: 2Gi + {{- end }} +{{- end }} diff --git a/templates/rabbitmq-cluster.yaml b/templates/rabbitmq-cluster.yaml new file mode 100644 index 00000000..6413bd7d --- /dev/null +++ b/templates/rabbitmq-cluster.yaml @@ -0,0 +1,172 @@ +{{- if .Values.rabbitmq.enabled }} +--- +apiVersion: rabbitmq.com/v1beta1 +kind: RabbitmqCluster +metadata: + name: {{ .Release.Name }}-rabbitmq + labels: + app.kubernetes.io/name: {{ .Release.Name }}-rabbitmq + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + replicas: {{ .Values.rabbitmq.replicas | default 3 }} + + {{- if .Values.rabbitmq.image }} + image: {{ .Values.rabbitmq.image }} + {{- end }} + + {{- if .Values.rabbitmq.imagePullSecrets }} + imagePullSecrets: + {{- toYaml .Values.rabbitmq.imagePullSecrets | nindent 4 }} + {{- end }} + + {{- if .Values.rabbitmq.resources }} + resources: + {{- toYaml .Values.rabbitmq.resources | nindent 4 }} + {{- end }} + + rabbitmq: + {{- if .Values.rabbitmq.additionalConfig }} + additionalConfig: | + {{- .Values.rabbitmq.additionalConfig | nindent 6 }} + {{- end }} + + {{- if .Values.rabbitmq.advancedConfig }} + advancedConfig: | + {{- .Values.rabbitmq.advancedConfig | nindent 6 }} + {{- end }} + + {{- if .Values.rabbitmq.envConfig }} + envConfig: | + {{- .Values.rabbitmq.envConfig | nindent 6 }} + {{- end }} + + {{- if .Values.rabbitmq.erlangInetConfig }} + erlangInetConfig: | + {{- .Values.rabbitmq.erlangInetConfig | nindent 6 }} + {{- end }} + + {{- if .Values.rabbitmq.additionalPlugins }} + additionalPlugins: + {{- toYaml .Values.rabbitmq.additionalPlugins | nindent 6 }} + {{- end }} + + persistence: + {{- if .Values.rabbitmq.persistence }} + {{- if .Values.rabbitmq.persistence.enabled }} + storage: {{ .Values.rabbitmq.persistence.storage | default "10Gi" | quote }} + {{- if .Values.rabbitmq.persistence.storageClassName }} + storageClassName: {{ .Values.rabbitmq.persistence.storageClassName | quote }} + {{- end }} + {{- else if .Values.rabbitmq.persistence.emptyDir }} + storage: "0Gi" + {{- else }} + storage: "0Gi" + {{- end }} + {{- else }} + storage: "10Gi" + {{- end }} + + {{- if .Values.rabbitmq.affinity }} + affinity: + {{- toYaml .Values.rabbitmq.affinity | nindent 4 }} + {{- end }} + + {{- if .Values.rabbitmq.tolerations }} + tolerations: + {{- toYaml .Values.rabbitmq.tolerations | nindent 4 }} + {{- end }} + + {{- if .Values.rabbitmq.service }} + service: + type: {{ .Values.rabbitmq.service.type | default "ClusterIP" }} + {{- if .Values.rabbitmq.service.annotations }} + annotations: + {{- toYaml .Values.rabbitmq.service.annotations | nindent 6 }} + {{- end }} + {{- if .Values.rabbitmq.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.rabbitmq.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.rabbitmq.service.labels }} + labels: + {{- toYaml .Values.rabbitmq.service.labels | nindent 6 }} + {{- end }} + {{- end }} + + {{- if .Values.rabbitmq.tls.enabled }} + tls: + {{- if .Values.rabbitmq.tls.secretName }} + secretName: {{ .Values.rabbitmq.tls.secretName }} + {{- end }} + {{- if .Values.rabbitmq.tls.caSecretName }} + caSecretName: {{ .Values.rabbitmq.tls.caSecretName }} + {{- end }} + {{- if .Values.rabbitmq.tls.disableNonTLSListeners }} + disableNonTLSListeners: true + {{- end }} + {{- end }} + + {{- if or .Values.rabbitmq.auth.existingSecret .Values.rabbitmq.secretBackend.vault.enabled }} + # Reference to existing secret containing RabbitMQ credentials + # If not provided, the operator will auto-generate credentials + secretBackend: + {{- if .Values.rabbitmq.secretBackend.vault.enabled }} + vault: + role: {{ required "rabbitmq.secretBackend.vault.role is required when vault is enabled" .Values.rabbitmq.secretBackend.vault.role }} + {{- if .Values.rabbitmq.secretBackend.vault.annotations }} + annotations: + {{- toYaml .Values.rabbitmq.secretBackend.vault.annotations | nindent 8 }} + {{- end }} + {{- if .Values.rabbitmq.secretBackend.vault.defaultUserPath }} + defaultUserPath: {{ .Values.rabbitmq.secretBackend.vault.defaultUserPath }} + {{- end }} + {{- if .Values.rabbitmq.secretBackend.vault.defaultUserUpdaterImage }} + defaultUserUpdaterImage: {{ .Values.rabbitmq.secretBackend.vault.defaultUserUpdaterImage }} + {{- end }} + {{- if .Values.rabbitmq.secretBackend.vault.tls }} + tls: + {{- toYaml .Values.rabbitmq.secretBackend.vault.tls | nindent 8 }} + {{- end }} + {{- else }} + externalSecret: + name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- end }} + {{- end }} + + {{- if or .Values.rabbitmq.nodeSelector .Values.rabbitmq.override.service .Values.rabbitmq.override.statefulSet }} + override: + {{- if .Values.rabbitmq.override.service }} + service: + {{- toYaml .Values.rabbitmq.override.service | nindent 6 }} + {{- end }} + {{- if or .Values.rabbitmq.nodeSelector .Values.rabbitmq.override.statefulSet }} + statefulSet: + {{- if .Values.rabbitmq.override.statefulSet }} + {{- toYaml .Values.rabbitmq.override.statefulSet | nindent 6 }} + {{- else if .Values.rabbitmq.nodeSelector }} + spec: + template: + spec: + nodeSelector: + {{- toYaml .Values.rabbitmq.nodeSelector | nindent 14 }} + {{- end }} + {{- end }} + {{- end }} + + {{- if .Values.rabbitmq.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.rabbitmq.terminationGracePeriodSeconds }} + {{- end }} + + {{- if .Values.rabbitmq.delayStartSeconds }} + delayStartSeconds: {{ .Values.rabbitmq.delayStartSeconds }} + {{- end }} + + {{- if .Values.rabbitmq.autoEnableAllFeatureFlags }} + autoEnableAllFeatureFlags: true + {{- end }} + + {{- if .Values.rabbitmq.skipPostDeploySteps }} + skipPostDeploySteps: true + {{- end }} + +{{- end }} diff --git a/templates/secrets_rabbitmq.yaml b/templates/secrets_rabbitmq.yaml deleted file mode 100644 index a09f1ddd..00000000 --- a/templates/secrets_rabbitmq.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.rabbitmq.enabled }} -# This configuration is a workaround to https://github.com/bitnami/charts/issues/4635 -# This code block should be dropped once the above issue is resolved and definitions can be defined as shown in -# https://github.com/bitnami/charts/tree/master/bitnami/rabbitmq#load-definitions ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-rabbitmq-definitions - annotations: - description: A rabbitmq definition which will be loaded by the rabbitmq subchart to enable mirroring for Rabbit HA - labels: {{- include "stackstorm-ha.labels" (list $ "st2") | nindent 4 }} -type: Opaque -data: - rabbitmq-definitions.json: {{ tpl (.Files.Get "conf/rabbit-definition.conf") . | b64enc }} -{{- end }} diff --git a/values.yaml b/values.yaml index 01f751cf..5dc24df8 100644 --- a/values.yaml +++ b/values.yaml @@ -1024,127 +1024,320 @@ jobs: # - custom_pack.warn_about_upgrade ## -## MongoDB HA configuration (3rd party chart dependency) +## MongoDB Community Operator Configuration ## -## For values.yaml reference: -## https://github.com/bitnami/charts/tree/master/bitnami/mongodb +## PREREQUISITE: MongoDB Community Operator must be installed in the cluster +## Install CRDs: kubectl apply -f kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/crds.yaml +## Install Operator: kubectl apply -f kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/mongodb-kubernetes.yaml +## +## For MongoDBCommunity CRD reference: +## https://github.com/mongodb/mongodb-kubernetes-operator/blob/master/docs/deploy-configure.md ## -mongodb: +mongodbCommunity: # Change to `false` to disable in-cluster mongodb deployment. # Specify your external [database] connection parameters under st2.config enabled: true - image: - # StackStorm currently supports maximum MongoDB v4.4 - tag: "4.4" - # MongoDB architecture. Allowed values: standalone or replicaset - architecture: replicaset + + # MongoDB version - StackStorm currently supports maximum MongoDB v4.4 + version: "7.0.22" + + # Number of MongoDB replicas in the replica set + members: 3 + # Name of the replica set - # Ignored when mongodb.architecture=standalone replicaSetName: rs0 - # Number of MongoDB replicas to deploy. - # Ignored when mongodb.architecture=standalone - replicaCount: 3 + + # Authentication configuration auth: enabled: true - # NB! It's highly recommended to change ALL insecure auth defaults! - # - # Stackstorm user that services will make connections using - username: "st2-admin" - # Stackstorm user that services will make connections using - password: "XeL5Rxwj7F0Wt43tFZVTN7H8Sg5XDHmK" - # Root password for the database - rootPassword: "8fAzdnksdzPFUWm4a68EfY7nMhBPaa" - # Initial database for stackstorm + # Database for StackStorm database: "st2" - # Minimal key length is 6 symbols and may only contain characters in the base64 set. - replicaSetKey: "82PItDpqroti5RngOA7UqbHH7c6bFUwy" - # Whether to enable the arbiter - arbiter: + # Username for StackStorm connections + username: "st2-admin" + + # IMPORTANT: Credentials should be stored in a Kubernetes secret, not in values.yaml + # Create the secret before installing this chart: + # + # Example using kubectl: + # kubectl create secret generic mongodb-credentials \ + # --from-literal=password=YOUR_SECURE_ST2_PASSWORD \ + # --from-literal=rootPassword=YOUR_SECURE_ROOT_PASSWORD + # + # Name of existing Kubernetes secret containing MongoDB credentials + existingSecret: "" # e.g., "mongodb-credentials" + # Keys within the secret (defaults shown below) + passwordKey: "password" + rootPasswordKey: "rootPassword" + + # Persistence configuration + persistence: + enabled: true + # Storage size per MongoDB pod + storage: "10Gi" + # Optional: specify storageClassName (uses cluster default when empty) + storageClassName: "" + + # TLS configuration (disabled by default) + tls: enabled: false + # When enabled, you must provide these secrets containing TLS certificates: + # certificateKeySecretRef: mongodb-tls-cert + # caCertificateSecretRef: mongodb-tls-ca + # See: https://github.com/mongodb/mongodb-kubernetes-operator/blob/master/docs/deploy-configure.md#tls-configuration + + # Resource requests and limits for MongoDB pods resources: {} + # limits: + # cpu: 2000m + # memory: 2Gi + # requests: + # cpu: 1000m + # memory: 2Gi + + # Pod placement controls + affinity: {} + nodeSelector: {} + tolerations: [] ## -## RabbitMQ configuration (3rd party chart dependency) +## RabbitMQ configuration (using RabbitMQ Cluster Operator) ## -## For values.yaml reference: -## https://github.com/bitnami/charts/tree/master/bitnami/rabbitmq +## PREREQUISITE: RabbitMQ Cluster Operator must be installed in the cluster +## Install with: kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml +## +## For RabbitmqCluster spec reference: +## https://www.rabbitmq.com/kubernetes/operator/using-operator.html ## rabbitmq: # Change to `false` to disable in-cluster rabbitmq deployment. # Specify your external [messaging] connection parameters under st2.config enabled: true - clustering: - # On unclean cluster restarts forceBoot is required to cleanup Mnesia tables (see: https://github.com/helm/charts/issues/13485) - # Use it only if you prefer availability over integrity. - forceBoot: true - # Authentication Details + + # Number of RabbitMQ nodes in the cluster (replicas) + replicas: 3 + + # Authentication configuration + # If existingSecret is provided, the operator will use it for RabbitMQ credentials + # If empty, the operator will auto-generate credentials + # + # The secret must contain three keys: username, password, erlangCookie + # + # Example: Create the secret before installing this chart: + # kubectl create secret generic rabbitmq-credentials \ + # --from-literal=username=admin \ + # --from-literal=password=YOUR_SECURE_PASSWORD \ + # --from-literal=erlangCookie=YOUR_SECURE_ERLANG_COOKIE + # + # The erlangCookie should be an alphanumeric string, typically 20-40 characters. + # Generate a secure erlangCookie with: openssl rand -base64 32 auth: - username: admin - # TODO: Use default random 10 character password, but need to fetch this string for use by downstream services - password: 9jS+w1u07NbHtZke1m+jW4Cj - # Up to 255 character string, should be fixed so that re-deploying the chart does not fail (see: https://github.com/helm/charts/issues/12371) - # NB! It's highly recommended to change the default insecure rabbitmqErlangCookie value! - erlangCookie: 8MrqQdCQ6AQ8U3MacSubHE5RqkSfvNaRHzvxuFcG - # RabbitMQ Memory high watermark. See: http://www.rabbitmq.com/memory.html - # Default values might not be enough for StackStorm deployment to work properly. We recommend to adjust these settings for you needs as well as enable Pod memory limits via "resources". - #rabbitmqMemoryHighWatermark: 512MB - #rabbitmqMemoryHighWatermarkType: absolute + existingSecret: "" # e.g., "rabbitmq-credentials" + + # Persistence configuration persistence: enabled: true - # Enable Queue Mirroring between nodes - # See https://www.rabbitmq.com/ha.html - # This code block is commented out waiting for - # https://github.com/bitnami/charts/issues/4635 - loadDefinition: - enabled: true - existingSecret: "{{ .Release.Name }}-rabbitmq-definitions" - extraConfiguration: | - load_definitions = /app/rabbitmq-definitions.json - # We recommend to set the memory limit for RabbitMQ-HA Pods in production deployments. - # Make sure to also change the rabbitmqMemoryHighWatermark following the formula: - # rabbitmqMemoryHighWatermark = 0.4 * resources.limits.memory + # Storage size per RabbitMQ pod + storage: "10Gi" + # Optional: specify storageClassName + # storageClassName: "" + # Alternative to persistent storage: use emptyDir (ephemeral storage) + # When emptyDir is defined, it takes precedence over persistent storage + emptyDir: {} + # medium: "" # "" (default) or "Memory" for tmpfs + # sizeLimit: "" # e.g., "1Gi" + + # Optional: Override RabbitMQ docker image + # image: rabbitmq:3.13-management + + # Optional: Image pull secrets for private registries + imagePullSecrets: [] + # - name: my-registry-secret + + # Resource requests and limits for RabbitMQ pods + # We recommend setting memory limits in production deployments resources: {} - # number of replicas in the rabbit cluster - replicaCount: 3 - # As RabbitMQ enabled prometheus operator monitoring by default, disable it for non-prometheus users - metrics: + # limits: + # cpu: 2000m + # memory: 2Gi + # requests: + # cpu: 1000m + # memory: 2Gi + + # Service configuration + service: + type: ClusterIP + annotations: {} + # IP family policy for dual-stack support + # Options: "SingleStack", "PreferDualStack", "RequireDualStack" + ipFamilyPolicy: "" + # Additional labels for the service + labels: {} + + # RabbitMQ configuration files + # Additional RabbitMQ configuration (rabbitmq.conf format) + # See: https://www.rabbitmq.com/configure.html + additionalConfig: | + # Enable queue mirroring for HA + # cluster_partition_handling = autoheal + + # Advanced RabbitMQ configuration (advanced.config format) + # See: https://www.rabbitmq.com/configure.html#advanced-config-file + advancedConfig: "" + + # Environment variable configuration (rabbitmq-env.conf format) + # See: https://www.rabbitmq.com/configure.html#customise-environment + envConfig: "" + + # Erlang inet configuration (erl_inetrc format) + # See: https://www.erlang.org/doc/apps/erts/inet_cfg.html + erlangInetConfig: "" + + # Additional RabbitMQ plugins to enable + # Default plugins (rabbitmq_management, rabbitmq_prometheus, rabbitmq_peer_discovery_k8s) are always enabled + additionalPlugins: [] + # - rabbitmq_shovel + # - rabbitmq_federation + + # TLS configuration + tls: enabled: false + # Name of secret containing tls.crt and tls.key + secretName: "" + # Name of secret containing ca.crt (optional) + caSecretName: "" + # Disable non-TLS listeners when TLS is enabled + disableNonTLSListeners: false + + # Secret backend configuration + # By default, uses externalSecret (Kubernetes secret) + # Alternatively, can use Vault for credential management + secretBackend: + # Vault configuration (alternative to externalSecret) + vault: + enabled: false + # Vault role for RabbitMQ + role: "" + # Annotations for Vault agent + annotations: {} + # Path to default user credentials in Vault + defaultUserPath: "" + # Image for default user updater sidecar + defaultUserUpdaterImage: "" + # TLS configuration for Vault + tls: {} + + # Override configurations for advanced customization + # These allow fine-grained control over the generated resources + override: {} + # Override service configuration + #service: {} + # spec: + # type: LoadBalancer + # ports: + # - name: additional-port + # port: 15672 + # Override statefulSet configuration + #statefulSet: + # spec: + # template: + # spec: + # # nodeSelector should be defined here for proper CRD structure + # nodeSelector: {} + + # Lifecycle settings + # Grace period for pod termination (default: 604800 = 7 days) + terminationGracePeriodSeconds: 604800 + # Delay before starting RabbitMQ (default: 30 seconds) + delayStartSeconds: 30 + + # Automatically enable all feature flags on startup + autoEnableAllFeatureFlags: false + + # Skip post-deployment steps performed by the operator + skipPostDeploySteps: false + + # Pod placement controls + # NOTE: nodeSelector is deprecated at this level. Use override.statefulSet.spec.template.spec.nodeSelector instead + affinity: {} + nodeSelector: {} + tolerations: [] ## -## Redis HA configuration (3rd party chart dependency) +## Valkey Configuration (using Valkey Helm Chart) ## -## For values.yaml reference: -## https://github.com/bitnami/charts/tree/master/bitnami/redis +## For Valkey chart reference: +## https://github.com/valkey-io/valkey-helm ## -redis: - # Change to `false` to disable in-cluster redis deployment. +valkey: + # Change to `false` to disable in-cluster valkey deployment. # Specify your external [coordination] connection parameters under st2.config enabled: true - # By default the cluster is enabled for the subchart. - # We just need replica count here to ensure it is HA - cluster: - slaveCount: 3 - # Sentinel settings. Sentinel is enabled for better resiliency. - # This is highly recommended as per tooz library documentation. - # Hence, the chart requires the setting. - # https://docs.openstack.org/tooz/latest/user/drivers.html#redis - # https://github.com/bitnami/charts/tree/master/bitnami/redis#master-slave-with-sentinel - sentinel: + + # Valkey image configuration + image: + registry: "docker.io" + repository: valkey/valkey + tag: "" # Uses chart's appVersion by default + pullPolicy: IfNotPresent + + # Authentication configuration + auth: enabled: true - # DO NOT SET sentinel.usePassword, the tooz driver cannot connect to a password protected sentinel - # however it can connect to a password protected redis that is being managed by sentinel - # usePassword: false - # Enable or disable static sentinel IDs for each replicas - # If disabled each sentinel will generate a random id at startup - # If enabled, each replicas will have a constant ID on each start-up - staticID: true - # if redis.usePassword is true, you can set the password here - # password: mysupersecretpassword - networkPolicy: - enabled: false - usePassword: false - metrics: - enabled: false + # IMPORTANT: Credentials should be stored in a Kubernetes secret, not in values.yaml + # Create the secret before installing this chart: + # + # Example using kubectl: + # kubectl create secret generic valkey-credentials \ + # --from-literal=default-password=YOUR_SECURE_PASSWORD + # + # Name of existing Kubernetes secret containing Valkey credentials + usersExistingSecret: "" # e.g., "valkey-credentials" + + # ACL users configuration + # The 'default' user must be defined when auth is enabled + aclUsers: + default: + permissions: "~* &* +@all" + password: "" # Set this or use usersExistingSecret + # passwordKey: "default-password" # Key in usersExistingSecret + + # Replica configuration for HA coordination backend + replica: + enabled: true + # Number of replica instances (total pods = replicas + 1 master) + replicas: 2 + # Username for replicas to authenticate to master + replicationUser: "default" + # Persistence configuration (required for replicas) + persistence: + size: "10Gi" + storageClass: "" # Uses cluster default if empty + accessModes: + - ReadWriteOnce + # Read service configuration + service: + enabled: true + type: ClusterIP + port: 6379 + + # Service configuration + service: + type: ClusterIP + port: 6379 + + # Resource requests and limits + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + + # Pod placement controls + affinity: {} + nodeSelector: {} + tolerations: [] ## ## Settings to be applied to all stackstorm-ha pods From 978a0c42ddf697070543ba15514fa724d269091e Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Tue, 17 Mar 2026 08:35:25 -0400 Subject: [PATCH 02/13] add liveness probe; remove packs.initcontainers not sure why i did this --- templates/deployments.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/templates/deployments.yaml b/templates/deployments.yaml index bdff8cdf..0b64238f 100644 --- a/templates/deployments.yaml +++ b/templates/deployments.yaml @@ -189,9 +189,6 @@ spec: initContainers: {{- include "stackstorm-ha.init-containers-wait-for-db" . | nindent 6 }} {{- include "stackstorm-ha.init-containers-wait-for-mq" . | nindent 6 }} - {{- if and .Values.st2.packs.images (not .Values.st2.packs.volumes.enabled) }} - {{- include "stackstorm-ha.packs-initContainers" . | nindent 6 }} - {{- end }} terminationGracePeriodSeconds: {{ .Values.st2api.terminationGracePeriodSeconds | default 30 }} containers: - name: st2api @@ -203,8 +200,15 @@ spec: ports: - containerPort: 9101 # TODO: Add liveness/readiness probes (#3) - #livenessProbe: - #readinessProbe: + readinessProbe: + exec: + command: + - /bin/sh + - -c + - curl -s -o /dev/null -w "%{http_code}" 127.0.0.1:9101 || (curl -s 127.0.0.1:9101 | grep -q '{"faultstring":"Unauthorized - One of Token or API key required."}' && exit 0 || exit 1) + initialDelaySeconds: 30 + periodSeconds: 30 + command: {{- include "stackstorm-ha.st2-entrypoint" $ | nindent 10 }} - /opt/stackstorm/st2/bin/st2api From 02d904ed1a44b5c740d978adaffd5b13cb91b7b8 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Fri, 20 Mar 2026 11:45:57 -0400 Subject: [PATCH 03/13] mongodb redis rabbit changes --- templates/_helpers.tpl | 41 ++++- templates/configmaps_st2-conf.yaml | 35 ++-- templates/configmaps_st2-db-init.yaml | 49 ++++++ templates/jobs.yaml | 94 ++++++++++- templates/mongodb-community.yaml | 77 +++++---- templates/mongodb-rbac.yaml | 37 ++++ templates/rabbitmq-cluster.yaml | 10 +- templates/secrets_st2-mongodb.yaml | 28 +++ templates/secrets_st2-rabbitmq.yaml | 25 +++ templates/secrets_st2-valkey.yaml | 22 +++ templates/service-account.yaml | 8 + values.yaml | 235 ++++++++++++++++++++++---- 12 files changed, 570 insertions(+), 91 deletions(-) create mode 100644 templates/configmaps_st2-db-init.yaml create mode 100644 templates/mongodb-rbac.yaml create mode 100644 templates/secrets_st2-mongodb.yaml create mode 100644 templates/secrets_st2-rabbitmq.yaml create mode 100644 templates/secrets_st2-valkey.yaml diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 0e5370a5..98791119 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -164,6 +164,21 @@ Reduce duplication of the st2.*.conf volume details - name: st2-config-vol mountPath: /etc/st2/st2.user.conf subPath: st2.user.conf +{{- if and .Values.mongodbCommunity.enabled (index .Values "mongodbCommunity" "auth" "st2User" "existingSecret") }} +- name: st2-mongodb-config-vol + mountPath: /etc/st2/st2.mongodb.conf + subPath: st2.secrets.conf +{{- end }} +{{- if and .Values.rabbitmq.enabled (index .Values "rabbitmq" "auth" "existingSecret") }} +- name: st2-rabbitmq-config-vol + mountPath: /etc/st2/st2.rabbitmq.conf + subPath: st2.rabbitmq.conf +{{- end }} +{{- if and .Values.valkey.enabled .Values.valkey.auth.enabled .Values.valkey.auth.usersExistingSecret }} +- name: st2-valkey-config-vol + mountPath: /etc/st2/st2.valkey.conf + subPath: st2.valkey.conf +{{- end }} {{- if $.Values.st2.existingConfigSecret }} - name: st2-config-secrets-vol mountPath: /etc/st2/st2.secrets.conf @@ -174,6 +189,21 @@ Reduce duplication of the st2.*.conf volume details - name: st2-config-vol configMap: name: {{ $.Release.Name }}-st2-config +{{- if and .Values.mongodbCommunity.enabled (index .Values "mongodbCommunity" "auth" "st2User" "existingSecret") }} +- name: st2-mongodb-config-vol + secret: + secretName: {{ $.Release.Name }}-st2-mongodb-config +{{- end }} +{{- if and .Values.rabbitmq.enabled (index .Values "rabbitmq" "auth" "existingSecret") }} +- name: st2-rabbitmq-config-vol + secret: + secretName: {{ $.Release.Name }}-st2-rabbitmq-config +{{- end }} +{{- if and .Values.valkey.enabled .Values.valkey.auth.enabled .Values.valkey.auth.usersExistingSecret }} +- name: st2-valkey-config-vol + secret: + secretName: {{ $.Release.Name }}-st2-valkey-config +{{- end }} {{- if $.Values.st2.existingConfigSecret }} - name: st2-config-secrets-vol secret: @@ -187,11 +217,20 @@ Reduce duplication of the st2.*.conf volume details {{- end }} {{- end -}} -# Override CMD CLI parameters passed to the startup of all pods to add support for /etc/st2/st2.secrets.conf +# Override CMD CLI parameters passed to the startup of all pods to add support for backend credential config files {{- define "stackstorm-ha.st2-config-file-parameters" -}} - --config-file=/etc/st2/st2.conf - --config-file=/etc/st2/st2.docker.conf - --config-file=/etc/st2/st2.user.conf +{{- if and .Values.mongodbCommunity.enabled (index .Values "mongodbCommunity" "auth" "st2User" "existingSecret") }} +- --config-file=/etc/st2/st2.mongodb.conf +{{- end }} +{{- if and .Values.rabbitmq.enabled (index .Values "rabbitmq" "auth" "existingSecret") }} +- --config-file=/etc/st2/st2.rabbitmq.conf +{{- end }} +{{- if and .Values.valkey.enabled .Values.valkey.auth.enabled .Values.valkey.auth.usersExistingSecret }} +- --config-file=/etc/st2/st2.valkey.conf +{{- end }} {{- if $.Values.st2.existingConfigSecret }} - --config-file=/etc/st2/st2.secrets.conf {{- end }} diff --git a/templates/configmaps_st2-conf.yaml b/templates/configmaps_st2-conf.yaml index 5c2aa28d..863867b3 100644 --- a/templates/configmaps_st2-conf.yaml +++ b/templates/configmaps_st2-conf.yaml @@ -15,37 +15,26 @@ data: [system_user] user = {{ .Values.st2.system_user.user }} ssh_key_file = {{ tpl .Values.st2.system_user.ssh_key_file . }} - {{- if index .Values "valkey" "enabled" }} - [coordination] - url = redis://{{ template "stackstorm-ha.valkey-connection" $ }} - {{- end }} - {{- if index .Values "rabbitmq" "enabled" }} - [messaging] - url = amqp://${RABBITMQ_USERNAME}:${RABBITMQ_PASSWORD}@{{ .Release.Name }}-rabbitmq:5672/ - {{- end }} - {{- if index .Values "mongodbCommunity" "enabled" }} - [database] - {{- if index .Values "mongodbCommunity" "auth" "enabled" }} - host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }}?authSource={{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }}&replicaSet={{ index .Values "mongodbCommunity" "replicaSetName" }} - username = {{ required "mongodbCommunity.auth.username is required!" (index .Values "mongodbCommunity" "auth" "username") }} - password = ${MONGODB_PASSWORD} - db_name = {{ required "mongodbCommunity.auth.database is required!" (index .Values "mongodbCommunity" "auth" "database") }} - {{- else }} - host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/?replicaSet={{ index .Values "mongodbCommunity" "replicaSetName" }} - {{- end }} - port = 27017 - {{- else if index .Values "mongodb" "enabled" }} + {{- if index .Values "mongodb" "enabled" }} [database] {{- if index .Values "mongodb" "auth" "enabled" }} - host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}?authSource={{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}&replicaSet={{ index .Values "mongodb" "replicaSetName" }} - username = {{ required "mongodb.auth.username is required!" (index .Values "mongodb" "auth" "username") }} + host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}?authSource={{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }}&replicaSet={{ .Release.Name }}-mongodb + username = {{ required "mongodb.auth.username is required!" (index .Values "mongodb.auth.username") }} password = {{ required "mongodb.auth.password is required!" (index .Values "mongodb" "auth" "password") }} db_name = {{ required "mongodb.auth.database is required!" (index .Values "mongodb" "auth" "database") }} {{- else }} - host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/?replicaSet={{ index .Values "mongodb" "replicaSetName" }} + host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/?replicaSet={{ .Release.Name }}-mongodb {{- end }} port = {{ index .Values "mongodb" "service" "port" }} {{- end }} + {{- if and .Values.rabbitmq.enabled (not (index .Values "rabbitmq" "auth" "existingSecret")) }} + [messaging] + url = amqp://guest:guest@{{ .Release.Name }}-rabbitmq:5672/ + {{- end }} + {{- if and .Values.valkey.enabled (not .Values.valkey.auth.usersExistingSecret) }} + [coordination] + url = redis://{{ .Release.Name }}-valkey.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:6379 + {{- end }} {{- if ne "disable" (default "" .Values.st2.datastore_crypto_key) }} [keyvalue] encryption_key_path = /etc/st2/keys/datastore_key.json diff --git a/templates/configmaps_st2-db-init.yaml b/templates/configmaps_st2-db-init.yaml new file mode 100644 index 00000000..075dc9bc --- /dev/null +++ b/templates/configmaps_st2-db-init.yaml @@ -0,0 +1,49 @@ +{{- if .Values.st2.rbac.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-st2-db-init-script + labels: {{- include "stackstorm-ha.labels" (list $ "st2-db-init") | nindent 4 }} +data: + st2-db-init.py: | + #!/opt/stackstorm/st2/bin/python + """ + Initialize StackStorm database with base RBAC roles. + This script calls service_setup.db_setup() to inject base roles into the database. + """ + import sys + import logging + from st2common import config + from st2common.rbac.migrations import run_all as run_all_rbac_migrations + from st2common.database_setup import db_setup + + __all__ = ["main"] + + # Configure logging + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(levelname)s - %(message)s' + ) + logger = logging.getLogger(__name__) + + def main(): + """Main function to initialize database with base RBAC roles.""" + # Get config file arguments from command line + # Expected format: --config-file /etc/st2/st2.conf --config-file /etc/st2/st2.docker.conf ... + config_args = sys.argv[1:] + config.parse_args(config_args) + + logger.info("Starting database initialization with base RBAC roles...") + logger.info(f"Config arguments: {config_args}") + + # Call db_setup to initialize database with base roles + db_setup() + run_all_rbac_migrations() + + logger.info("Database initialization completed successfully!") + sys.exit(0) + + if __name__ == "__main__": + main() +{{- end }} diff --git a/templates/jobs.yaml b/templates/jobs.yaml index dfbca877..ea7973f6 100644 --- a/templates/jobs.yaml +++ b/templates/jobs.yaml @@ -1,4 +1,96 @@ -{{- if and .Values.st2.rbac.enabled (not (.Values.jobs.skip | has "apply_rbac_definitions")) -}} +{{- if and .Values.st2.rbac.enabled (not (.Values.jobs.skip | has "initialize_rbac")) -}} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-job-st2-initialize-rbac + labels: {{- include "stackstorm-ha.labels" (list $ "st2-initialize-rbac") | nindent 4 }} + annotations: + helm.sh/hook: post-install, post-upgrade, post-rollback + helm.sh/hook-delete-policy: before-hook-creation + helm.sh/hook-weight: "4" + {{- if .Values.jobs.annotations }} + {{- toYaml .Values.jobs.annotations | nindent 4 }} + {{- end }} +spec: + template: + metadata: + name: job-st2-initialize-rbac + labels: {{- include "stackstorm-ha.labels" (list $ "st2-initialize-rbac") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmaps_st2-conf.yaml") . | sha256sum }} + checksum/script: {{ include (print $.Template.BasePath "/configmaps_st2-db-init.yaml") . | sha256sum }} + checksum/rbac: {{ include (print $.Template.BasePath "/configmaps_rbac.yaml") . | sha256sum }} + {{- if .Values.jobs.annotations }} + {{- toYaml .Values.jobs.annotations | nindent 8 }} + {{- end }} + spec: + {{- if .Values.image.pullSecret }} + imagePullSecrets: + - name: {{ .Values.image.pullSecret }} + {{- end }} + initContainers: + {{- include "stackstorm-ha.init-containers-wait-for-db" . | nindent 6 }} + containers: + - name: st2-initialize-rbac + image: '{{ template "stackstorm-ha.imageRepository" . }}/st2actionrunner:{{ tpl (.Values.jobs.image.tag | default (.Values.st2actionrunner.image.tag | default .Values.image.tag)) . }}' + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: {{- toYaml . | nindent 10 }} + {{- end }} + command: + - /usr/bin/st2-db-init.py + {{- include "stackstorm-ha.st2-config-file-parameters" . | nindent 10 }} + {{- if .Values.jobs.env }} + env: {{- include "stackstorm-ha.customEnv" .Values.jobs | nindent 8 }} + {{- end }} + {{- if .Values.jobs.envFromSecrets }} + envFrom: + {{- range .Values.jobs.envFromSecrets }} + - secretRef: + name: {{ . }} + {{- end }} + {{- end }} + volumeMounts: + {{- include "stackstorm-ha.st2-config-volume-mounts" . | nindent 8 }} + - name: st2-db-init-script + mountPath: /usr/bin/st2-db-init.py + subPath: st2-db-init.py + {{- range $.Values.jobs.extra_volumes }} + - name: {{ required "Each volume must have a 'name' in jobs.extra_volumes" .name }} + {{- tpl (required "Each volume must have a 'mount' definition in jobs.extra_volumes" .mount | toYaml) $ | nindent 10 }} + {{- end }} + volumes: + {{- include "stackstorm-ha.st2-config-volume" . | nindent 8 }} + - name: st2-db-init-script + configMap: + name: {{ .Release.Name }}-st2-db-init-script + defaultMode: 0755 + {{- range $.Values.jobs.extra_volumes }} + - name: {{ required "Each volume must have a 'name' in jobs.extra_volumes" .name }} + {{- tpl (required "Each volume must have a 'volume' definition in jobs.extra_volumes" .volume | toYaml) $ | nindent 10 }} + {{- end }} + restartPolicy: OnFailure + {{- if .Values.dnsPolicy }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- end }} + {{- with .Values.dnsConfig }} + dnsConfig: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.jobs.nodeSelector }} + nodeSelector: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.jobs.affinity }} + affinity: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.jobs.tolerations }} + tolerations: {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} +{{- if and .Values.st2.rbac.enabled (not (.Values.jobs.skip | has "apply_rbac_definitions")) }} --- apiVersion: batch/v1 kind: Job diff --git a/templates/mongodb-community.yaml b/templates/mongodb-community.yaml index 69f35b6d..777ad683 100644 --- a/templates/mongodb-community.yaml +++ b/templates/mongodb-community.yaml @@ -1,5 +1,4 @@ {{- if index .Values "mongodbCommunity" "enabled" }} -{{- $existingSecret := index .Values "mongodbCommunity" "auth" "existingSecret" | default (printf "%s-mongodb-auth" .Release.Name) }} --- apiVersion: mongodbcommunity.mongodb.com/v1 kind: MongoDBCommunity @@ -10,32 +9,52 @@ spec: members: {{ index .Values "mongodbCommunity" "members" }} type: ReplicaSet version: {{ index .Values "mongodbCommunity" "version" | quote }} - {{- if index .Values "mongodbCommunity" "auth" "enabled" }} + agent: + {{- with index .Values "mongodbCommunity" "agent" }} + logLevel: {{ .logLevel | default "INFO" }} + maxLogFileDurationHours: {{ .maxLogFileDurationHours | default 24 }} + {{- if .logRotate }} + {{- if .logRotate.enabled }} + logRotate: + sizeThresholdMB: {{ .logRotate.sizeThresholdMB | default "1000" | quote }} + timeThresholdHrs: {{ .logRotate.timeThresholdHrs | default 24 }} + numTotal: {{ .logRotate.numTotal | default 10 }} + numUncompressed: {{ .logRotate.numUncompressed | default 3 }} + percentOfDiskspace: {{ .logRotate.percentOfDiskspace | default "5.0" | quote }} + {{- end }} + {{- end }} + {{- end }} security: authentication: - modes: ["SCRAM"] + modes: {{ index .Values "mongodbCommunity" "auth" "modes" | toJson }} users: - - name: {{ index .Values "mongodbCommunity" "auth" "username" }} - db: {{ index .Values "mongodbCommunity" "auth" "database" }} + {{- with index .Values "mongodbCommunity" "auth" "st2User" }} + - name: {{ .name }} + db: {{ .database }} passwordSecretRef: - name: {{ $existingSecret }} - key: {{ index .Values "mongodbCommunity" "auth" "passwordKey" }} - roles: - - name: readWrite - db: {{ index .Values "mongodbCommunity" "auth" "database" }} - - name: clusterAdmin - db: admin - scramCredentialsSecretName: {{ .Release.Name }}-mongodb-scram - - name: admin - db: admin + name: {{ .existingSecret | required "mongodbCommunity.auth.st2User.existingSecret is required" }} + key: {{ .passwordSecretKey }} + roles: {{- toYaml .roles | nindent 8 }} + scramCredentialsSecretName: {{ $.Release.Name }}-mongodb-{{ .name }}-scram + {{- end }} + {{- with index .Values "mongodbCommunity" "auth" "adminUser" }} + - name: {{ .name }} + db: {{ .database }} passwordSecretRef: - name: {{ $existingSecret }} - key: {{ index .Values "mongodbCommunity" "auth" "rootPasswordKey" }} - roles: - - name: root - db: admin - scramCredentialsSecretName: {{ .Release.Name }}-mongodb-admin-scram - {{- end }} + name: {{ .existingSecret | required "mongodbCommunity.auth.adminUser.existingSecret is required" }} + key: {{ .passwordSecretKey }} + roles: {{- toYaml .roles | nindent 8 }} + scramCredentialsSecretName: {{ $.Release.Name }}-mongodb-{{ .name }}-scram + {{- end }} + {{- range index .Values "mongodbCommunity" "auth" "additionalUsers" }} + - name: {{ .name }} + db: {{ .database }} + passwordSecretRef: + name: {{ .existingSecret | required "Each additionalUser must have an existingSecret defined" }} + key: {{ .passwordSecretKey }} + roles: {{- toYaml .roles | nindent 8 }} + scramCredentialsSecretName: {{ $.Release.Name }}-mongodb-{{ .name }}-scram + {{- end }} {{- if index .Values "mongodbCommunity" "tls" "enabled" }} tls: enabled: true @@ -49,16 +68,15 @@ spec: {{- end }} {{- end }} additionalMongodConfig: - storage.wiredTiger.engineConfig.journalCompressor: snappy - net.compression.compressors: snappy - {{- if index .Values "mongodbCommunity" "replicaSetName" }} - replication.replSetName: {{ index .Values "mongodbCommunity" "replicaSetName" }} - {{- end }} + storage: + wiredTiger: + engineConfig: + journalCompressor: zlib statefulSet: spec: - {{- if or (index .Values "mongodbCommunity" "nodeSelector") (index .Values "mongodbCommunity" "affinity") (index .Values "mongodbCommunity" "tolerations") (index .Values "mongodbCommunity" "resources") }} template: spec: + serviceAccountName: mongodb-database {{- with index .Values "mongodbCommunity" "nodeSelector" }} nodeSelector: {{- toYaml . | nindent 12 }} {{- end }} @@ -79,7 +97,6 @@ spec: resources: {{- toYaml . | nindent 16 }} {{- end }} {{- end }} - {{- end }} {{- if index .Values "mongodbCommunity" "persistence" "enabled" }} volumeClaimTemplates: - metadata: @@ -101,6 +118,6 @@ spec: {{- end }} resources: requests: - storage: 2Gi + storage: {{ index .Values "mongodbCommunity" "persistence" "logsStorage" }} {{- end }} {{- end }} diff --git a/templates/mongodb-rbac.yaml b/templates/mongodb-rbac.yaml new file mode 100644 index 00000000..6b250ea3 --- /dev/null +++ b/templates/mongodb-rbac.yaml @@ -0,0 +1,37 @@ +{{- if index .Values "mongodbCommunity" "enabled" }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: mongodb-database + labels: {{- include "stackstorm-ha.labels" (list $ "mongodb") | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - apiGroups: + - "" + resources: + - pods + verbs: + - patch + - delete + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: mongodb-database + labels: {{- include "stackstorm-ha.labels" (list $ "mongodb") | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: mongodb-database +subjects: + - kind: ServiceAccount + name: mongodb-database + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/templates/rabbitmq-cluster.yaml b/templates/rabbitmq-cluster.yaml index 6413bd7d..515d5906 100644 --- a/templates/rabbitmq-cluster.yaml +++ b/templates/rabbitmq-cluster.yaml @@ -106,9 +106,8 @@ spec: {{- end }} {{- end }} - {{- if or .Values.rabbitmq.auth.existingSecret .Values.rabbitmq.secretBackend.vault.enabled }} - # Reference to existing secret containing RabbitMQ credentials - # If not provided, the operator will auto-generate credentials + # Reference to existing secret containing RabbitMQ credentials (REQUIRED) + # Either rabbitmq.auth.existingSecret OR rabbitmq.secretBackend.vault must be configured secretBackend: {{- if .Values.rabbitmq.secretBackend.vault.enabled }} vault: @@ -127,11 +126,12 @@ spec: tls: {{- toYaml .Values.rabbitmq.secretBackend.vault.tls | nindent 8 }} {{- end }} - {{- else }} + {{- else if .Values.rabbitmq.auth.existingSecret }} externalSecret: name: {{ .Values.rabbitmq.auth.existingSecret }} + {{- else }} + {{- fail "rabbitmq.auth.existingSecret is required when rabbitmq is enabled (unless using rabbitmq.secretBackend.vault)" }} {{- end }} - {{- end }} {{- if or .Values.rabbitmq.nodeSelector .Values.rabbitmq.override.service .Values.rabbitmq.override.statefulSet }} override: diff --git a/templates/secrets_st2-mongodb.yaml b/templates/secrets_st2-mongodb.yaml new file mode 100644 index 00000000..05ece2ed --- /dev/null +++ b/templates/secrets_st2-mongodb.yaml @@ -0,0 +1,28 @@ +{{- if and .Values.mongodbCommunity.enabled (index .Values "mongodbCommunity" "auth" "st2User" "existingSecret") }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-st2-mongodb-config + annotations: + description: StackStorm MongoDB database credentials configuration + labels: {{- include "stackstorm-ha.labels" (list $ "st2") | nindent 4 }} +type: Opaque +stringData: + st2.secrets.conf: | + [database] + host = mongodb://{{ template "stackstorm-ha.mongodb-nodes" $ }}/{{ required "mongodbCommunity.auth.st2User.database is required!" (index .Values "mongodbCommunity" "auth" "st2User" "database") }}?authSource={{ required "mongodbCommunity.auth.st2User.database is required!" (index .Values "mongodbCommunity" "auth" "st2User" "database") }}&authMechanism=SCRAM-SHA-256&replicaSet={{ .Release.Name }}-mongodb + port = 27017 + username = {{ required "mongodbCommunity.auth.st2User.name is required!" (index .Values "mongodbCommunity" "auth" "st2User" "name") }} + {{- $mongoSecret := lookup "v1" "Secret" .Release.Namespace (index .Values "mongodbCommunity" "auth" "st2User" "existingSecret") }} + {{- if $mongoSecret }} + {{- $passwordKey := default "password" (index .Values "mongodbCommunity" "auth" "st2User" "passwordSecretKey") }} + password = {{ index $mongoSecret.data $passwordKey | b64dec }} + {{- else }} + # WARNING: MongoDB password secret not found. This will cause authentication failures. + # Secret name: {{ index .Values "mongodbCommunity" "auth" "st2User" "existingSecret" }} + # Namespace: {{ .Release.Namespace }} + password = PLACEHOLDER_PASSWORD_SECRET_NOT_FOUND + {{- end }} + db_name = {{ required "mongodbCommunity.auth.st2User.database is required!" (index .Values "mongodbCommunity" "auth" "st2User" "database") }} +{{- end }} diff --git a/templates/secrets_st2-rabbitmq.yaml b/templates/secrets_st2-rabbitmq.yaml new file mode 100644 index 00000000..c63f6e89 --- /dev/null +++ b/templates/secrets_st2-rabbitmq.yaml @@ -0,0 +1,25 @@ +{{- if and .Values.rabbitmq.enabled (not .Values.rabbitmq.secretBackend.vault.enabled) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-st2-rabbitmq-config + annotations: + description: StackStorm RabbitMQ messaging credentials configuration + labels: {{- include "stackstorm-ha.labels" (list $ "st2") | nindent 4 }} +type: Opaque +stringData: + st2.rabbitmq.conf: | + [messaging] + {{- $rabbitmqSecret := lookup "v1" "Secret" .Release.Namespace (required "rabbitmq.auth.existingSecret is required when rabbitmq is enabled!" (index .Values "rabbitmq" "auth" "existingSecret")) }} + {{- if $rabbitmqSecret }} + {{- $username := index $rabbitmqSecret.data "username" | b64dec }} + {{- $password := index $rabbitmqSecret.data "password" | b64dec }} + url = amqp://{{ $username }}:{{ $password }}@{{ .Release.Name }}-rabbitmq:5672/ + {{- else }} + # WARNING: RabbitMQ credentials secret not found. This will cause authentication failures. + # Secret name: {{ index .Values "rabbitmq" "auth" "existingSecret" }} + # Namespace: {{ .Release.Namespace }} + url = amqp://PLACEHOLDER_USERNAME:PLACEHOLDER_PASSWORD@{{ .Release.Name }}-rabbitmq:5672/ + {{- end }} +{{- end }} diff --git a/templates/secrets_st2-valkey.yaml b/templates/secrets_st2-valkey.yaml new file mode 100644 index 00000000..a27da922 --- /dev/null +++ b/templates/secrets_st2-valkey.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.valkey.enabled .Values.valkey.auth.enabled .Values.valkey.auth.usersExistingSecret }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-st2-valkey-config + annotations: + description: StackStorm Valkey coordination credentials configuration + labels: {{- include "stackstorm-ha.labels" (list $ "st2") | nindent 4 }} +type: Opaque +stringData: + st2.valkey.conf: | + [coordination] + {{- $valkeySecret := lookup "v1" "Secret" .Release.Namespace .Values.valkey.auth.usersExistingSecret }} + {{- if $valkeySecret }} + {{- $passwordKey := default "default-password" .Values.valkey.auth.aclUsers.default.passwordKey }} + {{- $password := index $valkeySecret.data $passwordKey | b64dec }} + url = redis://:{{ $password }}@{{ .Release.Name }}-valkey.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:6379 + {{- else }} + url = redis://{{ .Release.Name }}-valkey.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}:6379 + {{- end }} +{{- end }} diff --git a/templates/service-account.yaml b/templates/service-account.yaml index d1e77850..ecbb675d 100644 --- a/templates/service-account.yaml +++ b/templates/service-account.yaml @@ -17,3 +17,11 @@ imagePullSecrets: - name: "{{ .Values.serviceAccount.pullSecret }}" {{- end }} {{- end }} +{{- if index .Values "mongodbCommunity" "enabled" }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: mongodb-database + labels: {{- include "stackstorm-ha.labels" (list $ "mongodb") | nindent 4 }} +{{- end }} diff --git a/values.yaml b/values.yaml index 5dc24df8..ac5025f7 100644 --- a/values.yaml +++ b/values.yaml @@ -1023,12 +1023,20 @@ jobs: # - --tail # - custom_pack.warn_about_upgrade +# for a remote mongodb deployment, set st2.config.mongodb.host/port/user/password and disable in-cluster mongodb deployment by setting st2.mongodbCommunity.enabled to false +mongodb: + enabled: false + authentication: false + host: "mongodb" + port: 27017 + user: "st2-admin" + password: "CHANGE-ME-PLEASE" ## ## MongoDB Community Operator Configuration ## ## PREREQUISITE: MongoDB Community Operator must be installed in the cluster -## Install CRDs: kubectl apply -f kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/crds.yaml -## Install Operator: kubectl apply -f kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/mongodb-kubernetes.yaml +## Install CRDs: kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/crds.yaml +## Install Operator: kubectl apply -f https://raw.githubusercontent.com/mongodb/mongodb-kubernetes/1.7.0/public/mongodb-kubernetes.yaml ## ## For MongoDBCommunity CRD reference: ## https://github.com/mongodb/mongodb-kubernetes-operator/blob/master/docs/deploy-configure.md @@ -1038,45 +1046,115 @@ mongodbCommunity: # Specify your external [database] connection parameters under st2.config enabled: true - # MongoDB version - StackStorm currently supports maximum MongoDB v4.4 + # MongoDB version version: "7.0.22" # Number of MongoDB replicas in the replica set members: 3 # Name of the replica set - replicaSetName: rs0 # Authentication configuration + # NOTE: The MongoDB Community Operator REQUIRES user definitions with passwords. + # This is a CRD requirement - users and security fields cannot be omitted. auth: - enabled: true - # Database for StackStorm - database: "st2" - # Username for StackStorm connections - username: "st2-admin" + # Authentication modes - controls whether MongoDB enforces authentication + # modes: ["SCRAM"] - Enforce SCRAM authentication (recommended for production) + # modes: [] - No authentication enforcement (development only) + modes: ["SCRAM"] - # IMPORTANT: Credentials should be stored in a Kubernetes secret, not in values.yaml - # Create the secret before installing this chart: - # - # Example using kubectl: - # kubectl create secret generic mongodb-credentials \ - # --from-literal=password=YOUR_SECURE_ST2_PASSWORD \ - # --from-literal=rootPassword=YOUR_SECURE_ROOT_PASSWORD - # - # Name of existing Kubernetes secret containing MongoDB credentials - existingSecret: "" # e.g., "mongodb-credentials" - # Keys within the secret (defaults shown below) - passwordKey: "password" - rootPasswordKey: "rootPassword" + # Required StackStorm application user configuration + # Each user must have a separate Kubernetes secret containing their password + st2User: + name: "st2-admin" + database: "st2" + # REQUIRED: Name of Kubernetes secret containing this user's password + # Example: Create the secret before installing this chart: + # kubectl create secret generic mongodb-st2-user-secret \ + # --from-literal=password=YOUR_SECURE_ST2_PASSWORD + existingSecret: "mongodb-st2-user-secret" + # Key in the secret containing the password (default: "password") + passwordSecretKey: "password" + roles: + - name: readWrite + db: st2 + - name: clusterAdmin + db: admin + + # Required MongoDB admin user configuration + # Each user must have a separate Kubernetes secret containing their password + adminUser: + name: "admin" + database: "admin" + # REQUIRED: Name of Kubernetes secret containing this user's password + # Example: Create the secret before installing this chart: + # kubectl create secret generic mongodb-admin-user-secret \ + # --from-literal=password=YOUR_SECURE_ADMIN_PASSWORD + existingSecret: "mongodb-admin-user-secret" + # Key in the secret containing the password (default: "password") + passwordSecretKey: "password" + roles: + - name: root + db: admin + + # Optional: Additional MongoDB users + # Each user must have their own separate Kubernetes secret + additionalUsers: [] + # Example additional users (each with their own secret): + # - name: "myapp-user" + # database: "myapp" + # # Create secret: kubectl create secret generic mongodb-myapp-user-secret --from-literal=password=MYAPP_PASSWORD + # existingSecret: "mongodb-myapp-user-secret" + # passwordSecretKey: "password" + # roles: + # - name: readWrite + # db: myapp + # - name: "readonly-user" + # database: "st2" + # # Create secret: kubectl create secret generic mongodb-readonly-user-secret --from-literal=password=READONLY_PASSWORD + # existingSecret: "mongodb-readonly-user-secret" + # passwordSecretKey: "password" + # roles: + # - name: read + # db: st2 # Persistence configuration persistence: enabled: true - # Storage size per MongoDB pod + # Storage size per MongoDB pod (data volume) storage: "10Gi" + # Storage size for MongoDB logs volume + # MongoDB Community Operator requires a separate logs volume + logsStorage: "2Gi" # Optional: specify storageClassName (uses cluster default when empty) storageClassName: "" + # Agent configuration (automation agent settings) + agent: + # Log level for the automation agent + logLevel: ERROR + # Maximum duration in hours for a single log file + maxLogFileDurationHours: 24 + # Log rotation configuration + # Helps manage log file sizes and retention + logRotate: + # Enable log rotation (recommended for production) + enabled: true + # Rotate when log file reaches this size (in MB) + # Can be fractional (e.g., "0.5" for 512KB) + sizeThresholdMB: "1000" + # Rotate log files every N hours + timeThresholdHrs: 24 + # Maximum number of log files to keep (total) + # Older files beyond this limit are deleted + numTotal: 10 + # Number of recent log files to keep uncompressed + # Older files are compressed to save space + numUncompressed: 3 + # Maximum percentage of disk space logs can consume + # Helps prevent logs from filling the disk + percentOfDiskspace: "90.0" + # TLS configuration (disabled by default) tls: enabled: false @@ -1116,23 +1194,117 @@ rabbitmq: # Number of RabbitMQ nodes in the cluster (replicas) replicas: 3 - # Authentication configuration - # If existingSecret is provided, the operator will use it for RabbitMQ credentials - # If empty, the operator will auto-generate credentials + # Authentication configuration (REQUIRED when rabbitmq.enabled is true) + # You must provide either: + # 1. rabbitmq.auth.existingSecret (Kubernetes secret), OR + # 2. rabbitmq.secretBackend.vault.enabled (Vault integration) # - # The secret must contain three keys: username, password, erlangCookie + # The secret must contain four keys: username, password, default_user.conf, erlangCookie # # Example: Create the secret before installing this chart: # kubectl create secret generic rabbitmq-credentials \ - # --from-literal=username=admin \ + # --from-literal=username=st2-rabbitmq \ # --from-literal=password=YOUR_SECURE_PASSWORD \ - # --from-literal=erlangCookie=YOUR_SECURE_ERLANG_COOKIE + # --from-literal=default_user.conf="default_user = st2-rabbitmq + # default_pass = YOUR_SECURE_PASSWORD" \ + # --from-literal=erlangCookie=$(openssl rand -base64 32) + # + # Or using a YAML file: + # apiVersion: v1 + # kind: Secret + # metadata: + # name: rabbitmq-credentials + # type: Opaque + # stringData: + # username: st2-rabbitmq + # password: YOUR_SECURE_PASSWORD + # default_user.conf: | + # default_user = st2-rabbitmq + # default_pass = YOUR_SECURE_PASSWORD + # erlangCookie: YOUR_ERLANG_COOKIE_HERE # # The erlangCookie should be an alphanumeric string, typically 20-40 characters. # Generate a secure erlangCookie with: openssl rand -base64 32 + # + # IMPORTANT: The RabbitMQ operator requires the default_user.conf key for authentication. + # StackStorm uses the username and password keys to connect to RabbitMQ. + # + # ============================================================================ + # CRITICAL TIMING REQUIREMENT: CREATE SECRET BEFORE DEPLOYING RABBITMQ + # ============================================================================ + # + # The RabbitMQ Cluster Operator only processes the default_user.conf file during + # the INITIAL database creation when the cluster is first deployed. If you create + # the credentials secret AFTER RabbitMQ has already been deployed, the operator + # will NOT create your specified user, and authentication will fail. + # + # WHY THIS HAPPENS: + # ----------------- + # 1. The operator mounts the secret to /etc/rabbitmq/conf.d/11-default_user.conf + # 2. RabbitMQ only reads this file during initial database initialization + # 3. Once the database exists, RabbitMQ ignores changes to default_user.conf + # 4. If no secret exists at deployment time, RabbitMQ creates a random user + # (e.g., "default_user_SMlHtAb1pNld-CNSmuQ") with a random password + # 5. Creating the secret later does NOT update or create users in the existing database + # + # SYMPTOMS OF LATE SECRET CREATION: + # ---------------------------------- + # - StackStorm pods fail to connect to RabbitMQ with authentication errors + # - RabbitMQ logs show: "PLAIN login refused: user 'st2-rabbitmq' - invalid credentials" + # - The user specified in your secret does not exist in RabbitMQ + # - A random "default_user_*" user exists instead + # + # HOW TO FIX IF SECRET WAS CREATED LATE: + # --------------------------------------- + # + # Option 1: Manual User Creation (Recommended for existing deployments) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # If you already have data in RabbitMQ and want to preserve it, manually create + # the user with the credentials from your secret: + # + # 1. Get the credentials from your secret: + # USERNAME=$(kubectl get secret rabbitmq-credentials -o jsonpath='{.data.username}' | base64 -d) + # PASSWORD=$(kubectl get secret rabbitmq-credentials -o jsonpath='{.data.password}' | base64 -d) + # + # 2. Exec into a RabbitMQ pod: + # kubectl exec -it -- bash + # + # 3. Create the user and set permissions: + # rabbitmqctl add_user "$USERNAME" "$PASSWORD" + # rabbitmqctl set_user_tags "$USERNAME" administrator + # rabbitmqctl set_permissions -p / "$USERNAME" ".*" ".*" ".*" + # + # 4. Verify the user was created: + # rabbitmqctl list_users + # + # 5. Restart StackStorm pods to reconnect with the new credentials: + # kubectl rollout restart deployment -l app.kubernetes.io/component=stackstorm + # + # Option 2: Fresh Deployment (For new installations or when data can be lost) + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # If you don't need to preserve existing RabbitMQ data, perform a clean deployment: + # + # 1. Delete the RabbitMQ cluster: + # kubectl delete rabbitmqcluster + # + # 2. Delete the PersistentVolumeClaims to remove old data: + # kubectl delete pvc -l app.kubernetes.io/name= + # + # 3. Ensure your credentials secret exists: + # kubectl get secret rabbitmq-credentials + # + # 4. Redeploy the Helm chart: + # helm upgrade --install -f values.yaml + # + # PREVENTION: + # ----------- + # Always create the RabbitMQ credentials secret BEFORE deploying this Helm chart + # or before enabling rabbitmq.enabled=true. This ensures the operator can properly + # initialize the database with your specified credentials during first startup. + # + # ============================================================================ auth: - existingSecret: "" # e.g., "rabbitmq-credentials" - + existingSecret: "" # e.g., "rabbitmq-credentials" (REQUIRED unless using Vault) # Persistence configuration persistence: enabled: true @@ -1165,6 +1337,7 @@ rabbitmq: # Service configuration service: + port: 5672 type: ClusterIP annotations: {} # IP family policy for dual-stack support From a4b2ce3c5ef7dad0b07a9542dd424ea61540d105 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 11:55:21 -0400 Subject: [PATCH 04/13] add subhcharts so that offline helm dependencies work --- .gitignore | 1 - charts/external-dns-4.0.0.tgz | Bin 0 -> 29491 bytes charts/valkey-0.9.3.tgz | Bin 0 -> 19409 bytes 3 files changed, 1 deletion(-) create mode 100644 charts/external-dns-4.0.0.tgz create mode 100644 charts/valkey-0.9.3.tgz diff --git a/.gitignore b/.gitignore index 32d4e736..4cf6b6c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -charts *.lock .DS_Store *.swp diff --git a/charts/external-dns-4.0.0.tgz b/charts/external-dns-4.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ed3987920ef398be8a1a4ab1bf0ef1aebe256de2 GIT binary patch literal 29491 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 literal 0 HcmV?d00001 From 0f077dbb586ae621b7bc61bdd6b1ca8a2b0ca6e5 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 14:19:27 -0400 Subject: [PATCH 05/13] extract valkey chart --- charts/valkey-0.9.3.tgz | Bin 19409 -> 0 bytes charts/valkey/.helmignore | 28 + charts/valkey/Chart.yaml | 14 + charts/valkey/README.md | 353 ++++++++++++ charts/valkey/templates/NOTES.txt | 137 +++++ charts/valkey/templates/_helpers.tpl | 190 +++++++ charts/valkey/templates/configmap.yaml | 11 + charts/valkey/templates/deploy_valkey.yaml | 289 ++++++++++ charts/valkey/templates/init_config.yaml | 231 ++++++++ charts/valkey/templates/metrics-svc.yaml | 29 + charts/valkey/templates/netpolicy.yaml | 33 ++ charts/valkey/templates/podmonitor.yaml | 53 ++ charts/valkey/templates/prometheusrules.yaml | 22 + charts/valkey/templates/pvc.yaml | 30 + charts/valkey/templates/secret.yaml | 20 + charts/valkey/templates/service-headless.yaml | 20 + charts/valkey/templates/service-read.yaml | 34 ++ charts/valkey/templates/service.yaml | 35 ++ charts/valkey/templates/serviceaccount.yaml | 13 + charts/valkey/templates/servicemonitor.yaml | 54 ++ charts/valkey/templates/statefulset.yaml | 279 +++++++++ charts/valkey/templates/tests/auth.yaml | 144 +++++ charts/valkey/values.schema.json | 535 ++++++++++++++++++ charts/valkey/values.yaml | 446 +++++++++++++++ 24 files changed, 3000 insertions(+) delete mode 100644 charts/valkey-0.9.3.tgz create mode 100644 charts/valkey/.helmignore create mode 100644 charts/valkey/Chart.yaml create mode 100644 charts/valkey/README.md create mode 100644 charts/valkey/templates/NOTES.txt create mode 100644 charts/valkey/templates/_helpers.tpl create mode 100644 charts/valkey/templates/configmap.yaml create mode 100644 charts/valkey/templates/deploy_valkey.yaml create mode 100644 charts/valkey/templates/init_config.yaml create mode 100644 charts/valkey/templates/metrics-svc.yaml create mode 100644 charts/valkey/templates/netpolicy.yaml create mode 100644 charts/valkey/templates/podmonitor.yaml create mode 100644 charts/valkey/templates/prometheusrules.yaml create mode 100644 charts/valkey/templates/pvc.yaml create mode 100644 charts/valkey/templates/secret.yaml create mode 100644 charts/valkey/templates/service-headless.yaml create mode 100644 charts/valkey/templates/service-read.yaml create mode 100644 charts/valkey/templates/service.yaml create mode 100644 charts/valkey/templates/serviceaccount.yaml create mode 100644 charts/valkey/templates/servicemonitor.yaml create mode 100644 charts/valkey/templates/statefulset.yaml create mode 100644 charts/valkey/templates/tests/auth.yaml create mode 100644 charts/valkey/values.schema.json create mode 100644 charts/valkey/values.yaml diff --git a/charts/valkey-0.9.3.tgz b/charts/valkey-0.9.3.tgz deleted file mode 100644 index 1f6ad50dbc3d630cf0cf5017cdb4d500f81b2a8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19409 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 diff --git a/charts/valkey/.helmignore b/charts/valkey/.helmignore new file mode 100644 index 00000000..26f45eac --- /dev/null +++ b/charts/valkey/.helmignore @@ -0,0 +1,28 @@ +# 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 +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ + +.github/ + +# Unit tests (only ignore root-level tests/, not templates/tests/) +/tests/ \ No newline at end of file diff --git a/charts/valkey/Chart.yaml b/charts/valkey/Chart.yaml new file mode 100644 index 00000000..499811c3 --- /dev/null +++ b/charts/valkey/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +appVersion: 9.0.1 +description: A Helm chart for Kubernetes +home: https://valkey.io/valkey-helm/ +icon: https://dyltqmyl993wv.cloudfront.net/assets/stacks/valkey/img/valkey-stack-220x234.png +maintainers: +- name: raven + url: https://github.com/mk-raven +name: valkey +sources: +- https://github.com/valkey-io/valkey-helm.git +- https://valkey.io +type: application +version: 0.9.3 diff --git a/charts/valkey/README.md b/charts/valkey/README.md new file mode 100644 index 00000000..0487321e --- /dev/null +++ b/charts/valkey/README.md @@ -0,0 +1,353 @@ +# valkey + +![Version: 0.9.0](https://img.shields.io/badge/Version-0.9.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 9.0.1](https://img.shields.io/badge/AppVersion-9.0.1-informational?style=flat-square) + +A Helm chart for Kubernetes + +**Homepage:** + +## Maintainers + +| Name | Url | +| ---- | --- | +| raven | [https://github.com/mk-raven] | +| sgissi | [https://github.com/sgissi] | + +## Source Code + +* +* + +## Deployment Modes + +### Standalone Mode (Default) + +Deploy a single Valkey instance: + +```bash +helm install valkey valkey/valkey +``` + +**Services:** + +* `valkey`: Master/read-write service + +### Replication Mode + +Deploy Valkey with master-replica architecture for read scaling and data redundancy: + +```bash +helm install valkey valkey/valkey --set replica.enabled=true --set replica.persistence.size=5Gi +``` + +**Services:** + +* `valkey`: Master/write service +* `valkey-read`: Read service (load-balances across all pods) - optional +* `valkey-headless`: Headless service for pod discovery + +**Write Safety Configuration:** + +Ensure data durability by requiring a minimum number of replicas to be in sync before accepting writes: + +```yaml +replica: + minReplicasToWrite: 1 # Require at least 1 replica + minReplicasMaxLag: 10 # Max 10 seconds replication lag +``` + +If fewer than `minReplicasToWrite` replicas are available, the master will reject write operations. + +## Storage + +### Standalone Storage + +Persistence is optional. By default, data is stored in an ephemeral volume and lost on pod restart. + +**Enable persistent storage:** + +```yaml +dataStorage: + enabled: true + requestedSize: 10Gi + className: "fast-ssd" # Optional +``` + +**Use existing PVC:** + +```yaml +dataStorage: + enabled: true + persistentVolumeClaimName: "my-existing-pvc" +``` + +### Replication Storage + +Persistent storage is **mandatory** in replication mode. Without it, the primary might comes up with an empty dataset after a restart, all replicas will synchronize with the empty primary and lose their data. See [Valkey Replication Safety](https://valkey.io/topics/replication/#safety-of-replication-when-primary-has-persistence-turned-off) for details. + +```yaml +replica: + enabled: true + persistence: + size: 10Gi # Required + storageClass: "fast-ssd" # Optional +``` + +## Authentication + +This chart supports ACL-based authentication for Valkey. + +**⚠️ IMPORTANT:** When authentication is enabled, the `default` user **MUST** be defined in either `auth.aclUsers` or `auth.aclConfig`. Without a default user, anyone can access the database without credentials. + +### Existing Secret (recommended) + +Reference an existing Kubernetes secret containing user passwords: + +```yaml +auth: + enabled: true + usersExistingSecret: "my-valkey-users" + aclUsers: + default: + permissions: "~* &* +@all" + # Password will be read from secret key "default" (defaults to username) + readonly: + permissions: "~* -@all +@read +ping +info" + passwordKey: "readonly-pwd" # Use custom secret key name +``` + +### Inline Passwords + +Define users directly in your values file with inline passwords: + +```yaml +auth: + enabled: true + aclUsers: + default: + permissions: "~* &* +@all" + password: "default-password" + readonly: + permissions: "~* -@all +@read +ping +info" + password: "readonly-password" +``` + +**Note:** + +* If `usersExistingSecret` is defined, passwords from the secret will take precedence over inline passwords. + +### Custom ACL Configuration + +You can also provide raw ACL configuration that will be appended after any generated users: + +```yaml +auth: + enabled: true + aclConfig: | + user default on >defaultpassword ~* &* +@all + user guest on nopass ~public:* +@read +``` + +### Replication with Authentication + +When using ACL authentication in replication mode, replicas need credentials to authenticate to the master: + +```yaml +auth: + enabled: true + usersExistingSecret: "my-valkey-users" + aclUsers: + default: + permissions: "~* &* +@all" + replication-user: + permissions: "+psync +replconf +ping" + +replica: + enabled: true + replicas: 2 + replicationUser: "replication-user" # Must be defined in auth.aclUsers +``` + +**Important Notes:** + +* `replica.replicationUser` specifies which ACL user replicas use to authenticate +* This user MUST be defined in `auth.aclUsers` with appropriate permissions +* Minimum permissions: `+psync +replconf +ping` + +## Metrics + +This chart supports Prometheus metrics collection using the [Redis exporter](https://github.com/oliver006/redis_exporter). + +Enable the metrics exporter sidecar: + +```yaml +metrics: + enabled: true +``` + +### Prometheus Operator discovery + +Automated Prometheus discovery using the Prometheus Operator ServiceMonitor: + +```yaml +metrics: + enabled: true + serviceMonitor: + enabled: true +``` + +## TLS + +This chart supports TLS encryption for Valkey connections. + +First create a secret containing the certificate public and private keys plus CA public key: + +```shell +kubectl create secret generic valkey-tls-secret --from-file=server.crt --from-file=server.key --from-file=ca.crt +``` + +Enable TLS and provide the name of the secret created above: + +```yaml +tls: + enabled: true + existingSecret: "valkey-tls-secret" +``` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.imageRegistry | string | '' | | +| global.imagePullSecrets | list | `[]` | | +| affinity | object | `{}` | | +| auth.aclConfig | string | `""` | | +| auth.aclUsers | object | `{}` | | +| auth.enabled | bool | `false` | | +| auth.usersExistingSecret | string | `""` | | +| dataStorage.accessModes[0] | string | `"ReadWriteOnce"` | | +| dataStorage.annotations | object | `{}` | | +| dataStorage.className | string | `""` | | +| dataStorage.enabled | bool | `false` | | +| dataStorage.keepPvc | bool | `false` | | +| dataStorage.labels | object | `{}` | | +| dataStorage.persistentVolumeClaimName | string | `""` | | +| dataStorage.requestedSize | string | `""` | | +| dataStorage.subPath | string | `""` | | +| dataStorage.volumeName | string | `"valkey-data"` | | +| dataStorage.hostPath | string | `""` | | +| deploymentStrategy | string | `"RollingUpdate"` | | +| env | object | `{}` | | +| extraSecretValkeyConfigs | bool | `false` | | +| extraVolumes | list | `[]` | | +| extraVolumeMounts | list | `[]` | | +| extraValkeyConfigs | list | `[]` | | +| extraValkeySecrets | list | `[]` | | +| fullnameOverride | string | `""` | | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.registry | string | `""` | | +| image.repository | string | `"docker.io/valkey/valkey"` | | +| image.tag | string | `""` | | +| imagePullSecrets | list | `[]` | | +| initResources | object | `{}` | | +| metrics.enabled | bool | `false` | | +| metrics.exporter.args | list | `[]` | | +| metrics.exporter.command | list | `[]` | | +| metrics.exporter.extraEnvs | object | `{}` | | +| metrics.exporter.extraVolumeMounts | list | `[]` | | +| metrics.exporter.image.pullPolicy | string | `"IfNotPresent"` | | +| metrics.exporter.image.repository | string | `"ghcr.io/oliver006/redis_exporter"` | | +| metrics.exporter.image.tag | string | `"v1.79.0"` | | +| metrics.exporter.port | int | `9121` | | +| metrics.exporter.resources | object | `{}` | | +| metrics.exporter.securityContext | object | `{}` | | +| metrics.podMonitor.additionalLabels | object | `{}` | | +| metrics.podMonitor.annotations | object | `{}` | | +| metrics.podMonitor.enabled | bool | `false` | | +| metrics.podMonitor.extraLabels | object | `{}` | | +| metrics.podMonitor.honorLabels | bool | `false` | | +| metrics.podMonitor.interval | string | `"30s"` | | +| metrics.podMonitor.metricRelabelings | list | `[]` | | +| metrics.podMonitor.podTargetLabels | list | `[]` | | +| metrics.podMonitor.port | string | `"metrics"` | | +| metrics.podMonitor.relabelings | list | `[]` | | +| metrics.podMonitor.sampleLimit | bool | `false` | | +| metrics.podMonitor.scrapeTimeout | string | `""` | | +| metrics.podMonitor.targetLimit | bool | `false` | | +| metrics.prometheusRule.enabled | bool | `false` | | +| metrics.prometheusRule.extraAnnotations | object | `{}` | | +| metrics.prometheusRule.extraLabels | object | `{}` | | +| metrics.prometheusRule.rules | list | `[]` | | +| metrics.service.annotations | object | `{}` | | +| metrics.service.enabled | bool | `true` | | +| metrics.service.extraLabels | object | `{}` | | +| metrics.service.ports.http | int | `9121` | | +| metrics.service.type | string | `"ClusterIP"` | | +| metrics.service.appProtocol | string | `""` | | +| metrics.serviceMonitor.additionalLabels | object | `{}` | | +| metrics.serviceMonitor.annotations | object | `{}` | | +| metrics.serviceMonitor.enabled | bool | `false` | | +| metrics.serviceMonitor.extraLabels | object | `{}` | | +| metrics.serviceMonitor.honorLabels | bool | `false` | | +| metrics.serviceMonitor.interval | string | `"30s"` | | +| metrics.serviceMonitor.metricRelabelings | list | `[]` | | +| metrics.serviceMonitor.podTargetLabels | list | `[]` | | +| metrics.serviceMonitor.port | string | `"metrics"` | | +| metrics.serviceMonitor.relabelings | list | `[]` | | +| metrics.serviceMonitor.sampleLimit | bool | `false` | | +| metrics.serviceMonitor.scrapeTimeout | string | `""` | | +| metrics.serviceMonitor.targetLimit | bool | `false` | | +| nameOverride | string | `""` | | +| networkPolicy | object | `{}` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podLabels | object | `{}` | | +| commonLabels | object | `{}` | | +| podSecurityContext.fsGroup | int | `1000` | | +| podSecurityContext.runAsGroup | int | `1000` | | +| podSecurityContext.runAsUser | int | `1000` | | +| priorityClassName | string | `""` | | +| replica.enabled | bool | `false` | | +| replica.replicas | int | `2` | | +| replica.replicationUser | string | `"default"` | | +| replica.disklessSync | bool | `false` | | +| replica.minReplicasToWrite | int | `0` | | +| replica.minReplicasMaxLag | int | `10` | | +| replica.service.enabled | bool | `"true"` | | +| replica.service.type | string | `"ClusterIP"` | | +| replica.service.port | int | `6379` | | +| replica.service.annotations | object | `{}` | | +| replica.service.nodePort | int | `0` | | +| replica.service.clusterIP | string | `""` | | +| replica.service.appProtocol | string | `""` | | +| replica.service.loadBalancerClass | string | `""` | | +| replica.persistence. | | `""` | | +| replica.persistence.size | string | `""` | Required if replica is enabled | +| replica.persistence.storageClass | string | `""` | | +| replica.persistence.accessModes | list | `""` | | +| resources | object | `{}` | | +| securityContext.capabilities.drop[0] | string | `"ALL"` | | +| securityContext.readOnlyRootFilesystem | bool | `true` | | +| securityContext.runAsNonRoot | bool | `true` | | +| securityContext.runAsUser | int | `1000` | | +| service.annotations | object | `{}` | | +| service.nodePort | int | `0` | | +| service.port | int | `6379` | | +| service.type | string | `"ClusterIP"` | | +| service.appProtocol | string | `""` | | +| service.loadBalancerClass | string | `""` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.automount | bool | `false` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| tls.caPublicKey | string | `"ca.crt"` | | +| tls.dhParamKey | string | `""` | | +| tls.enabled | bool | `false` | | +| tls.existingSecret | string | `""` | | +| tls.requireClientCertificate | bool | `false` | | +| tls.serverKey | string | `"server.key"` | | +| tls.serverPublicKey | string | `"server.crt"` | | +| tolerations | list | `[]` | | +| topologySpreadConstraints | list | `[]` | | +| valkeyConfig | string | `""` | | +| valkeyLogLevel | string | `"notice"` | | diff --git a/charts/valkey/templates/NOTES.txt b/charts/valkey/templates/NOTES.txt new file mode 100644 index 00000000..07ddb6dd --- /dev/null +++ b/charts/valkey/templates/NOTES.txt @@ -0,0 +1,137 @@ +{{/* + NOTES for Valkey Helm Chart + This file is rendered after `helm install` / `helm upgrade`. +*/}} + +⭐ Valkey has been deployed! + +Release: {{ .Release.Name }} +Namespace: {{ .Release.Namespace }} +Chart: {{ .Chart.Name }} {{ .Chart.Version }} +App version: {{ .Chart.AppVersion }} + +{{- if .Values.replica.enabled }} +================================================================================ +🔄 REPLICATION MODE +================================================================================ + +Your Valkey deployment is running in REPLICATION mode: +- 1 Master ({{ include "valkey.fullname" . }}-0) +- {{ .Values.replica.replicas }} Replica(s) + +{{- if .Values.replica.service.enabled }} + +READ Operations (Load-balanced across all pods): + Service: {{ include "valkey.fullname" . }}-read + Type: {{ .Values.replica.service.type }} + Port: {{ .Values.replica.service.port }} + +1) In-cluster access: + $ valkey-cli -h {{ include "valkey.fullname" . }}-read -p {{ .Values.replica.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} GET key + +2) Local access via kubectl port-forward: + $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }}-read 6379:{{ .Values.replica.service.port }} + $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} GET key +{{ if eq .Values.replica.service.type "LoadBalancer" }} +3) External access (LoadBalancer): + $ export SERVICE_IP=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }}-read -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + $ valkey-cli -h $SERVICE_IP -p {{ .Values.replica.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} GET key +{{ else if eq .Values.replica.service.type "NodePort" }} +3) External access (NodePort): + $ export NODE_PORT=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }}-read -o jsonpath='{.spec.ports[0].nodePort}') + $ export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + $ valkey-cli -h $NODE_IP -p $NODE_PORT{{ if .Values.tls.enabled }} --tls{{- end }} GET key +{{ end }} +{{- end }} + +Direct Pod Access: + Master: {{ include "valkey.fullname" . }}-0.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} +{{- range $i := until (int .Values.replica.replicas) }} + Replica: {{ include "valkey.fullname" $ }}-{{ add $i 1 }}.{{ include "valkey.headlessServiceName" $ }}.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }} +{{- end }} + +WRITE Operations (Master only): + Service: {{ include "valkey.fullname" . }} + Type: {{ .Values.service.type }} + Port: {{ .Values.service.port }} + +1) In-cluster access: + $ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING + +2) Local access: + $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }} 6379:{{ .Values.service.port }} + $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} SET key value +{{- else }} +================================================================================ +📦 STANDALONE MODE +================================================================================ + +Service: {{ include "valkey.fullname" . }} +Type: {{ .Values.service.type }} +Port: {{ .Values.service.port }} + +1) In-cluster access + From another Pod: + $ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING + +2) Local access via kubectl port-forward + $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }} 6379:{{ .Values.service.port }} + In another terminal: + $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} PING +{{- end }} +{{ if eq .Values.service.type "LoadBalancer" }} +3) External access (LoadBalancer) + $ export SERVICE_IP=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + $ valkey-cli -h $SERVICE_IP -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING +{{ else if eq .Values.service.type "NodePort" }} +3) External access (NodePort) + $ export NODE_PORT=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }} -o jsonpath='{.spec.ports[0].nodePort}') + $ export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') + $ valkey-cli -h $NODE_IP -p $NODE_PORT{{ if .Values.tls.enabled }} --tls{{- end }} PING +{{ end }} + +{{ if .Values.auth.enabled }} +🔐 Authentication is ENABLED (ACL) +- Provide the username and password from `.auth.aclUsers`. +{{ else }} +🔓 Authentication is DISABLED +- Enable with: `--set auth.enabled=true` and provide `.auth.aclUsers`. +{{ end }} + +✅ Quick test +$ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }}{{ if .Values.auth.enabled }} --user -a {{ end }} +valkey> SET foo bar +valkey> GET foo +"bar" + +💾 Persistence +{{- if .Values.replica.enabled }} +- Persistence is ENABLED (required for replication mode). Each instance has its own volume. +- Size: {{ .Values.replica.persistence.size }} +{{- if .Values.replica.persistence.storageClass }} +- Storage class: {{ .Values.replica.persistence.storageClass }} +{{- end }} +- To see PVCs: + $ kubectl -n {{ .Release.Namespace }} get pvc -l app.kubernetes.io/instance={{ .Release.Name }} +{{- else }} +{{ if .Values.dataStorage.enabled -}} +{{ if .Values.dataStorage.hostPath -}} +- Persistence is ENABLED. A hostPath volume is used at path='`{{ .Values.dataStorage.hostPath }}`'. +{{ else -}} +- Persistence is ENABLED. To see it: + $ kubectl -n {{ .Release.Namespace }} get pvc -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "valkey.name" . }} +- Note: `dataStorage.keepPvc={{ .Values.dataStorage.keepPvc }}` controls whether the PVC is kept on uninstall. +{{ end -}} +{{ else -}} +- Persistence is DISABLED. Data will not survive Pod restarts. +- Enable with: + `--set dataStorage.enabled=true --set dataStorage.requestedSize=5Gi` (optionally set `dataStorage.className`) + or + `--set dataStorage.enabled=true --set dataStorage.persistentVolumeClaimName=valkey-data-pvc` to use an existing PVC + or + `--set dataStorage.enabled=true --set dataStorage.hostPath=/some/path/` to use a hostPath volume. +{{- end }} +{{- end }} + +🧹 Uninstall +$ helm -n {{ .Release.Namespace }} uninstall {{ .Release.Name }} diff --git a/charts/valkey/templates/_helpers.tpl b/charts/valkey/templates/_helpers.tpl new file mode 100644 index 00000000..593cf77c --- /dev/null +++ b/charts/valkey/templates/_helpers.tpl @@ -0,0 +1,190 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "valkey.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 "valkey.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 "valkey.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "valkey.labels" -}} +helm.sh/chart: {{ include "valkey.chart" . }} +{{ include "valkey.selectorLabels" . }} +{{- if or .Values.image.tag .Chart.AppVersion }} +app.kubernetes.io/version: {{ mustRegexReplaceAllLiteral "@sha.*" .Values.image.tag "" | default .Chart.AppVersion | trunc 63 | trimSuffix "-" | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.commonLabels }} +{{- toYaml . | nindent 0 }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "valkey.selectorLabels" -}} +app.kubernetes.io/name: {{ include "valkey.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "valkey.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "valkey.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Returns the Valkey container image +*/}} +{{- define "valkey.image" -}} +{{- include "common.image" (dict "image" (dict "registry" .Values.image.registry "repository" .Values.image.repository "tag" (.Values.image.tag | default .Chart.AppVersion)) "global" .Values.global) }} +{{- end -}} + +{{/* +Returns the Valkey exporter container image +*/}} +{{- define "valkey.metrics.exporter.image" -}} +{{- include "common.image" (dict "image" .Values.metrics.exporter.image "global" .Values.global) }} +{{- end -}} + +{{/* +The common image function that renders the container image +*/}} +{{- define "common.image" -}} +{{- $registryName := .image.registry }} +{{- $repositoryName := .image.repository }} +{{- $tag := .image.tag }} +{{- 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 -}} + +{{/* +Returns the Valkey image pull secrets +*/}} +{{- define "valkey.imagePullSecrets" -}} +{{- $pullSecrets := list }} +{{- if .Values.global }} + {{- range .Values.global.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} +{{- end -}} +{{- range .Values.imagePullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} +{{- end -}} +{{- if (not (empty $pullSecrets)) }} +imagePullSecrets: +{{- range $pullSecrets }} +- name: {{ . }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +Check if there are any users with inline passwords +*/}} +{{- define "valkey.hasInlinePasswords" -}} +{{- $hasInlinePasswords := false -}} +{{- range $username, $user := .Values.auth.aclUsers -}} + {{- if $user.password -}} + {{- $hasInlinePasswords = true -}} + {{- end -}} +{{- end -}} +{{- $hasInlinePasswords -}} +{{- end -}} + +{{/* +Validate auth configuration +*/}} +{{- define "valkey.validateAuthConfig" -}} +{{- if .Values.auth.enabled }} + {{- if not (or .Values.auth.aclUsers .Values.auth.aclConfig) }} + {{- fail "auth.enabled is true but no authentication method is configured. Please provide auth.aclUsers or auth.aclConfig" }} + {{- end }} + {{- if .Values.auth.aclUsers }} + {{- $hasUsersExistingSecret := .Values.auth.usersExistingSecret }} + {{- if not (hasKey .Values.auth.aclUsers "default") }} + {{- fail "The 'default' user must be defined in auth.aclUsers when authentication is enabled. Without it, anyone can access the database without credentials." }} + {{- end }} + {{- range $username, $user := .Values.auth.aclUsers }} + {{- if not $user.permissions }} + {{- fail (printf "User '%s' in auth.aclUsers must have a 'permissions' field" $username) }} + {{- end }} + {{- if not (or $user.password $hasUsersExistingSecret) }} + {{- fail (printf "User '%s' must have either 'password' field or auth.usersExistingSecret must be set" $username) }} + {{- end }} + {{- if and $user.passwordKey (not $hasUsersExistingSecret) }} + {{- fail (printf "User '%s' has passwordKey but auth.usersExistingSecret is not set" $username) }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end -}} + +{{/* +Headless service name for replication +*/}} +{{- define "valkey.headlessServiceName" -}} +{{ include "valkey.fullname" . }}-headless +{{- end -}} + +{{/* +Validate replica persistence configuration +*/}} +{{- define "valkey.validateReplicaPersistence" -}} +{{- if .Values.replica.enabled }} + {{- if not .Values.replica.persistence.size }} + {{- fail "Replica mode requires persistent storage. Please set replica.persistence.size (e.g., '5Gi')" }} + {{- end }} +{{- end }} +{{- end -}} + +{{/* +Validate replica authentication configuration +*/}} +{{- define "valkey.validateReplicaAuth" -}} +{{- if and .Values.replica.enabled .Values.auth.enabled }} + {{- if not (hasKey .Values.auth.aclUsers .Values.replica.replicationUser) }} + {{- fail (printf "Replication user '%s' (replica.replicationUser) must be defined in auth.aclUsers. The chart requires this to retrieve the password for replica authentication." .Values.replica.replicationUser) }} + {{- end }} +{{- end }} +{{- end -}} + diff --git a/charts/valkey/templates/configmap.yaml b/charts/valkey/templates/configmap.yaml new file mode 100644 index 00000000..9176d12f --- /dev/null +++ b/charts/valkey/templates/configmap.yaml @@ -0,0 +1,11 @@ +{{- if .Values.valkeyConfig }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "valkey.fullname" . }}-config + labels: + {{- include "valkey.labels" . | nindent 4 }} +data: + valkey.conf: | + {{- .Values.valkeyConfig | nindent 4 }} +{{- end }} diff --git a/charts/valkey/templates/deploy_valkey.yaml b/charts/valkey/templates/deploy_valkey.yaml new file mode 100644 index 00000000..da7cd712 --- /dev/null +++ b/charts/valkey/templates/deploy_valkey.yaml @@ -0,0 +1,289 @@ +{{- if not .Values.replica.enabled }} +{{- $fullname := include "valkey.fullname" . }} +{{- $storage := .Values.dataStorage }} +{{- $createPVC := and $storage.enabled (not (empty $storage.requestedSize)) (empty $storage.persistentVolumeClaimName) }} +{{- include "valkey.validateAuthConfig" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} +spec: + replicas: 1 + strategy: + type: {{ .Values.deploymentStrategy }} + selector: + matchLabels: + {{- include "valkey.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "valkey.selectorLabels" . | nindent 8 }} + {{- with .Values.commonLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + checksum/initconfig: {{ include (print $.Template.BasePath "/init_config.yaml") . | sha256sum | trunc 32 }} + {{- if .Values.valkeyConfig }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | trunc 32 }} + {{- end }} + spec: + {{- include "valkey.imagePullSecrets" . | nindent 6 }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} + serviceAccountName: {{ include "valkey.serviceAccountName" . }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: {{ include "valkey.fullname" . }}-init + image: {{ include "valkey.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + command: [ "/scripts/init.sh" ] + volumeMounts: + - name: {{ .Values.dataStorage.volumeName }} + mountPath: /data + {{- if .Values.dataStorage.subPath }} + subPath: {{ .Values.dataStorage.subPath }} + {{- end }} + - name: scripts + mountPath: /scripts + {{- if .Values.valkeyConfig }} + - name: valkey-config + mountPath: /usr/local/etc/valkey/valkey.conf + subPath: valkey.conf + {{- end }} + {{- if .Values.extraSecretValkeyConfigs }} + - name: extravalkeyconfigs-volume + mountPath: /extravalkeyconfigs + {{- end }} + {{- if .Values.auth.enabled }} + - name: valkey-acl + mountPath: /etc/valkey + {{- if .Values.auth.usersExistingSecret }} + - name: valkey-users-secret + mountPath: /valkey-users-secret + readOnly: true + {{- end }} + {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} + - name: valkey-auth-secret + mountPath: /valkey-auth-secret + readOnly: true + {{- end }} + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.initResources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.extraInitContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "valkey.fullname" . }} + image: {{ include "valkey.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: [ "valkey-server" ] + args: [ "/data/conf/valkey.conf" ] + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + env: + {{- range $key, $val := .Values.env }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} + - name: VALKEY_LOGLEVEL + value: "{{ .Values.valkeyLogLevel }}" + ports: + - name: tcp + containerPort: {{ .Values.service.port }} + protocol: TCP + startupProbe: + exec: + {{- if .Values.tls.enabled }} + command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] + {{- else }} + command: [ "sh", "-c", "valkey-cli ping" ] + {{- end }} + livenessProbe: + exec: + {{- if .Values.tls.enabled }} + command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] + {{- else }} + command: [ "sh", "-c", "valkey-cli ping" ] + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: {{ .Values.dataStorage.volumeName }} + mountPath: /data + {{- if .Values.dataStorage.subPath }} + subPath: {{ .Values.dataStorage.subPath }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: {{ include "valkey.fullname" . }}-tls + mountPath: /tls + {{- end }} + {{- if .Values.auth.enabled }} + - name: valkey-acl + mountPath: /etc/valkey + {{- end }} + {{- range $secret := .Values.extraValkeySecrets }} + - name: {{ $secret.name }}-valkey + mountPath: {{ $secret.mountPath }} + {{- end }} + {{- range $config := .Values.extraValkeyConfigs }} + - name: {{ $config.name }}-valkey + mountPath: {{ $config.mountPath }} + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "valkey.metrics.exporter.image" . }} + imagePullPolicy: {{ .Values.metrics.exporter.image.pullPolicy | quote }} + {{- with .Values.metrics.exporter.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: metrics + containerPort: {{ .Values.metrics.exporter.port }} + startupProbe: + tcpSocket: + port: metrics + livenessProbe: + tcpSocket: + port: metrics + readinessProbe: + httpGet: + path: / + port: metrics + {{- with .Values.metrics.exporter.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.extraVolumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ include "valkey.fullname" . }} + {{- range $key, $val := .Values.metrics.exporter.extraEnvs }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} + {{- end }} + volumes: + - name: scripts + configMap: + name: {{ include "valkey.fullname" . }}-init-scripts + defaultMode: 0555 + {{- if .Values.auth.enabled }} + - name: valkey-acl + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.valkeyConfig }} + - name: valkey-config + configMap: + name: {{ include "valkey.fullname" . }}-config + {{- end }} + {{- range .Values.extraValkeySecrets }} + - name: {{ .name }}-valkey + secret: + secretName: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: {{ include "valkey.fullname" . }}-tls + secret: + secretName: {{ required "An existing secret is required to enable TLS" .Values.tls.existingSecret }} + defaultMode: 0400 + {{- end }} + {{- range .Values.extraValkeyConfigs }} + - name: {{ .name }}-valkey + configMap: + name: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- if .Values.metrics.enabled }} + {{- range .Values.metrics.exporter.extraExporterSecrets }} + - name: {{ .name }}-exporter + secret: + secretName: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- end }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usersExistingSecret }} + - name: valkey-users-secret + secret: + secretName: {{ .Values.auth.usersExistingSecret }} + defaultMode: 0400 + {{- end }} + {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} + - name: valkey-auth-secret + secret: + secretName: {{ include "valkey.fullname" . }}-auth + defaultMode: 0400 + {{- end }} + {{- end }} + - name: {{ $storage.volumeName }} + {{- if $storage.persistentVolumeClaimName }} + persistentVolumeClaim: + claimName: {{ $storage.persistentVolumeClaimName }} + {{- else if $createPVC }} + persistentVolumeClaim: + claimName: {{ include "valkey.fullname" . }} + {{- else if $storage.hostPath }} + hostPath: + path: {{ $storage.hostPath }} + type: DirectoryOrCreate + {{- else }} + emptyDir: {} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/valkey/templates/init_config.yaml b/charts/valkey/templates/init_config.yaml new file mode 100644 index 00000000..9b0337e5 --- /dev/null +++ b/charts/valkey/templates/init_config.yaml @@ -0,0 +1,231 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "valkey.fullname" . }}-init-scripts + labels: + {{- include "valkey.labels" . | nindent 4 }} +data: + init.sh: |- + #!/bin/sh + set -eu + + # Default config paths + VALKEY_CONFIG=${VALKEY_CONFIG_PATH:-/data/conf/valkey.conf} + + LOGFILE="/data/init.log" + DATA_DIR="/data/conf" + + # Logging function (outputs to stderr and file) + log() { + echo "$(date) $1" | tee -a "$LOGFILE" >&2 + } + + {{- if .Values.auth.enabled }} + # Function to get password for a user + # Usage: get_user_password [password_key] + # Returns: password via stdout, exits with error if not found + get_user_password() { + username="$1" + password_key="${2:-$username}" + password="" + + {{- if .Values.auth.usersExistingSecret }} + # Try to get password from existing secret first (priority) + if [ -f "/valkey-users-secret/$password_key" ]; then + password=$(cat "/valkey-users-secret/$password_key") + log "Using password from existing secret for user $username" + elif [ -f "/valkey-auth-secret/${username}-password" ]; then + # Fallback to inline password + password=$(cat "/valkey-auth-secret/${username}-password") + log "Using inline password for user $username" + else + log "ERROR: No password found for user $username" + return 1 + fi + {{- else }} + # Use inline password only + if [ -f "/valkey-auth-secret/${username}-password" ]; then + password=$(cat "/valkey-auth-secret/${username}-password") + log "Using inline password for user $username" + else + log "ERROR: No password found for user $username" + return 1 + fi + {{- end }} + + echo "$password" + } + {{- end }} + + # Clean old log if requested + if [ "${KEEP_OLD_LOGS:-false}" != "true" ]; then + rm -f "$LOGFILE" + fi + + if [ -f "$LOGFILE" ]; then + log "Detected restart of this instance ($HOSTNAME)" + fi + + log "Creating configuration in $DATA_DIR..." + mkdir -p "$DATA_DIR" + rm -f "$VALKEY_CONFIG" + + + # Base valkey.conf + log "Generating base valkey.conf" + { +{{- if .Values.tls.enabled }} + echo "tls-cert-file /tls/{{ .Values.tls.serverPublicKey }}" + echo "tls-key-file /tls/{{ .Values.tls.serverKey }}" + echo "tls-ca-cert-file /tls/{{ .Values.tls.caPublicKey }}" + {{- if .Values.tls.dhParamKey }} + echo "tls-dh-params-file /tls/{{ .Values.tls.dhParamKey }}" + {{- end }} + {{- if not .Values.tls.requireClientCertificate }} + echo "tls-auth-clients no" + {{- end }} + echo "port 0" + echo "tls-port 6379" +{{- else }} + echo "port 6379" +{{- end }} + echo "protected-mode no" + echo "bind * -::*" + echo "dir /data" + } >>"$VALKEY_CONFIG" + + {{- if .Values.auth.enabled }} + # Create secure directory for ACL file + log "Creating /etc/valkey directory for ACL file" + mkdir -p /etc/valkey + + # Set aclfile path in valkey.conf + echo "aclfile /etc/valkey/users.acl" >>"$VALKEY_CONFIG" + + # Remove or reset existing ACL file if present (it may be read-only from previous run) + log "Preparing ACL file at /etc/valkey/users.acl" + if [ -f /etc/valkey/users.acl ]; then + log "Removing existing read-only users.acl file" + chmod 0600 /etc/valkey/users.acl + rm -f /etc/valkey/users.acl + fi + + # Create ACL file with secure permissions + touch /etc/valkey/users.acl + chmod 0600 /etc/valkey/users.acl + + {{- if .Values.auth.aclUsers }} + # Generate ACL entries for each user + log "Generating ACL entries for users" + {{- range $username, $user := .Values.auth.aclUsers }} + {{- $passwordKey := $user.passwordKey | default $username }} + + # User: {{ $username }} + PASSWORD=$(get_user_password "{{ $username }}" "{{ $passwordKey }}") || exit 1 + + # Hash the password and write ACL entry + PASSHASH=$(echo -n "$PASSWORD" | sha256sum | cut -f 1 -d " ") + echo "user {{ $username }} on #$PASSHASH {{ $user.permissions }}" >> /etc/valkey/users.acl + + {{- end }} + {{- end }} + + {{- if .Values.auth.aclConfig }} + # Append inline ACL configuration + log "Appending custom ACL configuration" + if [ -f /valkey-auth-secret/aclConfig ]; then + cat /valkey-auth-secret/aclConfig >> /etc/valkey/users.acl + fi + {{- end }} + + # Set final permissions + chmod 0400 /etc/valkey/users.acl + log "ACL file created with 0400 permissions" + {{- end }} + + {{- if .Values.replica.enabled }} + # Replica mode configuration + log "Configuring replication mode" + + # Use POD_INDEX from Kubernetes metadata + POD_INDEX=${POD_INDEX:-0} + IS_MASTER=false + + # Check if this is pod-0 (master) + if [ "$POD_INDEX" = "0" ]; then + IS_MASTER=true + log "This pod (index $POD_INDEX) is configured as MASTER" + else + log "This pod (index $POD_INDEX) is configured as REPLICA" + fi + + # Configure replica settings + if [ "$IS_MASTER" = "false" ]; then + MASTER_HOST="{{ include "valkey.fullname" . }}-0.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + MASTER_PORT="{{ .Values.service.port }}" + + log "Configuring replica to follow master at $MASTER_HOST:$MASTER_PORT" + + { + echo "" + echo "# Replica Configuration" + echo "replicaof $MASTER_HOST $MASTER_PORT" + echo "replica-announce-ip {{ include "valkey.fullname" . }}-$POD_INDEX.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" + {{- if .Values.replica.disklessSync }} + echo "" + echo "# Diskless replication" + echo "repl-diskless-sync yes" + echo "repl-diskless-sync-delay 5" + {{- end }} + {{- if .Values.auth.enabled }} + echo "" + echo "# Master authentication" + {{- end }} + } >>"$VALKEY_CONFIG" + + {{- if .Values.auth.enabled }} + # Get the password for the replication user + {{- $replUsername := .Values.replica.replicationUser }} + {{- $replUser := index .Values.auth.aclUsers $replUsername }} + {{- $replPasswordKey := $replUser.passwordKey | default $replUsername }} + REPL_PASSWORD=$(get_user_password "{{ $replUsername }}" "{{ $replPasswordKey }}") || exit 1 + + # Write masterauth configuration + echo "masterauth $REPL_PASSWORD" >>"$VALKEY_CONFIG" + echo "masteruser {{ $replUsername }}" >>"$VALKEY_CONFIG" + log "Configured masterauth with user {{ $replUsername }}" + {{- end }} + + {{- if .Values.tls.enabled }} + # TLS for replication + { + echo "" + echo "# TLS for replication" + echo "tls-replication yes" + } >>"$VALKEY_CONFIG" + log "Enabled TLS for replication" + {{- end }} + fi + + {{- if gt (int .Values.replica.minReplicasToWrite) 0 }} + # Write safety - require minimum healthy replicas + { + echo "" + echo "# Minimum replicas for write operations" + echo "min-replicas-to-write {{ .Values.replica.minReplicasToWrite }}" + echo "min-replicas-max-lag {{ .Values.replica.minReplicasMaxLag }}" + } >>"$VALKEY_CONFIG" + log "Configured write safety: require {{ .Values.replica.minReplicasToWrite }} replicas with max {{ .Values.replica.minReplicasMaxLag }}s lag" + {{- end }} + {{- end }} + + # Append extra configs if present + if [ -f /usr/local/etc/valkey/valkey.conf ]; then + log "Appending /usr/local/etc/valkey/valkey.conf" + cat /usr/local/etc/valkey/valkey.conf >>"$VALKEY_CONFIG" + fi + if [ -d /extravalkeyconfigs ]; then + log "Appending files in /extravalkeyconfigs/" + cat /extravalkeyconfigs/* >>"$VALKEY_CONFIG" + fi + diff --git a/charts/valkey/templates/metrics-svc.yaml b/charts/valkey/templates/metrics-svc.yaml new file mode 100644 index 00000000..ffd71d70 --- /dev/null +++ b/charts/valkey/templates/metrics-svc.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.metrics.enabled .Values.metrics.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-metrics" (include "valkey.fullname" .) }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/component: metrics + app.kubernetes.io/part-of: valkey + {{- with .Values.metrics.service.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.metrics.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.metrics.service.type }} + ports: + - name: metrics + port: {{ .Values.metrics.service.ports.http }} + protocol: TCP + targetPort: metrics + {{- if .Values.metrics.service.appProtocol }} + appProtocol: {{ .Values.metrics.service.appProtocol }} + {{- end }} + selector: + {{- include "valkey.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/valkey/templates/netpolicy.yaml b/charts/valkey/templates/netpolicy.yaml new file mode 100644 index 00000000..f65c504d --- /dev/null +++ b/charts/valkey/templates/netpolicy.yaml @@ -0,0 +1,33 @@ +{{- with .Values.networkPolicy }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "valkey.fullname" $ }} + {{- with .labels }} + labels: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + podSelector: + matchLabels: + {{- include "valkey.selectorLabels" $ | nindent 6 }} + policyTypes: + {{- if .ingress }} + - Ingress + {{- end }} + {{- if .egress }} + - Egress + {{- end }} + {{- with .ingress }} + ingress: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .egress }} + egress: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/valkey/templates/podmonitor.yaml b/charts/valkey/templates/podmonitor.yaml new file mode 100644 index 00000000..3a22d6b8 --- /dev/null +++ b/charts/valkey/templates/podmonitor.yaml @@ -0,0 +1,53 @@ +{{- if and .Values.metrics.enabled .Values.metrics.podMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/part-of: valkey + app.kubernetes.io/component: podmonitor + {{- with .Values.metrics.podMonitor.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metrics.podMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + podMetricsEndpoints: + - port: {{ .Values.metrics.podMonitor.port }} + {{- with .Values.metrics.podMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.metrics.podMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + {{- with .Values.metrics.podMonitor.honorLabels }} + honorLabels: {{ . }} + {{- end }} + {{- with .Values.metrics.podMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.metrics.podMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.metrics.podMonitor.podTargetLabels }} + podTargetLabels: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metrics.podMonitor.sampleLimit -}} + sampleLimit: {{ . }} + {{- end }} + {{- with .Values.metrics.podMonitor.targetLimit -}} + targetLimit: {{ . }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "valkey.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/valkey/templates/prometheusrules.yaml b/charts/valkey/templates/prometheusrules.yaml new file mode 100644 index 00000000..ab3ba4a8 --- /dev/null +++ b/charts/valkey/templates/prometheusrules.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/part-of: valkey + {{- if .Values.metrics.prometheusRule.extraLabels }} + {{- toYaml .Values.metrics.prometheusRule.extraLabels | nindent 4 }} + {{- end }} + {{- with .Values.metrics.prometheusRule.extraAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- with .Values.metrics.prometheusRule.rules }} + groups: + - name: {{ include "valkey.fullname" $ }} + rules: {{ tpl (toYaml .) $ | nindent 8 }} +{{- end }} +{{- end }} diff --git a/charts/valkey/templates/pvc.yaml b/charts/valkey/templates/pvc.yaml new file mode 100644 index 00000000..aa20859b --- /dev/null +++ b/charts/valkey/templates/pvc.yaml @@ -0,0 +1,30 @@ +{{- if and .Values.dataStorage.enabled (not .Values.replica.enabled) (not (empty .Values.dataStorage.requestedSize)) (empty .Values.dataStorage.persistentVolumeClaimName) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + {{- with .Values.dataStorage.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if or .Values.dataStorage.keepPvc .Values.dataStorage.annotations }} + annotations: + {{- if .Values.dataStorage.keepPvc }} + "helm.sh/resource-policy": keep + {{- end }} + {{- with .Values.dataStorage.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +spec: + accessModes: + {{- toYaml .Values.dataStorage.accessModes | nindent 4 }} + volumeMode: Filesystem + resources: + requests: + storage: {{ .Values.dataStorage.requestedSize }} + {{- if .Values.dataStorage.className }} + storageClassName: {{ .Values.dataStorage.className }} + {{- end }} +{{- end }} diff --git a/charts/valkey/templates/secret.yaml b/charts/valkey/templates/secret.yaml new file mode 100644 index 00000000..3e909780 --- /dev/null +++ b/charts/valkey/templates/secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.auth.enabled }} +{{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "valkey.fullname" . }}-auth + labels: + {{- include "valkey.labels" . | nindent 4 }} +type: Opaque +data: + {{- range $username, $user := .Values.auth.aclUsers }} + {{- if $user.password }} + {{ $username }}-password: {{ $user.password | b64enc }} + {{- end }} + {{- end }} + {{- if .Values.auth.aclConfig }} + aclConfig: {{ .Values.auth.aclConfig | b64enc }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/valkey/templates/service-headless.yaml b/charts/valkey/templates/service-headless.yaml new file mode 100644 index 00000000..733ca683 --- /dev/null +++ b/charts/valkey/templates/service-headless.yaml @@ -0,0 +1,20 @@ +{{- if .Values.replica.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "valkey.fullname" . }}-headless + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/component: headless +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: tcp + port: {{ .Values.service.port }} + targetPort: tcp + protocol: TCP + selector: + {{- include "valkey.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/valkey/templates/service-read.yaml b/charts/valkey/templates/service-read.yaml new file mode 100644 index 00000000..83daf905 --- /dev/null +++ b/charts/valkey/templates/service-read.yaml @@ -0,0 +1,34 @@ +{{- if and .Values.replica.enabled .Values.replica.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "valkey.fullname" . }}-read + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/component: read + {{- with .Values.replica.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.replica.service.type }} + {{- if .Values.replica.service.clusterIP }} + clusterIP: {{ .Values.replica.service.clusterIP }} + {{- end }} + {{- if .Values.replica.service.loadBalancerClass }} + loadBalancerClass: {{ .Values.replica.service.loadBalancerClass }} + {{- end }} + ports: + - name: tcp + port: {{ .Values.replica.service.port }} + targetPort: tcp + protocol: TCP + {{- if and (eq .Values.replica.service.type "NodePort") .Values.replica.service.nodePort }} + nodePort: {{ .Values.replica.service.nodePort }} + {{- end }} + {{- if .Values.replica.service.appProtocol }} + appProtocol: {{ .Values.replica.service.appProtocol }} + {{- end }} + selector: + {{- include "valkey.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/valkey/templates/service.yaml b/charts/valkey/templates/service.yaml new file mode 100644 index 00000000..4ffb3028 --- /dev/null +++ b/charts/valkey/templates/service.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/component: primary + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if .Values.service.clusterIP }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if .Values.service.loadBalancerClass }} + loadBalancerClass: {{ .Values.service.loadBalancerClass }} + {{- end }} + ports: + - port: {{ .Values.service.port }} + targetPort: tcp + protocol: TCP + name: tcp + {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + {{- if .Values.service.appProtocol }} + appProtocol: {{ .Values.service.appProtocol }} + {{- end }} + selector: + {{- include "valkey.selectorLabels" . | nindent 4 }} + {{- if .Values.replica.enabled }} + statefulset.kubernetes.io/pod-name: {{ include "valkey.fullname" . }}-0 + {{- end }} diff --git a/charts/valkey/templates/serviceaccount.yaml b/charts/valkey/templates/serviceaccount.yaml new file mode 100644 index 00000000..e0577153 --- /dev/null +++ b/charts/valkey/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "valkey.serviceAccountName" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/valkey/templates/servicemonitor.yaml b/charts/valkey/templates/servicemonitor.yaml new file mode 100644 index 00000000..e46d8e6c --- /dev/null +++ b/charts/valkey/templates/servicemonitor.yaml @@ -0,0 +1,54 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled .Values.metrics.service.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} + app.kubernetes.io/part-of: valkey + app.kubernetes.io/component: service-monitor + {{- with .Values.metrics.serviceMonitor.extraLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + endpoints: + - port: {{ .Values.metrics.serviceMonitor.port }} + {{- with .Values.metrics.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.honorLabels }} + honorLabels: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.relabelings }} + relabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.podTargetLabels }} + podTargetLabels: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.sampleLimit }} + sampleLimit: {{ . }} + {{- end }} + {{- with .Values.metrics.serviceMonitor.targetLimit }} + targetLimit: {{ . }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "valkey.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: metrics +{{- end }} diff --git a/charts/valkey/templates/statefulset.yaml b/charts/valkey/templates/statefulset.yaml new file mode 100644 index 00000000..541f9df0 --- /dev/null +++ b/charts/valkey/templates/statefulset.yaml @@ -0,0 +1,279 @@ +{{- if .Values.replica.enabled }} +{{- include "valkey.validateAuthConfig" . }} +{{- include "valkey.validateReplicaPersistence" . }} +{{- include "valkey.validateReplicaAuth" . }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "valkey.fullname" . }} + labels: + {{- include "valkey.labels" . | nindent 4 }} +spec: + serviceName: {{ include "valkey.fullname" . }}-headless + replicas: {{ add (int .Values.replica.replicas) 1 }} + podManagementPolicy: OrderedReady + selector: + matchLabels: + {{- include "valkey.selectorLabels" . | nindent 6 }} + volumeClaimTemplates: + - metadata: + name: valkey-data + spec: + accessModes: {{ toYaml .Values.replica.persistence.accessModes | nindent 8 }} + {{- if .Values.replica.persistence.storageClass }} + storageClassName: {{ .Values.replica.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.replica.persistence.size | quote }} + template: + metadata: + labels: + {{- include "valkey.selectorLabels" . | nindent 8 }} + {{- with .Values.commonLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + checksum/initconfig: {{ include (print $.Template.BasePath "/init_config.yaml") . | sha256sum | trunc 32 | quote }} + {{- if .Values.valkeyConfig }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | trunc 32 | quote }} + {{- end }} + spec: + {{- (include "valkey.imagePullSecrets" .) | nindent 6 }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} + serviceAccountName: {{ include "valkey.serviceAccountName" . }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + initContainers: + - name: {{ include "valkey.fullname" . }}-init + image: {{ include "valkey.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + command: [ "/scripts/init.sh" ] + env: + - name: POD_INDEX + valueFrom: + fieldRef: + fieldPath: metadata.labels['apps.kubernetes.io/pod-index'] + volumeMounts: + - name: valkey-data + mountPath: /data + - name: scripts + mountPath: /scripts + {{- if .Values.valkeyConfig }} + - name: valkey-config + mountPath: /usr/local/etc/valkey/valkey.conf + subPath: valkey.conf + {{- end }} + {{- if .Values.extraSecretValkeyConfigs }} + - name: extravalkeyconfigs-volume + mountPath: /extravalkeyconfigs + {{- end }} + {{- if .Values.auth.enabled }} + - name: valkey-acl + mountPath: /etc/valkey + {{- if .Values.auth.usersExistingSecret }} + - name: valkey-users-secret + mountPath: /valkey-users-secret + readOnly: true + {{- end }} + {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} + - name: valkey-auth-secret + mountPath: /valkey-auth-secret + readOnly: true + {{- end }} + {{- end }} + {{- with .Values.initResources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.extraInitContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ include "valkey.fullname" . }} + image: {{ include "valkey.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: [ "valkey-server" ] + args: [ "/data/conf/valkey.conf" ] + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + env: + - name: POD_INDEX + valueFrom: + fieldRef: + fieldPath: metadata.labels['apps.kubernetes.io/pod-index'] + {{- range $key, $val := .Values.env }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} + - name: VALKEY_LOGLEVEL + value: "{{ .Values.valkeyLogLevel }}" + ports: + - name: tcp + containerPort: {{ .Values.service.port }} + protocol: TCP + startupProbe: + exec: + {{- if .Values.tls.enabled }} + command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] + {{- else }} + command: [ "sh", "-c", "valkey-cli ping" ] + {{- end }} + livenessProbe: + exec: + {{- if .Values.tls.enabled }} + command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] + {{- else }} + command: [ "sh", "-c", "valkey-cli ping" ] + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: valkey-data + mountPath: /data + {{- if .Values.tls.enabled }} + - name: {{ include "valkey.fullname" . }}-tls + mountPath: /tls + {{- end }} + {{- if .Values.auth.enabled }} + - name: valkey-acl + mountPath: /etc/valkey + {{- end }} + {{- range $secret := .Values.extraValkeySecrets }} + - name: {{ $secret.name }}-valkey + mountPath: {{ $secret.mountPath }} + {{- end }} + {{- range $config := .Values.extraValkeyConfigs }} + - name: {{ $config.name }}-valkey + mountPath: {{ $config.mountPath }} + {{- end }} + {{- if .Values.metrics.enabled }} + - name: metrics + image: {{ include "valkey.metrics.exporter.image" . }} + imagePullPolicy: {{ .Values.metrics.exporter.image.pullPolicy | quote }} + {{- with .Values.metrics.exporter.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: metrics + containerPort: {{ .Values.metrics.exporter.port }} + startupProbe: + tcpSocket: + port: metrics + livenessProbe: + tcpSocket: + port: metrics + readinessProbe: + httpGet: + path: / + port: metrics + {{- with .Values.metrics.exporter.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.metrics.exporter.extraVolumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: REDIS_ALIAS + value: {{ include "valkey.fullname" . }} + {{- range $key, $val := .Values.metrics.exporter.extraEnvs }} + - name: {{ $key }} + value: "{{ $val }}" + {{- end }} + {{- end }} + volumes: + - name: scripts + configMap: + name: {{ include "valkey.fullname" . }}-init-scripts + defaultMode: 0555 + {{- if .Values.auth.enabled }} + - name: valkey-acl + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.valkeyConfig }} + - name: valkey-config + configMap: + name: {{ include "valkey.fullname" . }}-config + {{- end }} + {{- range .Values.extraValkeySecrets }} + - name: {{ .name }}-valkey + secret: + secretName: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: {{ include "valkey.fullname" . }}-tls + secret: + secretName: {{ required "An existing secret is required to enable TLS" .Values.tls.existingSecret }} + defaultMode: 0400 + {{- end }} + {{- range .Values.extraValkeyConfigs }} + - name: {{ .name }}-valkey + configMap: + name: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- if .Values.metrics.enabled }} + {{- range .Values.metrics.exporter.extraExporterSecrets }} + - name: {{ .name }}-exporter + secret: + secretName: {{ .name }} + defaultMode: {{ .defaultMode | default 0440 }} + {{- end }} + {{- end }} + {{- if .Values.auth.enabled }} + {{- if .Values.auth.usersExistingSecret }} + - name: valkey-users-secret + secret: + secretName: {{ .Values.auth.usersExistingSecret }} + defaultMode: 0400 + {{- end }} + {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} + - name: valkey-auth-secret + secret: + secretName: {{ include "valkey.fullname" . }}-auth + defaultMode: 0400 + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/valkey/templates/tests/auth.yaml b/charts/valkey/templates/tests/auth.yaml new file mode 100644 index 00000000..d10477ab --- /dev/null +++ b/charts/valkey/templates/tests/auth.yaml @@ -0,0 +1,144 @@ +{{- if .Values.auth.enabled }} +{{- if include "valkey.hasInlinePasswords" . | eq "true" }} +{{- $firstUsername := "" }} +{{- range $username, $user := .Values.auth.aclUsers }} + {{- if $user.password }} + {{- if not $firstUsername }} + {{- $firstUsername = $username }} + {{- end }} + {{- end }} +{{- end }} +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "valkey.fullname" . }}-test-auth-generated + labels: + {{- include "valkey.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + restartPolicy: Never + containers: + - name: test-auth + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + command: + - sh + - -c + - | + set -e + echo "Testing authentication with user {{ $firstUsername }} (inline passwords)..." + + # Extract password from secret + PASSWORD=$(cat /valkey-auth/{{ $firstUsername }}-password) + USERNAME="{{ $firstUsername }}" + + {{- if .Values.tls.enabled }} + # TLS flags + TLS_FLAGS="--tls --cacert /tls/{{ .Values.tls.caPublicKey }}" + {{- else }} + TLS_FLAGS="" + {{- end }} + + # Test authentication + PING_RESULT=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" PING) + if [ "$PING_RESULT" != "PONG" ]; then + echo "✗ Authentication test failed: expected 'PONG', got '$PING_RESULT'" + exit 1 + fi + echo "✓ PING successful: $PING_RESULT" + + # Test that we can set and get a value (if permissions allow) + if valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" SET test-key-generated "test-value" 2>/dev/null; then + VALUE=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" GET test-key-generated) + if [ "$VALUE" = "test-value" ]; then + echo "✓ Authentication test passed with write permissions" + exit 0 + else + echo "✗ Authentication test failed: expected 'test-value', got '$VALUE'" + exit 1 + fi + else + echo "✓ Authentication test passed (read-only or restricted permissions)" + exit 0 + fi + volumeMounts: + - name: valkey-auth + mountPath: /valkey-auth + readOnly: true + {{- if .Values.tls.enabled }} + - name: valkey-tls + mountPath: /tls + readOnly: true + {{- end }} + volumes: + - name: valkey-auth + secret: + secretName: {{ include "valkey.fullname" . }}-auth + {{- if .Values.tls.enabled }} + - name: valkey-tls + secret: + secretName: {{ .Values.tls.existingSecret }} + {{- end }} +{{- end }} +{{- end }} +--- +{{- if and .Values.auth.enabled .Values.auth.usersExistingSecret }} +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "valkey.fullname" . }}-test-auth-existing + labels: + {{- include "valkey.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + restartPolicy: Never + containers: + - name: test-auth + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + command: + - sh + - -c + - | + set -e + echo "Testing authentication with usersExistingSecret..." + + {{- if .Values.tls.enabled }} + # TLS flags + TLS_FLAGS="--tls --cacert /tls/{{ .Values.tls.caPublicKey }}" + {{- else }} + TLS_FLAGS="" + {{- end }} + + # Test basic connection (no auth - will fail if auth is properly configured) + PING_RESULT=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS PING 2>&1 || true) + if [ "$PING_RESULT" = "PONG" ]; then + echo "✗ Authentication test failed: server allows unauthenticated access" + exit 1 + fi + + echo "✓ Authentication is enforced (unauthenticated access denied)" + echo "✓ Received expected error: $PING_RESULT" + echo "⚠ Manual verification recommended for usersExistingSecret configuration" + exit 0 + volumeMounts: + - name: valkey-users-secret + mountPath: /valkey-users-secret + readOnly: true + {{- if .Values.tls.enabled }} + - name: valkey-tls + mountPath: /tls + readOnly: true + {{- end }} + volumes: + - name: valkey-users-secret + secret: + secretName: {{ .Values.auth.usersExistingSecret }} + {{- if .Values.tls.enabled }} + - name: valkey-tls + secret: + secretName: {{ .Values.tls.existingSecret }} + {{- end }} +{{- end }} diff --git a/charts/valkey/values.schema.json b/charts/valkey/values.schema.json new file mode 100644 index 00000000..367c4d39 --- /dev/null +++ b/charts/valkey/values.schema.json @@ -0,0 +1,535 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "affinity": { + "type": "object" + }, + "auth": { + "type": "object", + "properties": { + "aclConfig": { + "type": "string" + }, + "aclUsers": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "usersExistingSecret": { + "type": "string" + } + } + }, + "clusterDomain": { + "type": "string" + }, + "commonLabels": { + "type": "object" + }, + "dataStorage": { + "type": "object", + "properties": { + "accessModes": { + "type": "array", + "items": { + "type": "string" + } + }, + "annotations": { + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "keepPvc": { + "type": "boolean" + }, + "labels": { + "type": "object" + }, + "persistentVolumeClaimName": { + "type": "string" + }, + "requestedSize": { + "type": "string" + }, + "subPath": { + "type": "string" + }, + "volumeName": { + "type": "string" + }, + "hostPath": { + "type": "string" + } + } + }, + "deploymentStrategy": { + "type": "string", + "enum": [ + "RollingUpdate", + "Recreate" + ] + }, + "env": { + "type": "object" + }, + "extraInitContainers": { + "type": "array" + }, + "extraSecretValkeyConfigs": { + "type": "boolean" + }, + "extraVolumes": { + "type": "array" + }, + "extraVolumeMounts": { + "type": "array" + }, + "extraValkeyConfigs": { + "type": "array" + }, + "extraValkeySecrets": { + "type": "array" + }, + "fullnameOverride": { + "type": "string" + }, + "global": { + "type": "object", + "properties": { + "imagePullSecrets": { + "type": "array" + }, + "imageRegistry": { + "type": "string" + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "registry": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "imagePullSecrets": { + "type": "array" + }, + "initResources": { + "type": "object" + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "exporter": { + "type": "object", + "properties": { + "args": { + "type": "array" + }, + "command": { + "type": "array" + }, + "extraEnvs": { + "type": "object" + }, + "extraExporterSecrets": { + "type": "array" + }, + "extraVolumeMounts": { + "type": "array" + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "registry": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "port": { + "type": "integer" + }, + "resources": { + "type": "object" + }, + "securityContext": { + "type": "object" + } + } + }, + "podMonitor": { + "type": "object", + "properties": { + "additionalLabels": { + "type": "object" + }, + "annotations": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "honorLabels": { + "type": "boolean" + }, + "interval": { + "type": "string" + }, + "metricRelabelings": { + "type": "array" + }, + "podTargetLabels": { + "type": "array" + }, + "port": { + "type": "string" + }, + "relabelings": { + "type": "array" + }, + "sampleLimit": { + "type": "boolean" + }, + "scrapeTimeout": { + "type": "string" + }, + "targetLimit": { + "type": "boolean" + } + } + }, + "prometheusRule": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "extraAnnotations": { + "type": "object" + }, + "extraLabels": { + "type": "object" + }, + "rules": { + "type": "array" + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "appProtocol": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "ports": { + "type": "object", + "properties": { + "http": { + "type": "integer" + } + } + }, + "type": { + "type": "string" + } + } + }, + "serviceMonitor": { + "type": "object", + "properties": { + "additionalLabels": { + "type": "object" + }, + "annotations": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "honorLabels": { + "type": "boolean" + }, + "interval": { + "type": "string" + }, + "metricRelabelings": { + "type": "array" + }, + "podTargetLabels": { + "type": "array" + }, + "port": { + "type": "string" + }, + "relabelings": { + "type": "array" + }, + "sampleLimit": { + "type": "boolean" + }, + "scrapeTimeout": { + "type": "string" + }, + "targetLimit": { + "type": "boolean" + } + } + } + } + }, + "nameOverride": { + "type": "string" + }, + "networkPolicy": { + "type": "object" + }, + "nodeSelector": { + "type": "object" + }, + "podAnnotations": { + "type": "object" + }, + "podLabels": { + "type": "object" + }, + "podSecurityContext": { + "type": "object", + "properties": { + "fsGroup": { + "type": "integer" + }, + "runAsGroup": { + "type": "integer" + }, + "runAsUser": { + "type": "integer" + } + } + }, + "priorityClassName": { + "type": "string" + }, + "replica": { + "type": "object", + "properties": { + "disklessSync": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "minReplicasMaxLag": { + "type": "integer" + }, + "minReplicasToWrite": { + "type": "integer" + }, + "persistence": { + "type": "object", + "properties": { + "accessModes": { + "type": "array", + "items": { + "type": "string" + } + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": "string" + } + } + }, + "replicas": { + "type": "integer" + }, + "replicationUser": { + "type": "string" + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "appProtocol": { + "type": "string" + }, + "clusterIP": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "loadBalancerClass": { + "type": "string" + }, + "nodePort": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + } + } + }, + "resources": { + "type": "object" + }, + "securityContext": { + "type": "object", + "properties": { + "capabilities": { + "type": "object", + "properties": { + "drop": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "type": "integer" + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "appProtocol": { + "type": "string" + }, + "clusterIP": { + "type": "string" + }, + "loadBalancerClass": { + "type": "string" + }, + "nodePort": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "annotations": { + "type": "object" + }, + "automount": { + "type": "boolean" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "tls": { + "type": "object", + "properties": { + "caPublicKey": { + "type": "string" + }, + "dhParamKey": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "existingSecret": { + "type": "string" + }, + "requireClientCertificate": { + "type": "boolean" + }, + "serverKey": { + "type": "string" + }, + "serverPublicKey": { + "type": "string" + } + } + }, + "tolerations": { + "type": "array" + }, + "topologySpreadConstraints": { + "type": "array" + }, + "valkeyConfig": { + "type": "string" + }, + "valkeyLogLevel": { + "type": "string" + } + } +} diff --git a/charts/valkey/values.yaml b/charts/valkey/values.yaml new file mode 100644 index 00000000..8580e2ea --- /dev/null +++ b/charts/valkey/values.yaml @@ -0,0 +1,446 @@ +global: + # The global image registry (this will override the registry of all container images defined in this chart) + imageRegistry: "" + # The global image pull secrets (list of secret names) + imagePullSecrets: [] + +image: + # Image registry + registry: "docker.io" + # Valkey container image repository + repository: valkey/valkey + # Image pull policy (Always, IfNotPresent, Never) + pullPolicy: IfNotPresent + # Image tag (leave empty to use .Chart.AppVersion) + tag: "" + +# List of image pull secrets (for private registries) +imagePullSecrets: [] + +# Override the default name or full name of resources +nameOverride: "" +fullnameOverride: "" + +# Kubernetes cluster domain name +clusterDomain: cluster.local + +serviceAccount: + # Create a service account for Valkey + create: true + # Whether to automount the service account token + automount: false + # Annotations to add to the service account + annotations: {} + # Name of an existing service account to use (if create: false) + name: "" + +# Annotations and labels for the pods +podAnnotations: {} +podLabels: {} + +# Common labels to add to all resources (Deployment, Service, ConfigMap, etc.) +commonLabels: {} + +# Security context for the pod (applies to all containers) +podSecurityContext: + fsGroup: 1000 + runAsUser: 1000 + runAsGroup: 1000 + +# Priority class name for pod scheduling (leave empty to use cluster's default) +priorityClassName: "" + +# Security context for the Valkey containers +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +service: + # Type of Kubernetes service (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + # Port on which Valkey will be exposed + port: 6379 + annotations: {} + # NodePort value (if service.type is NodePort) + nodePort: 0 + # ClusterIP value + clusterIP: "" + # Class of a load balancer implementation + loadBalancerClass: "" + # Application protocol + appProtocol: "" + +# Network policy to control traffic to the pods +# More info: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +networkPolicy: {} + +# Resource limits/requests for the main Valkey container +resources: {} + # Example: + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# Resource limits/requests for init containers +initResources: {} + # Example: + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# Additional init containers +extraInitContainers: [] + +# Persistent storage configuration (standalone deployment only) +dataStorage: + # Enable persistent volume claim creation + enabled: false + + # Use existing PVC by name (skip dynamic provisioning if set) + persistentVolumeClaimName: "" + + # Subpath inside PVC to mount + subPath: "" + + # Name of the volume (referenced in deployment) + volumeName: "valkey-data" + + # Request size (e.g. 5Gi) for dynamically provisioned volume + requestedSize: "" + + # Name of the storage class to use + className: "" + + # Access modes for the PVC (e.g., ReadWriteOnce, ReadWriteMany) + accessModes: + - ReadWriteOnce + + # If true, keep the PVC on Helm uninstall + keepPvc: false + + # Optional annotations to add to the PVC + annotations: {} + + # Optional labels to add to the PVC + labels: {} + + # If hostPath is set then a hostPath DirectoryOrCreate volume is used + hostPath: "" + +# Mount additional secrets into the Valkey container +extraValkeySecrets: [] + +# Mount additional configMaps into the Valkey container +extraValkeyConfigs: [] + +# Mount extra secrets as volume to init container (deprecated, use extraValkeySecrets) +extraSecretValkeyConfigs: false + +# Mount additional emptyDir or hostPath volumes (advanced use) +extraVolumes: [] +# - name: hostpath-volume +# hostPath: +# path: /opt/valkey/hostpath-volume/ +# type: DirectoryOrCreate +extraVolumeMounts: [] +# - mountPath: /some/path/ +# name: hostpath-volume + +# Content for valkey.conf (will be mounted via ConfigMap) +valkeyConfig: "" + +auth: + # Enable ACL-based authentication + # IMPORTANT: When authentication is enabled, the 'default' user MUST be defined in either + # aclUsers or aclConfig. Without a default user, anyone can access the database without + # credentials, creating a security risk. + enabled: false + + # Use an existing secret for user passwords. Key defaults to username. + usersExistingSecret: "" + + # Map of users to create with ACL permissions. + # If usersExistingSecret is set, passwords from the secret take priority over inline passwords. + # NOTE: If using aclUsers, the 'default' user must be included here. + aclUsers: {} + # Example: + # default: + # permissions: "~* &* +@all" + # password: "secretpass" # Inline password (fallback if usersExistingSecret not set) + # passwordKey: "admin-pwd" # Key in usersExistingSecret (defaults to username) + # read-user: + # permissions: "~* -@all +@read +ping +info" + + # Inline ACL configuration that will be appended after generated users. + # NOTE: If using aclConfig, ensure the 'default' user is defined here. + aclConfig: "" + # Example: + # aclConfig: | + # user default on >secretpass ~* &* +@all + +# Replica configuration for master-replica replication mode +replica: + enabled: false + + # Number of replica instances (total pods = replicas + 1 master) + replicas: 2 + + # Username for replicas to authenticate to master, ignored if auth.enabled is false. + # IMPORTANT: When auth.enabled is true, this user MUST be defined in auth.aclUsers. + # The chart requires this to retrieve the password for replica authentication. + # The user must have appropriate replication permissions: +psync +replconf +ping + replicationUser: "default" + + # Replication settings + # Use diskless replication (sync directly from memory) vs disk-based + disklessSync: false + + # Write safety - require minimum number of healthy replicas to accept writes + # Set to 0 to disable this check, or 1+ to require minimum replicas before accepting writes + # This ensures data durability by requiring at least N replicas to be in sync + minReplicasToWrite: 0 + + # Maximum replication lag in seconds before a replica is considered unhealthy + minReplicasMaxLag: 10 + + # Read service configuration + service: + # Enable read service (load balances read traffic across all pods) + enabled: true + # Service type (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + # Port on which the read service will be exposed + port: 6379 + # Optional annotations for the read service + annotations: {} + # NodePort value (if service.type is NodePort) + nodePort: 0 + # ClusterIP value + clusterIP: "" + # Application protocol + appProtocol: "" + # Class of a load balancer implementation + loadBalancerClass: "" + + # Persistence configuration (required for replicas) + persistence: + # Size of the PVC for each replica (required when replica.enabled is true) + size: "" + # Storage class name (empty = use default storage class) + storageClass: "" + # Access modes for the PVC + accessModes: + - ReadWriteOnce + +tls: + # Enable TLS + enabled: false + # Name of the Secret containing TLS keys (required) + existingSecret: "" + # Secret key name containing server public certificate + serverPublicKey: server.crt + # Secret key name containing server private key + serverKey: server.key + # Secret key name containing Certificate Authority public certificate + caPublicKey: ca.crt + # Secret key name containing DH parameters (optional) + dhParamKey: "" + # Require that clients authenticate with a certificate + requireClientCertificate: false + +# Node selector for pod assignment +nodeSelector: {} + +# Tolerations for pod assignment to tainted nodes +tolerations: [] + +# Affinity rules for pod scheduling +affinity: {} + +# Set Deployment strategy. See https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy +deploymentStrategy: RollingUpdate # @schema enum:[RollingUpdate,Recreate] + +# See https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints +topologySpreadConstraints: [] + +# Valkey logging level: debug, verbose, notice, warning +valkeyLogLevel: "notice" +# Environment variables to inject into Valkey container + +env: {} + # Example: + # LOG_LEVEL: info + +metrics: + # Enable Prometheus exporter sidecar + enabled: false + # Exporter configuration + exporter: + # Command to run in the metrics exporter container (overrides args) + command: [] + # Arguments to pass to the metrics exporter container + args: [] + # Example: + # - --redis.addr=redis:6379 + # Port on which the metrics exporter will listen + port: 9121 + # Image configuration + image: + # Image registry + registry: ghcr.io + # Prometheus exporter container image repository + repository: oliver006/redis_exporter + # Image pull policy (Always, IfNotPresent, Never) + pullPolicy: IfNotPresent + # Image tag (leave empty to use latest) + tag: "v1.79.0" + resources: {} + # Example: + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + # Extra volume mounts for metrics exporter container + extraVolumeMounts: [] + # Additional secrets to mount for metrics exporter + extraExporterSecrets: [] + # Environment variables to inject into the metrics exporter container + extraEnvs: {} + # Example: + # LOG_LEVEL: info + securityContext: {} + # Example: + # runAsNonRoot: true + # runAsUser: 1000 + # runAsGroup: 1000 + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + + # Service configuration for the metrics exporter + service: + # Enable a separate service for the metrics exporter + enabled: true + # Service type (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + # Port on which the metrics exporter service will be exposed + ports: + http: 9121 + # Optional annotations for the metrics exporter service + annotations: {} + # Optional labels for the metrics exporter service + extraLabels: {} + # ServiceMonitor configuration for Prometheus Operator + # Application protocol + appProtocol: "" + serviceMonitor: + # Enable ServiceMonitor resource for scraping service metrics + enabled: false + # Port name or number to scrape metrics from + port: metrics + # Extra labels for the ServiceMonitor resource + extraLabels: {} + # Extra annotations for the ServiceMonitor resource + annotations: {} + # How often Prometheus should scrape metrics + interval: 30s + # Maximum duration allowed for a scrape request + scrapeTimeout: "" + # Relabeling rules applied before scraping metrics + relabelings: [] + # Relabeling rules applied before ingesting metrics + metricRelabelings: [] + # Set honorLabels to true to preserve original metric labels + honorLabels: false + # Extra labels to help Prometheus discover ServiceMonitor resources + additionalLabels: {} + # Pod labels to copy onto the generated metrics + podTargetLabels: [] + # Maximum number of samples to collect per Pod scrape + sampleLimit: false + # Maximum number of scrape targets allowed + targetLimit: false + podMonitor: + # Enable PodMonitor resource for scraping pod metrics + enabled: false + # Port name or number to scrape metrics from + port: metrics + # Extra labels for the ServiceMonitor resource + extraLabels: {} + # Extra annotations for the ServiceMonitor resource + annotations: {} + # Frequency for Prometheus to scrape pod metrics + interval: 30s + # Time limit for each scrape operation + scrapeTimeout: "" + # Relabeling rules to apply before scraping pod metrics + relabelings: [] + # Relabeling rules to apply before ingesting pod metrics + metricRelabelings: [] + # If true, keeps original labels from the pod metrics + honorLabels: false + # Additional labels for Prometheus to find PodMonitor resources + additionalLabels: {} + # Pod labels to attach to the metrics + podTargetLabels: [] + # Maximum samples to scrape from each Pod + sampleLimit: false + # Maximum number of pods to scrape + targetLimit: false + + # PrometheusRule configuration for alerting rules (used by kube-prometheus-stack) + prometheusRule: + # Enable creation of PrometheusRule resource + enabled: false + # Extra labels to add to the PrometheusRule resource + extraLabels: {} + # Extra annotations to add to the PrometheusRule resource + extraAnnotations: {} + # List of Prometheus alerting rules + rules: [] + # Example alerting rules: + # - alert: ValkeyDown + # annotations: + # summary: Valkey instance {{ "{{ $labels.instance }}" }} down + # description: Valkey instance {{ "{{ $labels.instance }}" }} is down. + # expr: | + # redis_up{service="{{ include "valkey.fullname" . }}-metrics"} == 0 + # for: 2m + # labels: + # severity: error + # - alert: ValkeyMemoryHigh + # annotations: + # summary: Valkey instance {{ "{{ $labels.instance }}" }} is using too much memory + # description: | + # Valkey instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. + # expr: | + # redis_memory_used_bytes{service="{{ include "valkey.fullname" . }}-metrics"} * 100 + # / + # redis_memory_max_bytes{service="{{ include "valkey.fullname" . }}-metrics"} + # > 90 <= 100 + # for: 2m + # labels: + # severity: error + # - alert: ValkeyKeyEviction + # annotations: + # summary: Valkey instance {{ "{{ $labels.instance }}" }} has evicted keys + # description: | + # Valkey instance {{ "{{ $labels.instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. + # expr: | + # increase(redis_evicted_keys_total{service="{{ include "valkey.fullname" . }}-metrics"}[5m]) > 0 + # for: 1s + # labels: + # severity: error From 9250efedc69275bcaf0555871c3a76f27bba5465 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 14:56:47 -0400 Subject: [PATCH 06/13] replace valkey --- Chart.yaml | 2 +- charts/valkey-0.9.3.tgz | Bin 0 -> 19409 bytes charts/valkey/.helmignore | 28 - charts/valkey/Chart.yaml | 14 - charts/valkey/README.md | 353 ------------ charts/valkey/templates/NOTES.txt | 137 ----- charts/valkey/templates/_helpers.tpl | 190 ------- charts/valkey/templates/configmap.yaml | 11 - charts/valkey/templates/deploy_valkey.yaml | 289 ---------- charts/valkey/templates/init_config.yaml | 231 -------- charts/valkey/templates/metrics-svc.yaml | 29 - charts/valkey/templates/netpolicy.yaml | 33 -- charts/valkey/templates/podmonitor.yaml | 53 -- charts/valkey/templates/prometheusrules.yaml | 22 - charts/valkey/templates/pvc.yaml | 30 - charts/valkey/templates/secret.yaml | 20 - charts/valkey/templates/service-headless.yaml | 20 - charts/valkey/templates/service-read.yaml | 34 -- charts/valkey/templates/service.yaml | 35 -- charts/valkey/templates/serviceaccount.yaml | 13 - charts/valkey/templates/servicemonitor.yaml | 54 -- charts/valkey/templates/statefulset.yaml | 279 --------- charts/valkey/templates/tests/auth.yaml | 144 ----- charts/valkey/values.schema.json | 535 ------------------ charts/valkey/values.yaml | 446 --------------- 25 files changed, 1 insertion(+), 3001 deletions(-) create mode 100644 charts/valkey-0.9.3.tgz delete mode 100644 charts/valkey/.helmignore delete mode 100644 charts/valkey/Chart.yaml delete mode 100644 charts/valkey/README.md delete mode 100644 charts/valkey/templates/NOTES.txt delete mode 100644 charts/valkey/templates/_helpers.tpl delete mode 100644 charts/valkey/templates/configmap.yaml delete mode 100644 charts/valkey/templates/deploy_valkey.yaml delete mode 100644 charts/valkey/templates/init_config.yaml delete mode 100644 charts/valkey/templates/metrics-svc.yaml delete mode 100644 charts/valkey/templates/netpolicy.yaml delete mode 100644 charts/valkey/templates/podmonitor.yaml delete mode 100644 charts/valkey/templates/prometheusrules.yaml delete mode 100644 charts/valkey/templates/pvc.yaml delete mode 100644 charts/valkey/templates/secret.yaml delete mode 100644 charts/valkey/templates/service-headless.yaml delete mode 100644 charts/valkey/templates/service-read.yaml delete mode 100644 charts/valkey/templates/service.yaml delete mode 100644 charts/valkey/templates/serviceaccount.yaml delete mode 100644 charts/valkey/templates/servicemonitor.yaml delete mode 100644 charts/valkey/templates/statefulset.yaml delete mode 100644 charts/valkey/templates/tests/auth.yaml delete mode 100644 charts/valkey/values.schema.json delete mode 100644 charts/valkey/values.yaml diff --git a/Chart.yaml b/Chart.yaml index f3ccc2a2..0f7debb8 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.0 +version: 1.1.1 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg diff --git a/charts/valkey-0.9.3.tgz b/charts/valkey-0.9.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1f6ad50dbc3d630cf0cf5017cdb4d500f81b2a8e GIT binary patch literal 19409 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 literal 0 HcmV?d00001 diff --git a/charts/valkey/.helmignore b/charts/valkey/.helmignore deleted file mode 100644 index 26f45eac..00000000 --- a/charts/valkey/.helmignore +++ /dev/null @@ -1,28 +0,0 @@ -# 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 -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ - -.github/ - -# Unit tests (only ignore root-level tests/, not templates/tests/) -/tests/ \ No newline at end of file diff --git a/charts/valkey/Chart.yaml b/charts/valkey/Chart.yaml deleted file mode 100644 index 499811c3..00000000 --- a/charts/valkey/Chart.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v2 -appVersion: 9.0.1 -description: A Helm chart for Kubernetes -home: https://valkey.io/valkey-helm/ -icon: https://dyltqmyl993wv.cloudfront.net/assets/stacks/valkey/img/valkey-stack-220x234.png -maintainers: -- name: raven - url: https://github.com/mk-raven -name: valkey -sources: -- https://github.com/valkey-io/valkey-helm.git -- https://valkey.io -type: application -version: 0.9.3 diff --git a/charts/valkey/README.md b/charts/valkey/README.md deleted file mode 100644 index 0487321e..00000000 --- a/charts/valkey/README.md +++ /dev/null @@ -1,353 +0,0 @@ -# valkey - -![Version: 0.9.0](https://img.shields.io/badge/Version-0.9.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 9.0.1](https://img.shields.io/badge/AppVersion-9.0.1-informational?style=flat-square) - -A Helm chart for Kubernetes - -**Homepage:** - -## Maintainers - -| Name | Url | -| ---- | --- | -| raven | [https://github.com/mk-raven] | -| sgissi | [https://github.com/sgissi] | - -## Source Code - -* -* - -## Deployment Modes - -### Standalone Mode (Default) - -Deploy a single Valkey instance: - -```bash -helm install valkey valkey/valkey -``` - -**Services:** - -* `valkey`: Master/read-write service - -### Replication Mode - -Deploy Valkey with master-replica architecture for read scaling and data redundancy: - -```bash -helm install valkey valkey/valkey --set replica.enabled=true --set replica.persistence.size=5Gi -``` - -**Services:** - -* `valkey`: Master/write service -* `valkey-read`: Read service (load-balances across all pods) - optional -* `valkey-headless`: Headless service for pod discovery - -**Write Safety Configuration:** - -Ensure data durability by requiring a minimum number of replicas to be in sync before accepting writes: - -```yaml -replica: - minReplicasToWrite: 1 # Require at least 1 replica - minReplicasMaxLag: 10 # Max 10 seconds replication lag -``` - -If fewer than `minReplicasToWrite` replicas are available, the master will reject write operations. - -## Storage - -### Standalone Storage - -Persistence is optional. By default, data is stored in an ephemeral volume and lost on pod restart. - -**Enable persistent storage:** - -```yaml -dataStorage: - enabled: true - requestedSize: 10Gi - className: "fast-ssd" # Optional -``` - -**Use existing PVC:** - -```yaml -dataStorage: - enabled: true - persistentVolumeClaimName: "my-existing-pvc" -``` - -### Replication Storage - -Persistent storage is **mandatory** in replication mode. Without it, the primary might comes up with an empty dataset after a restart, all replicas will synchronize with the empty primary and lose their data. See [Valkey Replication Safety](https://valkey.io/topics/replication/#safety-of-replication-when-primary-has-persistence-turned-off) for details. - -```yaml -replica: - enabled: true - persistence: - size: 10Gi # Required - storageClass: "fast-ssd" # Optional -``` - -## Authentication - -This chart supports ACL-based authentication for Valkey. - -**⚠️ IMPORTANT:** When authentication is enabled, the `default` user **MUST** be defined in either `auth.aclUsers` or `auth.aclConfig`. Without a default user, anyone can access the database without credentials. - -### Existing Secret (recommended) - -Reference an existing Kubernetes secret containing user passwords: - -```yaml -auth: - enabled: true - usersExistingSecret: "my-valkey-users" - aclUsers: - default: - permissions: "~* &* +@all" - # Password will be read from secret key "default" (defaults to username) - readonly: - permissions: "~* -@all +@read +ping +info" - passwordKey: "readonly-pwd" # Use custom secret key name -``` - -### Inline Passwords - -Define users directly in your values file with inline passwords: - -```yaml -auth: - enabled: true - aclUsers: - default: - permissions: "~* &* +@all" - password: "default-password" - readonly: - permissions: "~* -@all +@read +ping +info" - password: "readonly-password" -``` - -**Note:** - -* If `usersExistingSecret` is defined, passwords from the secret will take precedence over inline passwords. - -### Custom ACL Configuration - -You can also provide raw ACL configuration that will be appended after any generated users: - -```yaml -auth: - enabled: true - aclConfig: | - user default on >defaultpassword ~* &* +@all - user guest on nopass ~public:* +@read -``` - -### Replication with Authentication - -When using ACL authentication in replication mode, replicas need credentials to authenticate to the master: - -```yaml -auth: - enabled: true - usersExistingSecret: "my-valkey-users" - aclUsers: - default: - permissions: "~* &* +@all" - replication-user: - permissions: "+psync +replconf +ping" - -replica: - enabled: true - replicas: 2 - replicationUser: "replication-user" # Must be defined in auth.aclUsers -``` - -**Important Notes:** - -* `replica.replicationUser` specifies which ACL user replicas use to authenticate -* This user MUST be defined in `auth.aclUsers` with appropriate permissions -* Minimum permissions: `+psync +replconf +ping` - -## Metrics - -This chart supports Prometheus metrics collection using the [Redis exporter](https://github.com/oliver006/redis_exporter). - -Enable the metrics exporter sidecar: - -```yaml -metrics: - enabled: true -``` - -### Prometheus Operator discovery - -Automated Prometheus discovery using the Prometheus Operator ServiceMonitor: - -```yaml -metrics: - enabled: true - serviceMonitor: - enabled: true -``` - -## TLS - -This chart supports TLS encryption for Valkey connections. - -First create a secret containing the certificate public and private keys plus CA public key: - -```shell -kubectl create secret generic valkey-tls-secret --from-file=server.crt --from-file=server.key --from-file=ca.crt -``` - -Enable TLS and provide the name of the secret created above: - -```yaml -tls: - enabled: true - existingSecret: "valkey-tls-secret" -``` - -## Values - -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| global.imageRegistry | string | '' | | -| global.imagePullSecrets | list | `[]` | | -| affinity | object | `{}` | | -| auth.aclConfig | string | `""` | | -| auth.aclUsers | object | `{}` | | -| auth.enabled | bool | `false` | | -| auth.usersExistingSecret | string | `""` | | -| dataStorage.accessModes[0] | string | `"ReadWriteOnce"` | | -| dataStorage.annotations | object | `{}` | | -| dataStorage.className | string | `""` | | -| dataStorage.enabled | bool | `false` | | -| dataStorage.keepPvc | bool | `false` | | -| dataStorage.labels | object | `{}` | | -| dataStorage.persistentVolumeClaimName | string | `""` | | -| dataStorage.requestedSize | string | `""` | | -| dataStorage.subPath | string | `""` | | -| dataStorage.volumeName | string | `"valkey-data"` | | -| dataStorage.hostPath | string | `""` | | -| deploymentStrategy | string | `"RollingUpdate"` | | -| env | object | `{}` | | -| extraSecretValkeyConfigs | bool | `false` | | -| extraVolumes | list | `[]` | | -| extraVolumeMounts | list | `[]` | | -| extraValkeyConfigs | list | `[]` | | -| extraValkeySecrets | list | `[]` | | -| fullnameOverride | string | `""` | | -| image.pullPolicy | string | `"IfNotPresent"` | | -| image.registry | string | `""` | | -| image.repository | string | `"docker.io/valkey/valkey"` | | -| image.tag | string | `""` | | -| imagePullSecrets | list | `[]` | | -| initResources | object | `{}` | | -| metrics.enabled | bool | `false` | | -| metrics.exporter.args | list | `[]` | | -| metrics.exporter.command | list | `[]` | | -| metrics.exporter.extraEnvs | object | `{}` | | -| metrics.exporter.extraVolumeMounts | list | `[]` | | -| metrics.exporter.image.pullPolicy | string | `"IfNotPresent"` | | -| metrics.exporter.image.repository | string | `"ghcr.io/oliver006/redis_exporter"` | | -| metrics.exporter.image.tag | string | `"v1.79.0"` | | -| metrics.exporter.port | int | `9121` | | -| metrics.exporter.resources | object | `{}` | | -| metrics.exporter.securityContext | object | `{}` | | -| metrics.podMonitor.additionalLabels | object | `{}` | | -| metrics.podMonitor.annotations | object | `{}` | | -| metrics.podMonitor.enabled | bool | `false` | | -| metrics.podMonitor.extraLabels | object | `{}` | | -| metrics.podMonitor.honorLabels | bool | `false` | | -| metrics.podMonitor.interval | string | `"30s"` | | -| metrics.podMonitor.metricRelabelings | list | `[]` | | -| metrics.podMonitor.podTargetLabels | list | `[]` | | -| metrics.podMonitor.port | string | `"metrics"` | | -| metrics.podMonitor.relabelings | list | `[]` | | -| metrics.podMonitor.sampleLimit | bool | `false` | | -| metrics.podMonitor.scrapeTimeout | string | `""` | | -| metrics.podMonitor.targetLimit | bool | `false` | | -| metrics.prometheusRule.enabled | bool | `false` | | -| metrics.prometheusRule.extraAnnotations | object | `{}` | | -| metrics.prometheusRule.extraLabels | object | `{}` | | -| metrics.prometheusRule.rules | list | `[]` | | -| metrics.service.annotations | object | `{}` | | -| metrics.service.enabled | bool | `true` | | -| metrics.service.extraLabels | object | `{}` | | -| metrics.service.ports.http | int | `9121` | | -| metrics.service.type | string | `"ClusterIP"` | | -| metrics.service.appProtocol | string | `""` | | -| metrics.serviceMonitor.additionalLabels | object | `{}` | | -| metrics.serviceMonitor.annotations | object | `{}` | | -| metrics.serviceMonitor.enabled | bool | `false` | | -| metrics.serviceMonitor.extraLabels | object | `{}` | | -| metrics.serviceMonitor.honorLabels | bool | `false` | | -| metrics.serviceMonitor.interval | string | `"30s"` | | -| metrics.serviceMonitor.metricRelabelings | list | `[]` | | -| metrics.serviceMonitor.podTargetLabels | list | `[]` | | -| metrics.serviceMonitor.port | string | `"metrics"` | | -| metrics.serviceMonitor.relabelings | list | `[]` | | -| metrics.serviceMonitor.sampleLimit | bool | `false` | | -| metrics.serviceMonitor.scrapeTimeout | string | `""` | | -| metrics.serviceMonitor.targetLimit | bool | `false` | | -| nameOverride | string | `""` | | -| networkPolicy | object | `{}` | | -| nodeSelector | object | `{}` | | -| podAnnotations | object | `{}` | | -| podLabels | object | `{}` | | -| commonLabels | object | `{}` | | -| podSecurityContext.fsGroup | int | `1000` | | -| podSecurityContext.runAsGroup | int | `1000` | | -| podSecurityContext.runAsUser | int | `1000` | | -| priorityClassName | string | `""` | | -| replica.enabled | bool | `false` | | -| replica.replicas | int | `2` | | -| replica.replicationUser | string | `"default"` | | -| replica.disklessSync | bool | `false` | | -| replica.minReplicasToWrite | int | `0` | | -| replica.minReplicasMaxLag | int | `10` | | -| replica.service.enabled | bool | `"true"` | | -| replica.service.type | string | `"ClusterIP"` | | -| replica.service.port | int | `6379` | | -| replica.service.annotations | object | `{}` | | -| replica.service.nodePort | int | `0` | | -| replica.service.clusterIP | string | `""` | | -| replica.service.appProtocol | string | `""` | | -| replica.service.loadBalancerClass | string | `""` | | -| replica.persistence. | | `""` | | -| replica.persistence.size | string | `""` | Required if replica is enabled | -| replica.persistence.storageClass | string | `""` | | -| replica.persistence.accessModes | list | `""` | | -| resources | object | `{}` | | -| securityContext.capabilities.drop[0] | string | `"ALL"` | | -| securityContext.readOnlyRootFilesystem | bool | `true` | | -| securityContext.runAsNonRoot | bool | `true` | | -| securityContext.runAsUser | int | `1000` | | -| service.annotations | object | `{}` | | -| service.nodePort | int | `0` | | -| service.port | int | `6379` | | -| service.type | string | `"ClusterIP"` | | -| service.appProtocol | string | `""` | | -| service.loadBalancerClass | string | `""` | | -| serviceAccount.annotations | object | `{}` | | -| serviceAccount.automount | bool | `false` | | -| serviceAccount.create | bool | `true` | | -| serviceAccount.name | string | `""` | | -| tls.caPublicKey | string | `"ca.crt"` | | -| tls.dhParamKey | string | `""` | | -| tls.enabled | bool | `false` | | -| tls.existingSecret | string | `""` | | -| tls.requireClientCertificate | bool | `false` | | -| tls.serverKey | string | `"server.key"` | | -| tls.serverPublicKey | string | `"server.crt"` | | -| tolerations | list | `[]` | | -| topologySpreadConstraints | list | `[]` | | -| valkeyConfig | string | `""` | | -| valkeyLogLevel | string | `"notice"` | | diff --git a/charts/valkey/templates/NOTES.txt b/charts/valkey/templates/NOTES.txt deleted file mode 100644 index 07ddb6dd..00000000 --- a/charts/valkey/templates/NOTES.txt +++ /dev/null @@ -1,137 +0,0 @@ -{{/* - NOTES for Valkey Helm Chart - This file is rendered after `helm install` / `helm upgrade`. -*/}} - -⭐ Valkey has been deployed! - -Release: {{ .Release.Name }} -Namespace: {{ .Release.Namespace }} -Chart: {{ .Chart.Name }} {{ .Chart.Version }} -App version: {{ .Chart.AppVersion }} - -{{- if .Values.replica.enabled }} -================================================================================ -🔄 REPLICATION MODE -================================================================================ - -Your Valkey deployment is running in REPLICATION mode: -- 1 Master ({{ include "valkey.fullname" . }}-0) -- {{ .Values.replica.replicas }} Replica(s) - -{{- if .Values.replica.service.enabled }} - -READ Operations (Load-balanced across all pods): - Service: {{ include "valkey.fullname" . }}-read - Type: {{ .Values.replica.service.type }} - Port: {{ .Values.replica.service.port }} - -1) In-cluster access: - $ valkey-cli -h {{ include "valkey.fullname" . }}-read -p {{ .Values.replica.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} GET key - -2) Local access via kubectl port-forward: - $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }}-read 6379:{{ .Values.replica.service.port }} - $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} GET key -{{ if eq .Values.replica.service.type "LoadBalancer" }} -3) External access (LoadBalancer): - $ export SERVICE_IP=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }}-read -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - $ valkey-cli -h $SERVICE_IP -p {{ .Values.replica.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} GET key -{{ else if eq .Values.replica.service.type "NodePort" }} -3) External access (NodePort): - $ export NODE_PORT=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }}-read -o jsonpath='{.spec.ports[0].nodePort}') - $ export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') - $ valkey-cli -h $NODE_IP -p $NODE_PORT{{ if .Values.tls.enabled }} --tls{{- end }} GET key -{{ end }} -{{- end }} - -Direct Pod Access: - Master: {{ include "valkey.fullname" . }}-0.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} -{{- range $i := until (int .Values.replica.replicas) }} - Replica: {{ include "valkey.fullname" $ }}-{{ add $i 1 }}.{{ include "valkey.headlessServiceName" $ }}.{{ $.Release.Namespace }}.svc.{{ $.Values.clusterDomain }} -{{- end }} - -WRITE Operations (Master only): - Service: {{ include "valkey.fullname" . }} - Type: {{ .Values.service.type }} - Port: {{ .Values.service.port }} - -1) In-cluster access: - $ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING - -2) Local access: - $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }} 6379:{{ .Values.service.port }} - $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} SET key value -{{- else }} -================================================================================ -📦 STANDALONE MODE -================================================================================ - -Service: {{ include "valkey.fullname" . }} -Type: {{ .Values.service.type }} -Port: {{ .Values.service.port }} - -1) In-cluster access - From another Pod: - $ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING - -2) Local access via kubectl port-forward - $ kubectl -n {{ .Release.Namespace }} port-forward svc/{{ include "valkey.fullname" . }} 6379:{{ .Values.service.port }} - In another terminal: - $ valkey-cli -h 127.0.0.1 -p 6379{{ if .Values.tls.enabled }} --tls{{- end }} PING -{{- end }} -{{ if eq .Values.service.type "LoadBalancer" }} -3) External access (LoadBalancer) - $ export SERVICE_IP=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') - $ valkey-cli -h $SERVICE_IP -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }} PING -{{ else if eq .Values.service.type "NodePort" }} -3) External access (NodePort) - $ export NODE_PORT=$(kubectl -n {{ .Release.Namespace }} get svc {{ include "valkey.fullname" . }} -o jsonpath='{.spec.ports[0].nodePort}') - $ export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') - $ valkey-cli -h $NODE_IP -p $NODE_PORT{{ if .Values.tls.enabled }} --tls{{- end }} PING -{{ end }} - -{{ if .Values.auth.enabled }} -🔐 Authentication is ENABLED (ACL) -- Provide the username and password from `.auth.aclUsers`. -{{ else }} -🔓 Authentication is DISABLED -- Enable with: `--set auth.enabled=true` and provide `.auth.aclUsers`. -{{ end }} - -✅ Quick test -$ valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }}{{ if .Values.tls.enabled }} --tls{{- end }}{{ if .Values.auth.enabled }} --user -a {{ end }} -valkey> SET foo bar -valkey> GET foo -"bar" - -💾 Persistence -{{- if .Values.replica.enabled }} -- Persistence is ENABLED (required for replication mode). Each instance has its own volume. -- Size: {{ .Values.replica.persistence.size }} -{{- if .Values.replica.persistence.storageClass }} -- Storage class: {{ .Values.replica.persistence.storageClass }} -{{- end }} -- To see PVCs: - $ kubectl -n {{ .Release.Namespace }} get pvc -l app.kubernetes.io/instance={{ .Release.Name }} -{{- else }} -{{ if .Values.dataStorage.enabled -}} -{{ if .Values.dataStorage.hostPath -}} -- Persistence is ENABLED. A hostPath volume is used at path='`{{ .Values.dataStorage.hostPath }}`'. -{{ else -}} -- Persistence is ENABLED. To see it: - $ kubectl -n {{ .Release.Namespace }} get pvc -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/name={{ include "valkey.name" . }} -- Note: `dataStorage.keepPvc={{ .Values.dataStorage.keepPvc }}` controls whether the PVC is kept on uninstall. -{{ end -}} -{{ else -}} -- Persistence is DISABLED. Data will not survive Pod restarts. -- Enable with: - `--set dataStorage.enabled=true --set dataStorage.requestedSize=5Gi` (optionally set `dataStorage.className`) - or - `--set dataStorage.enabled=true --set dataStorage.persistentVolumeClaimName=valkey-data-pvc` to use an existing PVC - or - `--set dataStorage.enabled=true --set dataStorage.hostPath=/some/path/` to use a hostPath volume. -{{- end }} -{{- end }} - -🧹 Uninstall -$ helm -n {{ .Release.Namespace }} uninstall {{ .Release.Name }} diff --git a/charts/valkey/templates/_helpers.tpl b/charts/valkey/templates/_helpers.tpl deleted file mode 100644 index 593cf77c..00000000 --- a/charts/valkey/templates/_helpers.tpl +++ /dev/null @@ -1,190 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "valkey.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 "valkey.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 "valkey.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "valkey.labels" -}} -helm.sh/chart: {{ include "valkey.chart" . }} -{{ include "valkey.selectorLabels" . }} -{{- if or .Values.image.tag .Chart.AppVersion }} -app.kubernetes.io/version: {{ mustRegexReplaceAllLiteral "@sha.*" .Values.image.tag "" | default .Chart.AppVersion | trunc 63 | trimSuffix "-" | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- with .Values.commonLabels }} -{{- toYaml . | nindent 0 }} -{{- end }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "valkey.selectorLabels" -}} -app.kubernetes.io/name: {{ include "valkey.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "valkey.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "valkey.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} - -{{/* -Returns the Valkey container image -*/}} -{{- define "valkey.image" -}} -{{- include "common.image" (dict "image" (dict "registry" .Values.image.registry "repository" .Values.image.repository "tag" (.Values.image.tag | default .Chart.AppVersion)) "global" .Values.global) }} -{{- end -}} - -{{/* -Returns the Valkey exporter container image -*/}} -{{- define "valkey.metrics.exporter.image" -}} -{{- include "common.image" (dict "image" .Values.metrics.exporter.image "global" .Values.global) }} -{{- end -}} - -{{/* -The common image function that renders the container image -*/}} -{{- define "common.image" -}} -{{- $registryName := .image.registry }} -{{- $repositoryName := .image.repository }} -{{- $tag := .image.tag }} -{{- 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 -}} - -{{/* -Returns the Valkey image pull secrets -*/}} -{{- define "valkey.imagePullSecrets" -}} -{{- $pullSecrets := list }} -{{- if .Values.global }} - {{- range .Values.global.imagePullSecrets -}} - {{- $pullSecrets = append $pullSecrets . -}} - {{- end -}} -{{- end -}} -{{- range .Values.imagePullSecrets -}} - {{- $pullSecrets = append $pullSecrets . -}} -{{- end -}} -{{- if (not (empty $pullSecrets)) }} -imagePullSecrets: -{{- range $pullSecrets }} -- name: {{ . }} -{{- end }} -{{- end }} -{{- end -}} - -{{/* -Check if there are any users with inline passwords -*/}} -{{- define "valkey.hasInlinePasswords" -}} -{{- $hasInlinePasswords := false -}} -{{- range $username, $user := .Values.auth.aclUsers -}} - {{- if $user.password -}} - {{- $hasInlinePasswords = true -}} - {{- end -}} -{{- end -}} -{{- $hasInlinePasswords -}} -{{- end -}} - -{{/* -Validate auth configuration -*/}} -{{- define "valkey.validateAuthConfig" -}} -{{- if .Values.auth.enabled }} - {{- if not (or .Values.auth.aclUsers .Values.auth.aclConfig) }} - {{- fail "auth.enabled is true but no authentication method is configured. Please provide auth.aclUsers or auth.aclConfig" }} - {{- end }} - {{- if .Values.auth.aclUsers }} - {{- $hasUsersExistingSecret := .Values.auth.usersExistingSecret }} - {{- if not (hasKey .Values.auth.aclUsers "default") }} - {{- fail "The 'default' user must be defined in auth.aclUsers when authentication is enabled. Without it, anyone can access the database without credentials." }} - {{- end }} - {{- range $username, $user := .Values.auth.aclUsers }} - {{- if not $user.permissions }} - {{- fail (printf "User '%s' in auth.aclUsers must have a 'permissions' field" $username) }} - {{- end }} - {{- if not (or $user.password $hasUsersExistingSecret) }} - {{- fail (printf "User '%s' must have either 'password' field or auth.usersExistingSecret must be set" $username) }} - {{- end }} - {{- if and $user.passwordKey (not $hasUsersExistingSecret) }} - {{- fail (printf "User '%s' has passwordKey but auth.usersExistingSecret is not set" $username) }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* -Headless service name for replication -*/}} -{{- define "valkey.headlessServiceName" -}} -{{ include "valkey.fullname" . }}-headless -{{- end -}} - -{{/* -Validate replica persistence configuration -*/}} -{{- define "valkey.validateReplicaPersistence" -}} -{{- if .Values.replica.enabled }} - {{- if not .Values.replica.persistence.size }} - {{- fail "Replica mode requires persistent storage. Please set replica.persistence.size (e.g., '5Gi')" }} - {{- end }} -{{- end }} -{{- end -}} - -{{/* -Validate replica authentication configuration -*/}} -{{- define "valkey.validateReplicaAuth" -}} -{{- if and .Values.replica.enabled .Values.auth.enabled }} - {{- if not (hasKey .Values.auth.aclUsers .Values.replica.replicationUser) }} - {{- fail (printf "Replication user '%s' (replica.replicationUser) must be defined in auth.aclUsers. The chart requires this to retrieve the password for replica authentication." .Values.replica.replicationUser) }} - {{- end }} -{{- end }} -{{- end -}} - diff --git a/charts/valkey/templates/configmap.yaml b/charts/valkey/templates/configmap.yaml deleted file mode 100644 index 9176d12f..00000000 --- a/charts/valkey/templates/configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -{{- if .Values.valkeyConfig }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "valkey.fullname" . }}-config - labels: - {{- include "valkey.labels" . | nindent 4 }} -data: - valkey.conf: | - {{- .Values.valkeyConfig | nindent 4 }} -{{- end }} diff --git a/charts/valkey/templates/deploy_valkey.yaml b/charts/valkey/templates/deploy_valkey.yaml deleted file mode 100644 index da7cd712..00000000 --- a/charts/valkey/templates/deploy_valkey.yaml +++ /dev/null @@ -1,289 +0,0 @@ -{{- if not .Values.replica.enabled }} -{{- $fullname := include "valkey.fullname" . }} -{{- $storage := .Values.dataStorage }} -{{- $createPVC := and $storage.enabled (not (empty $storage.requestedSize)) (empty $storage.persistentVolumeClaimName) }} -{{- include "valkey.validateAuthConfig" . }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} -spec: - replicas: 1 - strategy: - type: {{ .Values.deploymentStrategy }} - selector: - matchLabels: - {{- include "valkey.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "valkey.selectorLabels" . | nindent 8 }} - {{- with .Values.commonLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - checksum/initconfig: {{ include (print $.Template.BasePath "/init_config.yaml") . | sha256sum | trunc 32 }} - {{- if .Values.valkeyConfig }} - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | trunc 32 }} - {{- end }} - spec: - {{- include "valkey.imagePullSecrets" . | nindent 6 }} - automountServiceAccountToken: {{ .Values.serviceAccount.automount }} - serviceAccountName: {{ include "valkey.serviceAccountName" . }} - {{- if .Values.priorityClassName }} - priorityClassName: {{ .Values.priorityClassName | quote }} - {{- end }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - initContainers: - - name: {{ include "valkey.fullname" . }}-init - image: {{ include "valkey.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.securityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - command: [ "/scripts/init.sh" ] - volumeMounts: - - name: {{ .Values.dataStorage.volumeName }} - mountPath: /data - {{- if .Values.dataStorage.subPath }} - subPath: {{ .Values.dataStorage.subPath }} - {{- end }} - - name: scripts - mountPath: /scripts - {{- if .Values.valkeyConfig }} - - name: valkey-config - mountPath: /usr/local/etc/valkey/valkey.conf - subPath: valkey.conf - {{- end }} - {{- if .Values.extraSecretValkeyConfigs }} - - name: extravalkeyconfigs-volume - mountPath: /extravalkeyconfigs - {{- end }} - {{- if .Values.auth.enabled }} - - name: valkey-acl - mountPath: /etc/valkey - {{- if .Values.auth.usersExistingSecret }} - - name: valkey-users-secret - mountPath: /valkey-users-secret - readOnly: true - {{- end }} - {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} - - name: valkey-auth-secret - mountPath: /valkey-auth-secret - readOnly: true - {{- end }} - {{- end }} - {{- with .Values.extraVolumeMounts }} - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.initResources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.extraInitContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: {{ include "valkey.fullname" . }} - image: {{ include "valkey.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: [ "valkey-server" ] - args: [ "/data/conf/valkey.conf" ] - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - env: - {{- range $key, $val := .Values.env }} - - name: {{ $key }} - value: "{{ $val }}" - {{- end }} - - name: VALKEY_LOGLEVEL - value: "{{ .Values.valkeyLogLevel }}" - ports: - - name: tcp - containerPort: {{ .Values.service.port }} - protocol: TCP - startupProbe: - exec: - {{- if .Values.tls.enabled }} - command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] - {{- else }} - command: [ "sh", "-c", "valkey-cli ping" ] - {{- end }} - livenessProbe: - exec: - {{- if .Values.tls.enabled }} - command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] - {{- else }} - command: [ "sh", "-c", "valkey-cli ping" ] - {{- end }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: {{ .Values.dataStorage.volumeName }} - mountPath: /data - {{- if .Values.dataStorage.subPath }} - subPath: {{ .Values.dataStorage.subPath }} - {{- end }} - {{- if .Values.tls.enabled }} - - name: {{ include "valkey.fullname" . }}-tls - mountPath: /tls - {{- end }} - {{- if .Values.auth.enabled }} - - name: valkey-acl - mountPath: /etc/valkey - {{- end }} - {{- range $secret := .Values.extraValkeySecrets }} - - name: {{ $secret.name }}-valkey - mountPath: {{ $secret.mountPath }} - {{- end }} - {{- range $config := .Values.extraValkeyConfigs }} - - name: {{ $config.name }}-valkey - mountPath: {{ $config.mountPath }} - {{- end }} - {{- with .Values.extraVolumeMounts }} - {{- toYaml . | nindent 12 }} - {{- end }} - {{- if .Values.metrics.enabled }} - - name: metrics - image: {{ include "valkey.metrics.exporter.image" . }} - imagePullPolicy: {{ .Values.metrics.exporter.image.pullPolicy | quote }} - {{- with .Values.metrics.exporter.securityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.command }} - command: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.args }} - args: - {{- toYaml . | nindent 12 }} - {{- end }} - ports: - - name: metrics - containerPort: {{ .Values.metrics.exporter.port }} - startupProbe: - tcpSocket: - port: metrics - livenessProbe: - tcpSocket: - port: metrics - readinessProbe: - httpGet: - path: / - port: metrics - {{- with .Values.metrics.exporter.resources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.extraVolumeMounts }} - volumeMounts: - {{- toYaml . | nindent 12 }} - {{- end }} - env: - - name: REDIS_ALIAS - value: {{ include "valkey.fullname" . }} - {{- range $key, $val := .Values.metrics.exporter.extraEnvs }} - - name: {{ $key }} - value: "{{ $val }}" - {{- end }} - {{- end }} - volumes: - - name: scripts - configMap: - name: {{ include "valkey.fullname" . }}-init-scripts - defaultMode: 0555 - {{- if .Values.auth.enabled }} - - name: valkey-acl - emptyDir: - medium: Memory - {{- end }} - {{- if .Values.valkeyConfig }} - - name: valkey-config - configMap: - name: {{ include "valkey.fullname" . }}-config - {{- end }} - {{- range .Values.extraValkeySecrets }} - - name: {{ .name }}-valkey - secret: - secretName: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- if .Values.tls.enabled }} - - name: {{ include "valkey.fullname" . }}-tls - secret: - secretName: {{ required "An existing secret is required to enable TLS" .Values.tls.existingSecret }} - defaultMode: 0400 - {{- end }} - {{- range .Values.extraValkeyConfigs }} - - name: {{ .name }}-valkey - configMap: - name: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- if .Values.metrics.enabled }} - {{- range .Values.metrics.exporter.extraExporterSecrets }} - - name: {{ .name }}-exporter - secret: - secretName: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- end }} - {{- if .Values.auth.enabled }} - {{- if .Values.auth.usersExistingSecret }} - - name: valkey-users-secret - secret: - secretName: {{ .Values.auth.usersExistingSecret }} - defaultMode: 0400 - {{- end }} - {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} - - name: valkey-auth-secret - secret: - secretName: {{ include "valkey.fullname" . }}-auth - defaultMode: 0400 - {{- end }} - {{- end }} - - name: {{ $storage.volumeName }} - {{- if $storage.persistentVolumeClaimName }} - persistentVolumeClaim: - claimName: {{ $storage.persistentVolumeClaimName }} - {{- else if $createPVC }} - persistentVolumeClaim: - claimName: {{ include "valkey.fullname" . }} - {{- else if $storage.hostPath }} - hostPath: - path: {{ $storage.hostPath }} - type: DirectoryOrCreate - {{- else }} - emptyDir: {} - {{- end }} - {{- with .Values.extraVolumes }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.topologySpreadConstraints }} - topologySpreadConstraints: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/charts/valkey/templates/init_config.yaml b/charts/valkey/templates/init_config.yaml deleted file mode 100644 index 9b0337e5..00000000 --- a/charts/valkey/templates/init_config.yaml +++ /dev/null @@ -1,231 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "valkey.fullname" . }}-init-scripts - labels: - {{- include "valkey.labels" . | nindent 4 }} -data: - init.sh: |- - #!/bin/sh - set -eu - - # Default config paths - VALKEY_CONFIG=${VALKEY_CONFIG_PATH:-/data/conf/valkey.conf} - - LOGFILE="/data/init.log" - DATA_DIR="/data/conf" - - # Logging function (outputs to stderr and file) - log() { - echo "$(date) $1" | tee -a "$LOGFILE" >&2 - } - - {{- if .Values.auth.enabled }} - # Function to get password for a user - # Usage: get_user_password [password_key] - # Returns: password via stdout, exits with error if not found - get_user_password() { - username="$1" - password_key="${2:-$username}" - password="" - - {{- if .Values.auth.usersExistingSecret }} - # Try to get password from existing secret first (priority) - if [ -f "/valkey-users-secret/$password_key" ]; then - password=$(cat "/valkey-users-secret/$password_key") - log "Using password from existing secret for user $username" - elif [ -f "/valkey-auth-secret/${username}-password" ]; then - # Fallback to inline password - password=$(cat "/valkey-auth-secret/${username}-password") - log "Using inline password for user $username" - else - log "ERROR: No password found for user $username" - return 1 - fi - {{- else }} - # Use inline password only - if [ -f "/valkey-auth-secret/${username}-password" ]; then - password=$(cat "/valkey-auth-secret/${username}-password") - log "Using inline password for user $username" - else - log "ERROR: No password found for user $username" - return 1 - fi - {{- end }} - - echo "$password" - } - {{- end }} - - # Clean old log if requested - if [ "${KEEP_OLD_LOGS:-false}" != "true" ]; then - rm -f "$LOGFILE" - fi - - if [ -f "$LOGFILE" ]; then - log "Detected restart of this instance ($HOSTNAME)" - fi - - log "Creating configuration in $DATA_DIR..." - mkdir -p "$DATA_DIR" - rm -f "$VALKEY_CONFIG" - - - # Base valkey.conf - log "Generating base valkey.conf" - { -{{- if .Values.tls.enabled }} - echo "tls-cert-file /tls/{{ .Values.tls.serverPublicKey }}" - echo "tls-key-file /tls/{{ .Values.tls.serverKey }}" - echo "tls-ca-cert-file /tls/{{ .Values.tls.caPublicKey }}" - {{- if .Values.tls.dhParamKey }} - echo "tls-dh-params-file /tls/{{ .Values.tls.dhParamKey }}" - {{- end }} - {{- if not .Values.tls.requireClientCertificate }} - echo "tls-auth-clients no" - {{- end }} - echo "port 0" - echo "tls-port 6379" -{{- else }} - echo "port 6379" -{{- end }} - echo "protected-mode no" - echo "bind * -::*" - echo "dir /data" - } >>"$VALKEY_CONFIG" - - {{- if .Values.auth.enabled }} - # Create secure directory for ACL file - log "Creating /etc/valkey directory for ACL file" - mkdir -p /etc/valkey - - # Set aclfile path in valkey.conf - echo "aclfile /etc/valkey/users.acl" >>"$VALKEY_CONFIG" - - # Remove or reset existing ACL file if present (it may be read-only from previous run) - log "Preparing ACL file at /etc/valkey/users.acl" - if [ -f /etc/valkey/users.acl ]; then - log "Removing existing read-only users.acl file" - chmod 0600 /etc/valkey/users.acl - rm -f /etc/valkey/users.acl - fi - - # Create ACL file with secure permissions - touch /etc/valkey/users.acl - chmod 0600 /etc/valkey/users.acl - - {{- if .Values.auth.aclUsers }} - # Generate ACL entries for each user - log "Generating ACL entries for users" - {{- range $username, $user := .Values.auth.aclUsers }} - {{- $passwordKey := $user.passwordKey | default $username }} - - # User: {{ $username }} - PASSWORD=$(get_user_password "{{ $username }}" "{{ $passwordKey }}") || exit 1 - - # Hash the password and write ACL entry - PASSHASH=$(echo -n "$PASSWORD" | sha256sum | cut -f 1 -d " ") - echo "user {{ $username }} on #$PASSHASH {{ $user.permissions }}" >> /etc/valkey/users.acl - - {{- end }} - {{- end }} - - {{- if .Values.auth.aclConfig }} - # Append inline ACL configuration - log "Appending custom ACL configuration" - if [ -f /valkey-auth-secret/aclConfig ]; then - cat /valkey-auth-secret/aclConfig >> /etc/valkey/users.acl - fi - {{- end }} - - # Set final permissions - chmod 0400 /etc/valkey/users.acl - log "ACL file created with 0400 permissions" - {{- end }} - - {{- if .Values.replica.enabled }} - # Replica mode configuration - log "Configuring replication mode" - - # Use POD_INDEX from Kubernetes metadata - POD_INDEX=${POD_INDEX:-0} - IS_MASTER=false - - # Check if this is pod-0 (master) - if [ "$POD_INDEX" = "0" ]; then - IS_MASTER=true - log "This pod (index $POD_INDEX) is configured as MASTER" - else - log "This pod (index $POD_INDEX) is configured as REPLICA" - fi - - # Configure replica settings - if [ "$IS_MASTER" = "false" ]; then - MASTER_HOST="{{ include "valkey.fullname" . }}-0.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" - MASTER_PORT="{{ .Values.service.port }}" - - log "Configuring replica to follow master at $MASTER_HOST:$MASTER_PORT" - - { - echo "" - echo "# Replica Configuration" - echo "replicaof $MASTER_HOST $MASTER_PORT" - echo "replica-announce-ip {{ include "valkey.fullname" . }}-$POD_INDEX.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" - {{- if .Values.replica.disklessSync }} - echo "" - echo "# Diskless replication" - echo "repl-diskless-sync yes" - echo "repl-diskless-sync-delay 5" - {{- end }} - {{- if .Values.auth.enabled }} - echo "" - echo "# Master authentication" - {{- end }} - } >>"$VALKEY_CONFIG" - - {{- if .Values.auth.enabled }} - # Get the password for the replication user - {{- $replUsername := .Values.replica.replicationUser }} - {{- $replUser := index .Values.auth.aclUsers $replUsername }} - {{- $replPasswordKey := $replUser.passwordKey | default $replUsername }} - REPL_PASSWORD=$(get_user_password "{{ $replUsername }}" "{{ $replPasswordKey }}") || exit 1 - - # Write masterauth configuration - echo "masterauth $REPL_PASSWORD" >>"$VALKEY_CONFIG" - echo "masteruser {{ $replUsername }}" >>"$VALKEY_CONFIG" - log "Configured masterauth with user {{ $replUsername }}" - {{- end }} - - {{- if .Values.tls.enabled }} - # TLS for replication - { - echo "" - echo "# TLS for replication" - echo "tls-replication yes" - } >>"$VALKEY_CONFIG" - log "Enabled TLS for replication" - {{- end }} - fi - - {{- if gt (int .Values.replica.minReplicasToWrite) 0 }} - # Write safety - require minimum healthy replicas - { - echo "" - echo "# Minimum replicas for write operations" - echo "min-replicas-to-write {{ .Values.replica.minReplicasToWrite }}" - echo "min-replicas-max-lag {{ .Values.replica.minReplicasMaxLag }}" - } >>"$VALKEY_CONFIG" - log "Configured write safety: require {{ .Values.replica.minReplicasToWrite }} replicas with max {{ .Values.replica.minReplicasMaxLag }}s lag" - {{- end }} - {{- end }} - - # Append extra configs if present - if [ -f /usr/local/etc/valkey/valkey.conf ]; then - log "Appending /usr/local/etc/valkey/valkey.conf" - cat /usr/local/etc/valkey/valkey.conf >>"$VALKEY_CONFIG" - fi - if [ -d /extravalkeyconfigs ]; then - log "Appending files in /extravalkeyconfigs/" - cat /extravalkeyconfigs/* >>"$VALKEY_CONFIG" - fi - diff --git a/charts/valkey/templates/metrics-svc.yaml b/charts/valkey/templates/metrics-svc.yaml deleted file mode 100644 index ffd71d70..00000000 --- a/charts/valkey/templates/metrics-svc.yaml +++ /dev/null @@ -1,29 +0,0 @@ -{{- if and .Values.metrics.enabled .Values.metrics.service.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ printf "%s-metrics" (include "valkey.fullname" .) }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/component: metrics - app.kubernetes.io/part-of: valkey - {{- with .Values.metrics.service.extraLabels }} - {{- toYaml . | nindent 4 }} - {{- end }} - annotations: - {{- with .Values.metrics.service.annotations }} - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.metrics.service.type }} - ports: - - name: metrics - port: {{ .Values.metrics.service.ports.http }} - protocol: TCP - targetPort: metrics - {{- if .Values.metrics.service.appProtocol }} - appProtocol: {{ .Values.metrics.service.appProtocol }} - {{- end }} - selector: - {{- include "valkey.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/valkey/templates/netpolicy.yaml b/charts/valkey/templates/netpolicy.yaml deleted file mode 100644 index f65c504d..00000000 --- a/charts/valkey/templates/netpolicy.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- with .Values.networkPolicy }} -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ include "valkey.fullname" $ }} - {{- with .labels }} - labels: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - podSelector: - matchLabels: - {{- include "valkey.selectorLabels" $ | nindent 6 }} - policyTypes: - {{- if .ingress }} - - Ingress - {{- end }} - {{- if .egress }} - - Egress - {{- end }} - {{- with .ingress }} - ingress: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .egress }} - egress: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/charts/valkey/templates/podmonitor.yaml b/charts/valkey/templates/podmonitor.yaml deleted file mode 100644 index 3a22d6b8..00000000 --- a/charts/valkey/templates/podmonitor.yaml +++ /dev/null @@ -1,53 +0,0 @@ -{{- if and .Values.metrics.enabled .Values.metrics.podMonitor.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: PodMonitor -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/part-of: valkey - app.kubernetes.io/component: podmonitor - {{- with .Values.metrics.podMonitor.extraLabels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.metrics.podMonitor.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - podMetricsEndpoints: - - port: {{ .Values.metrics.podMonitor.port }} - {{- with .Values.metrics.podMonitor.interval }} - interval: {{ . }} - {{- end }} - {{- with .Values.metrics.podMonitor.scrapeTimeout }} - scrapeTimeout: {{ . }} - {{- end }} - {{- with .Values.metrics.podMonitor.honorLabels }} - honorLabels: {{ . }} - {{- end }} - {{- with .Values.metrics.podMonitor.relabelings }} - relabelings: - {{- toYaml . | nindent 6 }} - {{- end }} - {{- with .Values.metrics.podMonitor.metricRelabelings }} - metricRelabelings: - {{- toYaml . | nindent 6 }} - {{- end }} - {{- with .Values.metrics.podMonitor.podTargetLabels }} - podTargetLabels: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.metrics.podMonitor.sampleLimit -}} - sampleLimit: {{ . }} - {{- end }} - {{- with .Values.metrics.podMonitor.targetLimit -}} - targetLimit: {{ . }} - {{- end }} - namespaceSelector: - matchNames: - - {{ .Release.Namespace }} - selector: - matchLabels: - {{- include "valkey.selectorLabels" . | nindent 6 }} -{{- end }} diff --git a/charts/valkey/templates/prometheusrules.yaml b/charts/valkey/templates/prometheusrules.yaml deleted file mode 100644 index ab3ba4a8..00000000 --- a/charts/valkey/templates/prometheusrules.yaml +++ /dev/null @@ -1,22 +0,0 @@ -{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/part-of: valkey - {{- if .Values.metrics.prometheusRule.extraLabels }} - {{- toYaml .Values.metrics.prometheusRule.extraLabels | nindent 4 }} - {{- end }} - {{- with .Values.metrics.prometheusRule.extraAnnotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: -{{- with .Values.metrics.prometheusRule.rules }} - groups: - - name: {{ include "valkey.fullname" $ }} - rules: {{ tpl (toYaml .) $ | nindent 8 }} -{{- end }} -{{- end }} diff --git a/charts/valkey/templates/pvc.yaml b/charts/valkey/templates/pvc.yaml deleted file mode 100644 index aa20859b..00000000 --- a/charts/valkey/templates/pvc.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{{- if and .Values.dataStorage.enabled (not .Values.replica.enabled) (not (empty .Values.dataStorage.requestedSize)) (empty .Values.dataStorage.persistentVolumeClaimName) }} -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - {{- with .Values.dataStorage.labels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- if or .Values.dataStorage.keepPvc .Values.dataStorage.annotations }} - annotations: - {{- if .Values.dataStorage.keepPvc }} - "helm.sh/resource-policy": keep - {{- end }} - {{- with .Values.dataStorage.annotations }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} -spec: - accessModes: - {{- toYaml .Values.dataStorage.accessModes | nindent 4 }} - volumeMode: Filesystem - resources: - requests: - storage: {{ .Values.dataStorage.requestedSize }} - {{- if .Values.dataStorage.className }} - storageClassName: {{ .Values.dataStorage.className }} - {{- end }} -{{- end }} diff --git a/charts/valkey/templates/secret.yaml b/charts/valkey/templates/secret.yaml deleted file mode 100644 index 3e909780..00000000 --- a/charts/valkey/templates/secret.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.auth.enabled }} -{{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "valkey.fullname" . }}-auth - labels: - {{- include "valkey.labels" . | nindent 4 }} -type: Opaque -data: - {{- range $username, $user := .Values.auth.aclUsers }} - {{- if $user.password }} - {{ $username }}-password: {{ $user.password | b64enc }} - {{- end }} - {{- end }} - {{- if .Values.auth.aclConfig }} - aclConfig: {{ .Values.auth.aclConfig | b64enc }} - {{- end }} -{{- end }} -{{- end }} diff --git a/charts/valkey/templates/service-headless.yaml b/charts/valkey/templates/service-headless.yaml deleted file mode 100644 index 733ca683..00000000 --- a/charts/valkey/templates/service-headless.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if .Values.replica.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "valkey.fullname" . }}-headless - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/component: headless -spec: - type: ClusterIP - clusterIP: None - publishNotReadyAddresses: true - ports: - - name: tcp - port: {{ .Values.service.port }} - targetPort: tcp - protocol: TCP - selector: - {{- include "valkey.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/valkey/templates/service-read.yaml b/charts/valkey/templates/service-read.yaml deleted file mode 100644 index 83daf905..00000000 --- a/charts/valkey/templates/service-read.yaml +++ /dev/null @@ -1,34 +0,0 @@ -{{- if and .Values.replica.enabled .Values.replica.service.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "valkey.fullname" . }}-read - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/component: read - {{- with .Values.replica.service.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.replica.service.type }} - {{- if .Values.replica.service.clusterIP }} - clusterIP: {{ .Values.replica.service.clusterIP }} - {{- end }} - {{- if .Values.replica.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.replica.service.loadBalancerClass }} - {{- end }} - ports: - - name: tcp - port: {{ .Values.replica.service.port }} - targetPort: tcp - protocol: TCP - {{- if and (eq .Values.replica.service.type "NodePort") .Values.replica.service.nodePort }} - nodePort: {{ .Values.replica.service.nodePort }} - {{- end }} - {{- if .Values.replica.service.appProtocol }} - appProtocol: {{ .Values.replica.service.appProtocol }} - {{- end }} - selector: - {{- include "valkey.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/charts/valkey/templates/service.yaml b/charts/valkey/templates/service.yaml deleted file mode 100644 index 4ffb3028..00000000 --- a/charts/valkey/templates/service.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/component: primary - {{- with .Values.service.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.service.type }} - {{- if .Values.service.clusterIP }} - clusterIP: {{ .Values.service.clusterIP }} - {{- end }} - {{- if .Values.service.loadBalancerClass }} - loadBalancerClass: {{ .Values.service.loadBalancerClass }} - {{- end }} - ports: - - port: {{ .Values.service.port }} - targetPort: tcp - protocol: TCP - name: tcp - {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }} - nodePort: {{ .Values.service.nodePort }} - {{- end }} - {{- if .Values.service.appProtocol }} - appProtocol: {{ .Values.service.appProtocol }} - {{- end }} - selector: - {{- include "valkey.selectorLabels" . | nindent 4 }} - {{- if .Values.replica.enabled }} - statefulset.kubernetes.io/pod-name: {{ include "valkey.fullname" . }}-0 - {{- end }} diff --git a/charts/valkey/templates/serviceaccount.yaml b/charts/valkey/templates/serviceaccount.yaml deleted file mode 100644 index e0577153..00000000 --- a/charts/valkey/templates/serviceaccount.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "valkey.serviceAccountName" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -automountServiceAccountToken: {{ .Values.serviceAccount.automount }} -{{- end }} diff --git a/charts/valkey/templates/servicemonitor.yaml b/charts/valkey/templates/servicemonitor.yaml deleted file mode 100644 index e46d8e6c..00000000 --- a/charts/valkey/templates/servicemonitor.yaml +++ /dev/null @@ -1,54 +0,0 @@ -{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled .Values.metrics.service.enabled }} -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} - app.kubernetes.io/part-of: valkey - app.kubernetes.io/component: service-monitor - {{- with .Values.metrics.serviceMonitor.extraLabels }} - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - endpoints: - - port: {{ .Values.metrics.serviceMonitor.port }} - {{- with .Values.metrics.serviceMonitor.interval }} - interval: {{ . }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.scrapeTimeout }} - scrapeTimeout: {{ . }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.honorLabels }} - honorLabels: {{ . }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.relabelings }} - relabelings: - {{- toYaml . | nindent 6 }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.metricRelabelings }} - metricRelabelings: - {{- toYaml . | nindent 6 }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.podTargetLabels }} - podTargetLabels: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.sampleLimit }} - sampleLimit: {{ . }} - {{- end }} - {{- with .Values.metrics.serviceMonitor.targetLimit }} - targetLimit: {{ . }} - {{- end }} - namespaceSelector: - matchNames: - - {{ .Release.Namespace }} - selector: - matchLabels: - {{- include "valkey.selectorLabels" . | nindent 6 }} - app.kubernetes.io/component: metrics -{{- end }} diff --git a/charts/valkey/templates/statefulset.yaml b/charts/valkey/templates/statefulset.yaml deleted file mode 100644 index 541f9df0..00000000 --- a/charts/valkey/templates/statefulset.yaml +++ /dev/null @@ -1,279 +0,0 @@ -{{- if .Values.replica.enabled }} -{{- include "valkey.validateAuthConfig" . }} -{{- include "valkey.validateReplicaPersistence" . }} -{{- include "valkey.validateReplicaAuth" . }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "valkey.fullname" . }} - labels: - {{- include "valkey.labels" . | nindent 4 }} -spec: - serviceName: {{ include "valkey.fullname" . }}-headless - replicas: {{ add (int .Values.replica.replicas) 1 }} - podManagementPolicy: OrderedReady - selector: - matchLabels: - {{- include "valkey.selectorLabels" . | nindent 6 }} - volumeClaimTemplates: - - metadata: - name: valkey-data - spec: - accessModes: {{ toYaml .Values.replica.persistence.accessModes | nindent 8 }} - {{- if .Values.replica.persistence.storageClass }} - storageClassName: {{ .Values.replica.persistence.storageClass | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.replica.persistence.size | quote }} - template: - metadata: - labels: - {{- include "valkey.selectorLabels" . | nindent 8 }} - {{- with .Values.commonLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.podLabels }} - {{- toYaml . | nindent 8 }} - {{- end }} - annotations: - {{- with .Values.podAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} - checksum/initconfig: {{ include (print $.Template.BasePath "/init_config.yaml") . | sha256sum | trunc 32 | quote }} - {{- if .Values.valkeyConfig }} - checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum | trunc 32 | quote }} - {{- end }} - spec: - {{- (include "valkey.imagePullSecrets" .) | nindent 6 }} - automountServiceAccountToken: {{ .Values.serviceAccount.automount }} - serviceAccountName: {{ include "valkey.serviceAccountName" . }} - {{- if .Values.priorityClassName }} - priorityClassName: {{ .Values.priorityClassName | quote }} - {{- end }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - initContainers: - - name: {{ include "valkey.fullname" . }}-init - image: {{ include "valkey.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.securityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - command: [ "/scripts/init.sh" ] - env: - - name: POD_INDEX - valueFrom: - fieldRef: - fieldPath: metadata.labels['apps.kubernetes.io/pod-index'] - volumeMounts: - - name: valkey-data - mountPath: /data - - name: scripts - mountPath: /scripts - {{- if .Values.valkeyConfig }} - - name: valkey-config - mountPath: /usr/local/etc/valkey/valkey.conf - subPath: valkey.conf - {{- end }} - {{- if .Values.extraSecretValkeyConfigs }} - - name: extravalkeyconfigs-volume - mountPath: /extravalkeyconfigs - {{- end }} - {{- if .Values.auth.enabled }} - - name: valkey-acl - mountPath: /etc/valkey - {{- if .Values.auth.usersExistingSecret }} - - name: valkey-users-secret - mountPath: /valkey-users-secret - readOnly: true - {{- end }} - {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} - - name: valkey-auth-secret - mountPath: /valkey-auth-secret - readOnly: true - {{- end }} - {{- end }} - {{- with .Values.initResources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.extraInitContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: {{ include "valkey.fullname" . }} - image: {{ include "valkey.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - command: [ "valkey-server" ] - args: [ "/data/conf/valkey.conf" ] - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - env: - - name: POD_INDEX - valueFrom: - fieldRef: - fieldPath: metadata.labels['apps.kubernetes.io/pod-index'] - {{- range $key, $val := .Values.env }} - - name: {{ $key }} - value: "{{ $val }}" - {{- end }} - - name: VALKEY_LOGLEVEL - value: "{{ .Values.valkeyLogLevel }}" - ports: - - name: tcp - containerPort: {{ .Values.service.port }} - protocol: TCP - startupProbe: - exec: - {{- if .Values.tls.enabled }} - command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] - {{- else }} - command: [ "sh", "-c", "valkey-cli ping" ] - {{- end }} - livenessProbe: - exec: - {{- if .Values.tls.enabled }} - command: [ "sh", "-c", "valkey-cli --cacert /tls/{{ .Values.tls.caPublicKey }} --tls ping" ] - {{- else }} - command: [ "sh", "-c", "valkey-cli ping" ] - {{- end }} - resources: - {{- toYaml .Values.resources | nindent 12 }} - volumeMounts: - - name: valkey-data - mountPath: /data - {{- if .Values.tls.enabled }} - - name: {{ include "valkey.fullname" . }}-tls - mountPath: /tls - {{- end }} - {{- if .Values.auth.enabled }} - - name: valkey-acl - mountPath: /etc/valkey - {{- end }} - {{- range $secret := .Values.extraValkeySecrets }} - - name: {{ $secret.name }}-valkey - mountPath: {{ $secret.mountPath }} - {{- end }} - {{- range $config := .Values.extraValkeyConfigs }} - - name: {{ $config.name }}-valkey - mountPath: {{ $config.mountPath }} - {{- end }} - {{- if .Values.metrics.enabled }} - - name: metrics - image: {{ include "valkey.metrics.exporter.image" . }} - imagePullPolicy: {{ .Values.metrics.exporter.image.pullPolicy | quote }} - {{- with .Values.metrics.exporter.securityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.command }} - command: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.args }} - args: - {{- toYaml . | nindent 12 }} - {{- end }} - ports: - - name: metrics - containerPort: {{ .Values.metrics.exporter.port }} - startupProbe: - tcpSocket: - port: metrics - livenessProbe: - tcpSocket: - port: metrics - readinessProbe: - httpGet: - path: / - port: metrics - {{- with .Values.metrics.exporter.resources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with .Values.metrics.exporter.extraVolumeMounts }} - volumeMounts: - {{- toYaml . | nindent 12 }} - {{- end }} - env: - - name: REDIS_ALIAS - value: {{ include "valkey.fullname" . }} - {{- range $key, $val := .Values.metrics.exporter.extraEnvs }} - - name: {{ $key }} - value: "{{ $val }}" - {{- end }} - {{- end }} - volumes: - - name: scripts - configMap: - name: {{ include "valkey.fullname" . }}-init-scripts - defaultMode: 0555 - {{- if .Values.auth.enabled }} - - name: valkey-acl - emptyDir: - medium: Memory - {{- end }} - {{- if .Values.valkeyConfig }} - - name: valkey-config - configMap: - name: {{ include "valkey.fullname" . }}-config - {{- end }} - {{- range .Values.extraValkeySecrets }} - - name: {{ .name }}-valkey - secret: - secretName: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- if .Values.tls.enabled }} - - name: {{ include "valkey.fullname" . }}-tls - secret: - secretName: {{ required "An existing secret is required to enable TLS" .Values.tls.existingSecret }} - defaultMode: 0400 - {{- end }} - {{- range .Values.extraValkeyConfigs }} - - name: {{ .name }}-valkey - configMap: - name: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- if .Values.metrics.enabled }} - {{- range .Values.metrics.exporter.extraExporterSecrets }} - - name: {{ .name }}-exporter - secret: - secretName: {{ .name }} - defaultMode: {{ .defaultMode | default 0440 }} - {{- end }} - {{- end }} - {{- if .Values.auth.enabled }} - {{- if .Values.auth.usersExistingSecret }} - - name: valkey-users-secret - secret: - secretName: {{ .Values.auth.usersExistingSecret }} - defaultMode: 0400 - {{- end }} - {{- if or (include "valkey.hasInlinePasswords" . | eq "true") .Values.auth.aclConfig }} - - name: valkey-auth-secret - secret: - secretName: {{ include "valkey.fullname" . }}-auth - defaultMode: 0400 - {{- end }} - {{- end }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.topologySpreadConstraints }} - topologySpreadConstraints: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} diff --git a/charts/valkey/templates/tests/auth.yaml b/charts/valkey/templates/tests/auth.yaml deleted file mode 100644 index d10477ab..00000000 --- a/charts/valkey/templates/tests/auth.yaml +++ /dev/null @@ -1,144 +0,0 @@ -{{- if .Values.auth.enabled }} -{{- if include "valkey.hasInlinePasswords" . | eq "true" }} -{{- $firstUsername := "" }} -{{- range $username, $user := .Values.auth.aclUsers }} - {{- if $user.password }} - {{- if not $firstUsername }} - {{- $firstUsername = $username }} - {{- end }} - {{- end }} -{{- end }} -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "valkey.fullname" . }}-test-auth-generated - labels: - {{- include "valkey.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded -spec: - restartPolicy: Never - containers: - - name: test-auth - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - command: - - sh - - -c - - | - set -e - echo "Testing authentication with user {{ $firstUsername }} (inline passwords)..." - - # Extract password from secret - PASSWORD=$(cat /valkey-auth/{{ $firstUsername }}-password) - USERNAME="{{ $firstUsername }}" - - {{- if .Values.tls.enabled }} - # TLS flags - TLS_FLAGS="--tls --cacert /tls/{{ .Values.tls.caPublicKey }}" - {{- else }} - TLS_FLAGS="" - {{- end }} - - # Test authentication - PING_RESULT=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" PING) - if [ "$PING_RESULT" != "PONG" ]; then - echo "✗ Authentication test failed: expected 'PONG', got '$PING_RESULT'" - exit 1 - fi - echo "✓ PING successful: $PING_RESULT" - - # Test that we can set and get a value (if permissions allow) - if valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" SET test-key-generated "test-value" 2>/dev/null; then - VALUE=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS --user "$USERNAME" --pass "$PASSWORD" GET test-key-generated) - if [ "$VALUE" = "test-value" ]; then - echo "✓ Authentication test passed with write permissions" - exit 0 - else - echo "✗ Authentication test failed: expected 'test-value', got '$VALUE'" - exit 1 - fi - else - echo "✓ Authentication test passed (read-only or restricted permissions)" - exit 0 - fi - volumeMounts: - - name: valkey-auth - mountPath: /valkey-auth - readOnly: true - {{- if .Values.tls.enabled }} - - name: valkey-tls - mountPath: /tls - readOnly: true - {{- end }} - volumes: - - name: valkey-auth - secret: - secretName: {{ include "valkey.fullname" . }}-auth - {{- if .Values.tls.enabled }} - - name: valkey-tls - secret: - secretName: {{ .Values.tls.existingSecret }} - {{- end }} -{{- end }} -{{- end }} ---- -{{- if and .Values.auth.enabled .Values.auth.usersExistingSecret }} -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "valkey.fullname" . }}-test-auth-existing - labels: - {{- include "valkey.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": test - "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded -spec: - restartPolicy: Never - containers: - - name: test-auth - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - command: - - sh - - -c - - | - set -e - echo "Testing authentication with usersExistingSecret..." - - {{- if .Values.tls.enabled }} - # TLS flags - TLS_FLAGS="--tls --cacert /tls/{{ .Values.tls.caPublicKey }}" - {{- else }} - TLS_FLAGS="" - {{- end }} - - # Test basic connection (no auth - will fail if auth is properly configured) - PING_RESULT=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} $TLS_FLAGS PING 2>&1 || true) - if [ "$PING_RESULT" = "PONG" ]; then - echo "✗ Authentication test failed: server allows unauthenticated access" - exit 1 - fi - - echo "✓ Authentication is enforced (unauthenticated access denied)" - echo "✓ Received expected error: $PING_RESULT" - echo "⚠ Manual verification recommended for usersExistingSecret configuration" - exit 0 - volumeMounts: - - name: valkey-users-secret - mountPath: /valkey-users-secret - readOnly: true - {{- if .Values.tls.enabled }} - - name: valkey-tls - mountPath: /tls - readOnly: true - {{- end }} - volumes: - - name: valkey-users-secret - secret: - secretName: {{ .Values.auth.usersExistingSecret }} - {{- if .Values.tls.enabled }} - - name: valkey-tls - secret: - secretName: {{ .Values.tls.existingSecret }} - {{- end }} -{{- end }} diff --git a/charts/valkey/values.schema.json b/charts/valkey/values.schema.json deleted file mode 100644 index 367c4d39..00000000 --- a/charts/valkey/values.schema.json +++ /dev/null @@ -1,535 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "affinity": { - "type": "object" - }, - "auth": { - "type": "object", - "properties": { - "aclConfig": { - "type": "string" - }, - "aclUsers": { - "type": "object" - }, - "enabled": { - "type": "boolean" - }, - "usersExistingSecret": { - "type": "string" - } - } - }, - "clusterDomain": { - "type": "string" - }, - "commonLabels": { - "type": "object" - }, - "dataStorage": { - "type": "object", - "properties": { - "accessModes": { - "type": "array", - "items": { - "type": "string" - } - }, - "annotations": { - "type": "object" - }, - "className": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "keepPvc": { - "type": "boolean" - }, - "labels": { - "type": "object" - }, - "persistentVolumeClaimName": { - "type": "string" - }, - "requestedSize": { - "type": "string" - }, - "subPath": { - "type": "string" - }, - "volumeName": { - "type": "string" - }, - "hostPath": { - "type": "string" - } - } - }, - "deploymentStrategy": { - "type": "string", - "enum": [ - "RollingUpdate", - "Recreate" - ] - }, - "env": { - "type": "object" - }, - "extraInitContainers": { - "type": "array" - }, - "extraSecretValkeyConfigs": { - "type": "boolean" - }, - "extraVolumes": { - "type": "array" - }, - "extraVolumeMounts": { - "type": "array" - }, - "extraValkeyConfigs": { - "type": "array" - }, - "extraValkeySecrets": { - "type": "array" - }, - "fullnameOverride": { - "type": "string" - }, - "global": { - "type": "object", - "properties": { - "imagePullSecrets": { - "type": "array" - }, - "imageRegistry": { - "type": "string" - } - } - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "registry": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "imagePullSecrets": { - "type": "array" - }, - "initResources": { - "type": "object" - }, - "metrics": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "exporter": { - "type": "object", - "properties": { - "args": { - "type": "array" - }, - "command": { - "type": "array" - }, - "extraEnvs": { - "type": "object" - }, - "extraExporterSecrets": { - "type": "array" - }, - "extraVolumeMounts": { - "type": "array" - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "registry": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "port": { - "type": "integer" - }, - "resources": { - "type": "object" - }, - "securityContext": { - "type": "object" - } - } - }, - "podMonitor": { - "type": "object", - "properties": { - "additionalLabels": { - "type": "object" - }, - "annotations": { - "type": "object" - }, - "enabled": { - "type": "boolean" - }, - "extraLabels": { - "type": "object" - }, - "honorLabels": { - "type": "boolean" - }, - "interval": { - "type": "string" - }, - "metricRelabelings": { - "type": "array" - }, - "podTargetLabels": { - "type": "array" - }, - "port": { - "type": "string" - }, - "relabelings": { - "type": "array" - }, - "sampleLimit": { - "type": "boolean" - }, - "scrapeTimeout": { - "type": "string" - }, - "targetLimit": { - "type": "boolean" - } - } - }, - "prometheusRule": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "extraAnnotations": { - "type": "object" - }, - "extraLabels": { - "type": "object" - }, - "rules": { - "type": "array" - } - } - }, - "service": { - "type": "object", - "properties": { - "annotations": { - "type": "object" - }, - "appProtocol": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "extraLabels": { - "type": "object" - }, - "ports": { - "type": "object", - "properties": { - "http": { - "type": "integer" - } - } - }, - "type": { - "type": "string" - } - } - }, - "serviceMonitor": { - "type": "object", - "properties": { - "additionalLabels": { - "type": "object" - }, - "annotations": { - "type": "object" - }, - "enabled": { - "type": "boolean" - }, - "extraLabels": { - "type": "object" - }, - "honorLabels": { - "type": "boolean" - }, - "interval": { - "type": "string" - }, - "metricRelabelings": { - "type": "array" - }, - "podTargetLabels": { - "type": "array" - }, - "port": { - "type": "string" - }, - "relabelings": { - "type": "array" - }, - "sampleLimit": { - "type": "boolean" - }, - "scrapeTimeout": { - "type": "string" - }, - "targetLimit": { - "type": "boolean" - } - } - } - } - }, - "nameOverride": { - "type": "string" - }, - "networkPolicy": { - "type": "object" - }, - "nodeSelector": { - "type": "object" - }, - "podAnnotations": { - "type": "object" - }, - "podLabels": { - "type": "object" - }, - "podSecurityContext": { - "type": "object", - "properties": { - "fsGroup": { - "type": "integer" - }, - "runAsGroup": { - "type": "integer" - }, - "runAsUser": { - "type": "integer" - } - } - }, - "priorityClassName": { - "type": "string" - }, - "replica": { - "type": "object", - "properties": { - "disklessSync": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "minReplicasMaxLag": { - "type": "integer" - }, - "minReplicasToWrite": { - "type": "integer" - }, - "persistence": { - "type": "object", - "properties": { - "accessModes": { - "type": "array", - "items": { - "type": "string" - } - }, - "size": { - "type": "string" - }, - "storageClass": { - "type": "string" - } - } - }, - "replicas": { - "type": "integer" - }, - "replicationUser": { - "type": "string" - }, - "service": { - "type": "object", - "properties": { - "annotations": { - "type": "object" - }, - "appProtocol": { - "type": "string" - }, - "clusterIP": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "loadBalancerClass": { - "type": "string" - }, - "nodePort": { - "type": "integer" - }, - "port": { - "type": "integer" - }, - "type": { - "type": "string" - } - } - } - } - }, - "resources": { - "type": "object" - }, - "securityContext": { - "type": "object", - "properties": { - "capabilities": { - "type": "object", - "properties": { - "drop": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "readOnlyRootFilesystem": { - "type": "boolean" - }, - "runAsNonRoot": { - "type": "boolean" - }, - "runAsUser": { - "type": "integer" - } - } - }, - "service": { - "type": "object", - "properties": { - "annotations": { - "type": "object" - }, - "appProtocol": { - "type": "string" - }, - "clusterIP": { - "type": "string" - }, - "loadBalancerClass": { - "type": "string" - }, - "nodePort": { - "type": "integer" - }, - "port": { - "type": "integer" - }, - "type": { - "type": "string" - } - } - }, - "serviceAccount": { - "type": "object", - "properties": { - "annotations": { - "type": "object" - }, - "automount": { - "type": "boolean" - }, - "create": { - "type": "boolean" - }, - "name": { - "type": "string" - } - } - }, - "tls": { - "type": "object", - "properties": { - "caPublicKey": { - "type": "string" - }, - "dhParamKey": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "existingSecret": { - "type": "string" - }, - "requireClientCertificate": { - "type": "boolean" - }, - "serverKey": { - "type": "string" - }, - "serverPublicKey": { - "type": "string" - } - } - }, - "tolerations": { - "type": "array" - }, - "topologySpreadConstraints": { - "type": "array" - }, - "valkeyConfig": { - "type": "string" - }, - "valkeyLogLevel": { - "type": "string" - } - } -} diff --git a/charts/valkey/values.yaml b/charts/valkey/values.yaml deleted file mode 100644 index 8580e2ea..00000000 --- a/charts/valkey/values.yaml +++ /dev/null @@ -1,446 +0,0 @@ -global: - # The global image registry (this will override the registry of all container images defined in this chart) - imageRegistry: "" - # The global image pull secrets (list of secret names) - imagePullSecrets: [] - -image: - # Image registry - registry: "docker.io" - # Valkey container image repository - repository: valkey/valkey - # Image pull policy (Always, IfNotPresent, Never) - pullPolicy: IfNotPresent - # Image tag (leave empty to use .Chart.AppVersion) - tag: "" - -# List of image pull secrets (for private registries) -imagePullSecrets: [] - -# Override the default name or full name of resources -nameOverride: "" -fullnameOverride: "" - -# Kubernetes cluster domain name -clusterDomain: cluster.local - -serviceAccount: - # Create a service account for Valkey - create: true - # Whether to automount the service account token - automount: false - # Annotations to add to the service account - annotations: {} - # Name of an existing service account to use (if create: false) - name: "" - -# Annotations and labels for the pods -podAnnotations: {} -podLabels: {} - -# Common labels to add to all resources (Deployment, Service, ConfigMap, etc.) -commonLabels: {} - -# Security context for the pod (applies to all containers) -podSecurityContext: - fsGroup: 1000 - runAsUser: 1000 - runAsGroup: 1000 - -# Priority class name for pod scheduling (leave empty to use cluster's default) -priorityClassName: "" - -# Security context for the Valkey containers -securityContext: - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 1000 - -service: - # Type of Kubernetes service (ClusterIP, NodePort, LoadBalancer) - type: ClusterIP - # Port on which Valkey will be exposed - port: 6379 - annotations: {} - # NodePort value (if service.type is NodePort) - nodePort: 0 - # ClusterIP value - clusterIP: "" - # Class of a load balancer implementation - loadBalancerClass: "" - # Application protocol - appProtocol: "" - -# Network policy to control traffic to the pods -# More info: https://kubernetes.io/docs/concepts/services-networking/network-policies/ -networkPolicy: {} - -# Resource limits/requests for the main Valkey container -resources: {} - # Example: - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -# Resource limits/requests for init containers -initResources: {} - # Example: - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - -# Additional init containers -extraInitContainers: [] - -# Persistent storage configuration (standalone deployment only) -dataStorage: - # Enable persistent volume claim creation - enabled: false - - # Use existing PVC by name (skip dynamic provisioning if set) - persistentVolumeClaimName: "" - - # Subpath inside PVC to mount - subPath: "" - - # Name of the volume (referenced in deployment) - volumeName: "valkey-data" - - # Request size (e.g. 5Gi) for dynamically provisioned volume - requestedSize: "" - - # Name of the storage class to use - className: "" - - # Access modes for the PVC (e.g., ReadWriteOnce, ReadWriteMany) - accessModes: - - ReadWriteOnce - - # If true, keep the PVC on Helm uninstall - keepPvc: false - - # Optional annotations to add to the PVC - annotations: {} - - # Optional labels to add to the PVC - labels: {} - - # If hostPath is set then a hostPath DirectoryOrCreate volume is used - hostPath: "" - -# Mount additional secrets into the Valkey container -extraValkeySecrets: [] - -# Mount additional configMaps into the Valkey container -extraValkeyConfigs: [] - -# Mount extra secrets as volume to init container (deprecated, use extraValkeySecrets) -extraSecretValkeyConfigs: false - -# Mount additional emptyDir or hostPath volumes (advanced use) -extraVolumes: [] -# - name: hostpath-volume -# hostPath: -# path: /opt/valkey/hostpath-volume/ -# type: DirectoryOrCreate -extraVolumeMounts: [] -# - mountPath: /some/path/ -# name: hostpath-volume - -# Content for valkey.conf (will be mounted via ConfigMap) -valkeyConfig: "" - -auth: - # Enable ACL-based authentication - # IMPORTANT: When authentication is enabled, the 'default' user MUST be defined in either - # aclUsers or aclConfig. Without a default user, anyone can access the database without - # credentials, creating a security risk. - enabled: false - - # Use an existing secret for user passwords. Key defaults to username. - usersExistingSecret: "" - - # Map of users to create with ACL permissions. - # If usersExistingSecret is set, passwords from the secret take priority over inline passwords. - # NOTE: If using aclUsers, the 'default' user must be included here. - aclUsers: {} - # Example: - # default: - # permissions: "~* &* +@all" - # password: "secretpass" # Inline password (fallback if usersExistingSecret not set) - # passwordKey: "admin-pwd" # Key in usersExistingSecret (defaults to username) - # read-user: - # permissions: "~* -@all +@read +ping +info" - - # Inline ACL configuration that will be appended after generated users. - # NOTE: If using aclConfig, ensure the 'default' user is defined here. - aclConfig: "" - # Example: - # aclConfig: | - # user default on >secretpass ~* &* +@all - -# Replica configuration for master-replica replication mode -replica: - enabled: false - - # Number of replica instances (total pods = replicas + 1 master) - replicas: 2 - - # Username for replicas to authenticate to master, ignored if auth.enabled is false. - # IMPORTANT: When auth.enabled is true, this user MUST be defined in auth.aclUsers. - # The chart requires this to retrieve the password for replica authentication. - # The user must have appropriate replication permissions: +psync +replconf +ping - replicationUser: "default" - - # Replication settings - # Use diskless replication (sync directly from memory) vs disk-based - disklessSync: false - - # Write safety - require minimum number of healthy replicas to accept writes - # Set to 0 to disable this check, or 1+ to require minimum replicas before accepting writes - # This ensures data durability by requiring at least N replicas to be in sync - minReplicasToWrite: 0 - - # Maximum replication lag in seconds before a replica is considered unhealthy - minReplicasMaxLag: 10 - - # Read service configuration - service: - # Enable read service (load balances read traffic across all pods) - enabled: true - # Service type (ClusterIP, NodePort, LoadBalancer) - type: ClusterIP - # Port on which the read service will be exposed - port: 6379 - # Optional annotations for the read service - annotations: {} - # NodePort value (if service.type is NodePort) - nodePort: 0 - # ClusterIP value - clusterIP: "" - # Application protocol - appProtocol: "" - # Class of a load balancer implementation - loadBalancerClass: "" - - # Persistence configuration (required for replicas) - persistence: - # Size of the PVC for each replica (required when replica.enabled is true) - size: "" - # Storage class name (empty = use default storage class) - storageClass: "" - # Access modes for the PVC - accessModes: - - ReadWriteOnce - -tls: - # Enable TLS - enabled: false - # Name of the Secret containing TLS keys (required) - existingSecret: "" - # Secret key name containing server public certificate - serverPublicKey: server.crt - # Secret key name containing server private key - serverKey: server.key - # Secret key name containing Certificate Authority public certificate - caPublicKey: ca.crt - # Secret key name containing DH parameters (optional) - dhParamKey: "" - # Require that clients authenticate with a certificate - requireClientCertificate: false - -# Node selector for pod assignment -nodeSelector: {} - -# Tolerations for pod assignment to tainted nodes -tolerations: [] - -# Affinity rules for pod scheduling -affinity: {} - -# Set Deployment strategy. See https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy -deploymentStrategy: RollingUpdate # @schema enum:[RollingUpdate,Recreate] - -# See https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints -topologySpreadConstraints: [] - -# Valkey logging level: debug, verbose, notice, warning -valkeyLogLevel: "notice" -# Environment variables to inject into Valkey container - -env: {} - # Example: - # LOG_LEVEL: info - -metrics: - # Enable Prometheus exporter sidecar - enabled: false - # Exporter configuration - exporter: - # Command to run in the metrics exporter container (overrides args) - command: [] - # Arguments to pass to the metrics exporter container - args: [] - # Example: - # - --redis.addr=redis:6379 - # Port on which the metrics exporter will listen - port: 9121 - # Image configuration - image: - # Image registry - registry: ghcr.io - # Prometheus exporter container image repository - repository: oliver006/redis_exporter - # Image pull policy (Always, IfNotPresent, Never) - pullPolicy: IfNotPresent - # Image tag (leave empty to use latest) - tag: "v1.79.0" - resources: {} - # Example: - # limits: - # cpu: 100m - # memory: 128Mi - # requests: - # cpu: 100m - # memory: 128Mi - # Extra volume mounts for metrics exporter container - extraVolumeMounts: [] - # Additional secrets to mount for metrics exporter - extraExporterSecrets: [] - # Environment variables to inject into the metrics exporter container - extraEnvs: {} - # Example: - # LOG_LEVEL: info - securityContext: {} - # Example: - # runAsNonRoot: true - # runAsUser: 1000 - # runAsGroup: 1000 - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - - # Service configuration for the metrics exporter - service: - # Enable a separate service for the metrics exporter - enabled: true - # Service type (ClusterIP, NodePort, LoadBalancer) - type: ClusterIP - # Port on which the metrics exporter service will be exposed - ports: - http: 9121 - # Optional annotations for the metrics exporter service - annotations: {} - # Optional labels for the metrics exporter service - extraLabels: {} - # ServiceMonitor configuration for Prometheus Operator - # Application protocol - appProtocol: "" - serviceMonitor: - # Enable ServiceMonitor resource for scraping service metrics - enabled: false - # Port name or number to scrape metrics from - port: metrics - # Extra labels for the ServiceMonitor resource - extraLabels: {} - # Extra annotations for the ServiceMonitor resource - annotations: {} - # How often Prometheus should scrape metrics - interval: 30s - # Maximum duration allowed for a scrape request - scrapeTimeout: "" - # Relabeling rules applied before scraping metrics - relabelings: [] - # Relabeling rules applied before ingesting metrics - metricRelabelings: [] - # Set honorLabels to true to preserve original metric labels - honorLabels: false - # Extra labels to help Prometheus discover ServiceMonitor resources - additionalLabels: {} - # Pod labels to copy onto the generated metrics - podTargetLabels: [] - # Maximum number of samples to collect per Pod scrape - sampleLimit: false - # Maximum number of scrape targets allowed - targetLimit: false - podMonitor: - # Enable PodMonitor resource for scraping pod metrics - enabled: false - # Port name or number to scrape metrics from - port: metrics - # Extra labels for the ServiceMonitor resource - extraLabels: {} - # Extra annotations for the ServiceMonitor resource - annotations: {} - # Frequency for Prometheus to scrape pod metrics - interval: 30s - # Time limit for each scrape operation - scrapeTimeout: "" - # Relabeling rules to apply before scraping pod metrics - relabelings: [] - # Relabeling rules to apply before ingesting pod metrics - metricRelabelings: [] - # If true, keeps original labels from the pod metrics - honorLabels: false - # Additional labels for Prometheus to find PodMonitor resources - additionalLabels: {} - # Pod labels to attach to the metrics - podTargetLabels: [] - # Maximum samples to scrape from each Pod - sampleLimit: false - # Maximum number of pods to scrape - targetLimit: false - - # PrometheusRule configuration for alerting rules (used by kube-prometheus-stack) - prometheusRule: - # Enable creation of PrometheusRule resource - enabled: false - # Extra labels to add to the PrometheusRule resource - extraLabels: {} - # Extra annotations to add to the PrometheusRule resource - extraAnnotations: {} - # List of Prometheus alerting rules - rules: [] - # Example alerting rules: - # - alert: ValkeyDown - # annotations: - # summary: Valkey instance {{ "{{ $labels.instance }}" }} down - # description: Valkey instance {{ "{{ $labels.instance }}" }} is down. - # expr: | - # redis_up{service="{{ include "valkey.fullname" . }}-metrics"} == 0 - # for: 2m - # labels: - # severity: error - # - alert: ValkeyMemoryHigh - # annotations: - # summary: Valkey instance {{ "{{ $labels.instance }}" }} is using too much memory - # description: | - # Valkey instance {{ "{{ $labels.instance }}" }} is using {{ "{{ $value }}" }}% of its available memory. - # expr: | - # redis_memory_used_bytes{service="{{ include "valkey.fullname" . }}-metrics"} * 100 - # / - # redis_memory_max_bytes{service="{{ include "valkey.fullname" . }}-metrics"} - # > 90 <= 100 - # for: 2m - # labels: - # severity: error - # - alert: ValkeyKeyEviction - # annotations: - # summary: Valkey instance {{ "{{ $labels.instance }}" }} has evicted keys - # description: | - # Valkey instance {{ "{{ $labels.instance }}" }} has evicted {{ "{{ $value }}" }} keys in the last 5 minutes. - # expr: | - # increase(redis_evicted_keys_total{service="{{ include "valkey.fullname" . }}-metrics"}[5m]) > 0 - # for: 1s - # labels: - # severity: error From 2c9482937330b6f4e20b472ea00de6cac62c2507 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 18:43:57 -0400 Subject: [PATCH 07/13] switch to mirror for air gapped helm deps --- Chart.yaml | 4 ++-- charts/external-dns-4.0.0.tgz | Bin 29491 -> 0 bytes charts/valkey-0.9.3.tgz | Bin 19409 -> 0 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 charts/external-dns-4.0.0.tgz delete mode 100644 charts/valkey-0.9.3.tgz diff --git a/Chart.yaml b/Chart.yaml index 0f7debb8..c3734c61 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -30,9 +30,9 @@ details: dependencies: - name: external-dns version: 4.0.0 - repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami + repository: @bitnami-mirror condition: external-dns.enabled - name: valkey version: 0.9.3 - repository: https://valkey.io/valkey-helm/ + repository: @valkey-mirror condition: valkey.enabled diff --git a/charts/external-dns-4.0.0.tgz b/charts/external-dns-4.0.0.tgz deleted file mode 100644 index ed3987920ef398be8a1a4ab1bf0ef1aebe256de2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29491 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 From 47e2d2953d604bb22d5a68b04cfa290b8a7a24fa Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 18:45:08 -0400 Subject: [PATCH 08/13] bump version --- Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Chart.yaml b/Chart.yaml index c3734c61..841d81db 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.1 +version: 1.1.2 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg From 363412d6e9c21946981fc734dc8c2efcc6cdb6f5 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 19:47:46 -0400 Subject: [PATCH 09/13] alias --- Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index 841d81db..545eb246 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -30,9 +30,9 @@ details: dependencies: - name: external-dns version: 4.0.0 - repository: @bitnami-mirror + repository: alias:bitnami-mirror condition: external-dns.enabled - name: valkey version: 0.9.3 - repository: @valkey-mirror + repository: alias:valkey-mirror condition: valkey.enabled From 9d571f4d8b35d00d81fa088fb60171a03f89d7aa Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 19:49:26 -0400 Subject: [PATCH 10/13] version --- Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Chart.yaml b/Chart.yaml index 545eb246..8a41dc13 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.2 +version: 1.1.3 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg From 9b39c61eb3245c47f78be730fcef5392b5546acd Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 19:56:20 -0400 Subject: [PATCH 11/13] add back deps. didnot work --- Chart.yaml | 2 +- charts/external-dns-4.0.0.tgz | Bin 0 -> 29491 bytes charts/valkey-0.9.3.tgz | Bin 0 -> 19409 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 charts/external-dns-4.0.0.tgz create mode 100755 charts/valkey-0.9.3.tgz diff --git a/Chart.yaml b/Chart.yaml index 8a41dc13..f7908a61 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.3 +version: 1.1.4 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg diff --git a/charts/external-dns-4.0.0.tgz b/charts/external-dns-4.0.0.tgz new file mode 100755 index 0000000000000000000000000000000000000000..ed3987920ef398be8a1a4ab1bf0ef1aebe256de2 GIT binary patch literal 29491 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 literal 0 HcmV?d00001 From c0c4185eb24e1088c6ca8361ebdd9894fb9db234 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 19:59:10 -0400 Subject: [PATCH 12/13] remove gz --- Chart.yaml | 2 +- charts/external-dns-4.0.0.tgz | Bin 29491 -> 0 bytes charts/valkey-0.9.3.tgz | Bin 19409 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100755 charts/external-dns-4.0.0.tgz delete mode 100755 charts/valkey-0.9.3.tgz diff --git a/Chart.yaml b/Chart.yaml index f7908a61..2b910c23 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.4 +version: 1.1.5 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg diff --git a/charts/external-dns-4.0.0.tgz b/charts/external-dns-4.0.0.tgz deleted file mode 100755 index ed3987920ef398be8a1a4ab1bf0ef1aebe256de2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29491 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 From 86a03edfc266ce6b979e4bd8c706594ece2b79c9 Mon Sep 17 00:00:00 2001 From: guzzijones12 Date: Wed, 25 Mar 2026 20:12:44 -0400 Subject: [PATCH 13/13] add back deps --- Chart.yaml | 2 +- charts/external-dns-4.0.0.tgz | Bin 0 -> 29491 bytes charts/valkey-0.9.3.tgz | Bin 0 -> 19409 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 charts/external-dns-4.0.0.tgz create mode 100755 charts/valkey-0.9.3.tgz diff --git a/Chart.yaml b/Chart.yaml index 2b910c23..518aa984 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 # StackStorm version which refers to Docker images tag appVersion: "3.8" name: stackstorm-ha -version: 1.1.5 +version: 1.1.6 description: StackStorm K8s Helm Chart, optimized for running StackStorm in HA environment. home: https://stackstorm.com/ icon: https://landscape.cncf.io/logos/stack-storm.svg diff --git a/charts/external-dns-4.0.0.tgz b/charts/external-dns-4.0.0.tgz new file mode 100755 index 0000000000000000000000000000000000000000..ed3987920ef398be8a1a4ab1bf0ef1aebe256de2 GIT binary patch literal 29491 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 literal 0 HcmV?d00001