{{- $name := .Values.fullnameOverride | default "redis" -}} apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ $name }} namespace: {{ .Release.Namespace }} labels: app: {{ $name }} spec: serviceName: {{ $name }} replicas: 1 selector: matchLabels: app: {{ $name }} template: metadata: labels: app: {{ $name }} spec: securityContext: # The official image runs as the redis user (uid 999 on the Alpine # variant). fsGroup makes the provisioned volume group-writable so # Redis can write the ACL file it is given — without it ACL SAVE # fails at provisioning time with a permission error. fsGroup: 999 terminationGracePeriodSeconds: 30 initContainers: # Seeds the ACL file with the default (admin) user on first boot # only. Redis will not start with an --aclfile that does not exist, # and the default user has to be defined there rather than by # requirepass — see the long note in values.yaml for why that # distinction is a security property and not a preference. # # Never overwrites an existing file. That file is rewritten by ACL # SAVE every time toolshed provisions an app user, so recreating it # on every pod start would silently delete every provisioned user # and lock those apps out — the exact failure this whole design # exists to prevent, reintroduced from the other end. # # Consequence worth knowing: rotating the admin password in Vault # does NOT propagate here, because this only ever runs against a # missing file. Rotating means `ACL SETUSER default >newpassword` # followed by `ACL SAVE` against the running server. - name: seed-acl image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: {{ .Values.existingSecret }} key: {{ .Values.secretKeys.password }} command: - sh - -c - | set -e if [ -f /data/users.acl ]; then echo "ACL file already present; leaving it alone." exit 0 fi echo "user default on >$REDIS_PASSWORD ~* &* +@all" > /data/users.acl chmod 600 /data/users.acl echo "Seeded ACL file with the default user." volumeMounts: - name: data mountPath: /data containers: - name: redis image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - redis-server - --aclfile - /data/users.acl - --maxmemory - {{ .Values.config.maxmemory | quote }} - --maxmemory-policy - {{ .Values.config.maxmemoryPolicy | quote }} - --save - {{ .Values.config.save | quote }} ports: - name: redis containerPort: 6379 protocol: TCP # Authenticated probes: with the ACL file in place an # unauthenticated PING is correctly refused with NOAUTH, so a # bare `redis-cli ping` would mark a perfectly healthy server as # failing. Run through a shell so the environment expands — # Kubernetes does not substitute $(VAR) inside exec probe # commands. env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: {{ .Values.existingSecret }} key: {{ .Values.secretKeys.password }} readinessProbe: exec: command: ["sh", "-c", 'redis-cli --no-auth-warning -a "$REDIS_PASSWORD" ping | grep -q PONG'] initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: exec: command: ["sh", "-c", 'redis-cli --no-auth-warning -a "$REDIS_PASSWORD" ping | grep -q PONG'] initialDelaySeconds: 20 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 6 resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: - name: data mountPath: /data {{- if .Values.persistence.enabled }} volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: {{ .Values.persistence.storageClass | quote }} resources: requests: storage: {{ .Values.persistence.size | quote }} {{- end }}