apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs spec: replicas: {{ include "valkey.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.valkey.replicas) }} serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service selector: matchLabels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs template: metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs annotations: # Hash of the rendered ConfigMap: a changed configuration changes the pod # template and thus restarts the pod. Without it a changed maxmemory or # eviction policy would apply to nothing - Valkey reads its # configuration once, at start, and a mounted file is not re-read. checksum/config: {{ include (print $.Template.BasePath "/valkey.configmap.yaml") . | sha256sum }} # Hash of the rendered OnePasswordItem. This covers the SOURCE of the # password, not its content: repointing secrets.authSecretRef at a # different vault item rolls the pod instead of leaving the old password # in the running server. A change INSIDE the item cannot be covered - # Helm never sees a secret the 1Password operator materializes (see # values.yaml). # # operator.1password.io/auto-restart is deliberately NOT set: the # operator only restarts Deployments, so on a StatefulSet it is a silent # no-op that advertises a guarantee which does not exist. checksum/secret-ref: {{ include (print $.Template.BasePath "/valkey.secret.yaml") . | sha256sum }} spec: automountServiceAccountToken: false securityContext: {{- include "valkey.podSecurityContext" .Values.valkey.securityContext | nindent 8 }} initContainers: # Validates the mounted auth file and nothing else. It does not compose # the configuration line - that comes finished out of the vault - it only # refuses to let the pod start when the line is missing, which is the one # misconfiguration of this chart that would otherwise produce a healthy, # password-less, network-reachable Valkey. The script, and the reason it # never prints the file, are in templates/valkey.configmap.yaml. # # Same image as the server, so there is no second image to pin, no # Renovate block to maintain for it and nothing extra to pull. - name: {{ .Release.Name }}-{{ .Chart.Name }}-check-auth image: "docker.io/valkey/valkey:{{ .Chart.AppVersion }}" imagePullPolicy: IfNotPresent # command, not args: args would be handed to the image's entrypoint, # which starts valkey-server for anything that looks like a config # file or an option. command: - /bin/sh - /etc/valkey/check-auth.sh securityContext: {{- include "valkey.containerSecurityContext" .Values.valkey.securityContext | nindent 12 }} # Fixed and deliberately small: this container runs one grep. Not # values-configurable - there is nothing here to tune. resources: requests: cpu: 10m memory: 16Mi ephemeral-storage: 1Mi limits: cpu: 100m memory: 32Mi ephemeral-storage: 10Mi volumeMounts: - name: config mountPath: /etc/valkey readOnly: true - name: auth mountPath: /etc/valkey-auth readOnly: true containers: - name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs image: "docker.io/valkey/valkey:{{ .Chart.AppVersion }}" imagePullPolicy: IfNotPresent # args, NOT command: the image's entrypoint is # "tini -- docker-entrypoint.sh", and replacing it would lose tini as # PID 1 (zombie reaping) and the entrypoint's umask. An argument # ending in .conf makes the entrypoint start # "valkey-server /etc/valkey/valkey.conf", which is exactly what is # wanted. Note which branch of that entrypoint is NOT taken: running as # uid 999 skips its root-only "chown /data + setpriv" step, so the # volume is made writable by fsGroup instead. args: - /etc/valkey/valkey.conf # The chart sets no environment variable of its own - everything the # server is told comes from its configuration file, which is what keeps # the password out of the environment. So the block is rendered only # when the operator actually adds an entry, instead of leaving an empty # "env:" (i.e. env: null) in the manifest. {{- with .Values.valkey.env }} env: {{- include "valkey.env" (dict "root" $ "env" .) | nindent 12 }} {{- end }} ports: - containerPort: 6379 protocol: TCP name: valkey # Probes, resources and both security contexts go through the # null-safe helpers (#99): an erased block means "chart defaults # apply" instead of a vanished probe or an unhardened container. # Never wrap these in an "if" - a guard hides the erased case # completely instead of rendering a visible null. livenessProbe: {{- include "valkey.livenessProbe" .Values.valkey.livenessProbe | nindent 12 }} readinessProbe: {{- include "valkey.readinessProbe" .Values.valkey.readinessProbe | nindent 12 }} startupProbe: {{- include "valkey.startupProbe" .Values.valkey.startupProbe | nindent 12 }} resources: {{- include "valkey.effectiveResources" .Values.valkey.resources | nindent 12 }} securityContext: {{- include "valkey.containerSecurityContext" .Values.valkey.securityContext | nindent 12 }} volumeMounts: # The image's WORKDIR. Everything Valkey writes goes here (the AOF # directory, RDB snapshots), and it is the only writable path the # server needs - which is what lets readOnlyRootFilesystem stay true # without a single extra emptyDir. - name: data mountPath: /data - name: config mountPath: /etc/valkey readOnly: true # The auth file, mounted as a DIRECTORY rather than with subPath, so # the kubelet keeps it up to date when the 1Password operator writes # a new password into the Secret. That does not restart the server by # itself (see the annotations above). - name: auth mountPath: /etc/valkey-auth readOnly: true restartPolicy: Always volumes: - name: data {{- if .Values.persistence.enabled }} persistentVolumeClaim: claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc {{- else }} # persistence.enabled is false: the dataset lives in the pod and is # gone with it. The configuration switches both AOF and the RDB save # points off in that case, so this emptyDir stays empty - it exists # because the image's WORKDIR has to be writable. emptyDir: {} {{- end }} - name: config configMap: name: {{ .Release.Name }}-{{ .Chart.Name }}-configmap - name: auth secret: secretName: {{ .Release.Name }}-{{ .Chart.Name }}-secret items: # The 1Password item's field name on the left, the file name on the # right. Spelled out rather than mounting the whole Secret, so a # missing field fails the pod with a named reference instead of # producing a directory without the file. - key: auth-conf path: auth.conf