{{- $root := . -}} {{- $fullname := include "redis-cluster.fullname" . -}} {{- if gt (len $fullname) 53 -}} {{- fail (printf "release fullname %q is too long; use fullnameOverride with at most 53 characters" $fullname) -}} {{- end -}} {{- if ge (int .Values.cluster.replicasPerShard) (int .Values.cluster.shards) -}} {{- fail "cluster.replicasPerShard must be lower than cluster.shards so crossed placement never puts a shard replica with its own primary" -}} {{- end -}} {{- $headless := include "redis-cluster.headlessFqdn" . -}} {{- $nodeCount := include "redis-cluster.nodeCount" . -}} {{- $allHosts := list -}} {{- $primaryHosts := list -}} {{- $assignments := list -}} {{- $addressAssignments := list -}} {{- range $shard := until (int .Values.cluster.shards) -}} {{- range $member := until (int (add (int $.Values.cluster.replicasPerShard) 1)) -}} {{- $memberName := printf "%s-s%d-n%d" $fullname $shard $member | trunc 63 | trimSuffix "-" -}} {{- $podName := printf "%s-0" $memberName -}} {{- $host := printf "%s-0.%s" $memberName $headless -}} {{- $allHosts = append $allHosts $host -}} {{- $addressAssignments = append $addressAssignments (printf "%s=%s" $podName $host) -}} {{- if eq $member 0 -}} {{- $primaryHosts = append $primaryHosts $host -}} {{- else -}} {{- $primaryName := printf "%s-s%d-n0" $fullname $shard | trunc 63 | trimSuffix "-" -}} {{- $primaryHost := printf "%s-0.%s" $primaryName $headless -}} {{- $assignments = append $assignments (printf "%s=%s" $host $primaryHost) -}} {{- end -}} {{- end -}} {{- end -}} {{- $coordinator := first $primaryHosts -}} {{- range $shard := until (int .Values.cluster.shards) }} {{- range $member := until (int (add (int $.Values.cluster.replicasPerShard) 1)) }} {{- $memberName := printf "%s-s%d-n%d" $fullname $shard $member | trunc 63 | trimSuffix "-" }} {{- $memberIndex := add (mul $shard (add (int $.Values.cluster.replicasPerShard) 1)) $member }} {{- $announceHostname := printf "%s-0.%s" $memberName $headless }} {{- $externalServiceName := printf "%s-x" $memberName }} --- apiVersion: apps/v1 kind: StatefulSet metadata: name: {{ $memberName }} namespace: {{ $.Release.Namespace }} labels: {{- include "redis-cluster.labels" $root | nindent 4 }} redis-cluster.shard: {{ $shard | quote }} redis-cluster.member: {{ $member | quote }} spec: serviceName: {{ $fullname }}-headless replicas: 1 podManagementPolicy: Parallel # Separate one-member StatefulSets would otherwise all roll concurrently. # OnDelete makes upgrades explicit and safe; see README.md. updateStrategy: type: OnDelete selector: matchLabels: {{- include "redis-cluster.selectorLabels" $root | nindent 6 }} redis-cluster.shard: {{ $shard | quote }} redis-cluster.member: {{ $member | quote }} template: metadata: labels: {{- include "redis-cluster.selectorLabels" $root | nindent 8 }} redis-cluster.shard: {{ $shard | quote }} redis-cluster.member: {{ $member | quote }} {{- with $.Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") $root | sha256sum }} {{- if not $.Values.istio.injectSidecar }} sidecar.istio.io/inject: "false" {{- else }} proxy.istio.io/config: '{ "holdApplicationUntilProxyStarts": true }' {{- end }} {{- with $.Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} spec: serviceAccountName: {{ include "redis-cluster.serviceAccountName" $root }} automountServiceAccountToken: false terminationGracePeriodSeconds: {{ $.Values.terminationGracePeriodSeconds }} {{- with $.Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} {{- with $.Values.priorityClassName }} priorityClassName: {{ . }} {{- end }} securityContext: {{- toYaml $.Values.podSecurityContext | nindent 8 }} affinity: {{- if gt $member 0 }} # Cross replicas onto the worker holding another shard's canonical # primary. For 3x1 this produces A:P0+R2, B:P1+R0, C:P2+R1. podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: {{- include "redis-cluster.selectorLabels" $root | nindent 18 }} redis-cluster.shard: {{ mod (add $shard $member) (int $.Values.cluster.shards) | quote }} redis-cluster.member: "0" topologyKey: kubernetes.io/hostname {{- end }} podAntiAffinity: # Hard invariants: copies of one shard never share a worker, and # equivalent member identities are spread across workers. In the # canonical topology, this permits at most one primary per worker. requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: {{- include "redis-cluster.selectorLabels" $root | nindent 18 }} redis-cluster.shard: {{ $shard | quote }} topologyKey: kubernetes.io/hostname - labelSelector: matchLabels: {{- include "redis-cluster.selectorLabels" $root | nindent 18 }} redis-cluster.member: {{ $member | quote }} topologyKey: kubernetes.io/hostname {{- with $.Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with $.Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} {{- with $.Values.topologySpreadConstraints }} topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} {{- if $.Values.externalAccess.enabled }} initContainers: - name: discover-external-endpoint image: "{{ $.Values.externalAccess.endpointDiscovery.image.repository }}:{{ $.Values.externalAccess.endpointDiscovery.image.tag }}" imagePullPolicy: {{ $.Values.externalAccess.endpointDiscovery.image.pullPolicy }} command: ["/bin/sh", "/scripts/discover-external-endpoint.sh"] securityContext: {{- toYaml $.Values.containerSecurityContext | nindent 12 }} env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: EXTERNAL_SERVICE_NAME value: {{ $externalServiceName | quote }} resources: {{- toYaml $.Values.externalAccess.endpointDiscovery.resources | nindent 12 }} volumeMounts: - name: scripts mountPath: /scripts - name: external-endpoint mountPath: /external-endpoint - name: kube-api-access mountPath: /var/run/secrets/kubernetes.io/serviceaccount readOnly: true {{- end }} containers: - name: redis image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}" imagePullPolicy: {{ $.Values.image.pullPolicy }} command: ["/bin/sh", "/scripts/start-redis.sh"] securityContext: {{- toYaml $.Values.containerSecurityContext | nindent 12 }} env: {{- include "redis-cluster.commonEnv" $root | nindent 12 }} - name: COORDINATOR_HOST value: {{ $coordinator | quote }} - name: ALL_NODE_HOSTS value: {{ join "," $allHosts | quote }} - name: PRIMARY_HOSTS value: {{ join "," $primaryHosts | quote }} - name: REPLICA_ASSIGNMENTS value: {{ join "," $assignments | quote }} - name: NODE_ADDRESS_ASSIGNMENTS value: {{ join "," $addressAssignments | quote }} - name: ANNOUNCE_HOSTNAME value: {{ $announceHostname | quote }} - name: ANNOUNCE_PORT value: {{ ternary $.Values.externalAccess.port $.Values.redis.port $.Values.externalAccess.enabled | quote }} - name: EXTERNAL_ACCESS_ENABLED value: {{ $.Values.externalAccess.enabled | quote }} ports: - name: tcp-redis containerPort: {{ $.Values.redis.port }} - name: tcp-cluster containerPort: {{ $.Values.cluster.busPort }} startupProbe: exec: command: ["/bin/sh", "/scripts/startup-redis.sh"] initialDelaySeconds: {{ $.Values.probes.redis.startup.initialDelaySeconds }} periodSeconds: {{ $.Values.probes.redis.startup.periodSeconds }} timeoutSeconds: {{ $.Values.probes.redis.startup.timeoutSeconds }} failureThreshold: {{ $.Values.probes.redis.startup.failureThreshold }} livenessProbe: exec: command: ["/bin/sh", "/scripts/liveness-redis.sh"] periodSeconds: {{ $.Values.probes.redis.liveness.periodSeconds }} timeoutSeconds: {{ $.Values.probes.redis.liveness.timeoutSeconds }} failureThreshold: {{ $.Values.probes.redis.liveness.failureThreshold }} readinessProbe: exec: command: ["/bin/sh", "/scripts/readiness-redis.sh"] periodSeconds: {{ $.Values.probes.redis.readiness.periodSeconds }} timeoutSeconds: {{ $.Values.probes.redis.readiness.timeoutSeconds }} failureThreshold: {{ $.Values.probes.redis.readiness.failureThreshold }} lifecycle: preStop: exec: command: ["/bin/sh", "/scripts/prestop-redis.sh"] resources: {{- toYaml $.Values.redis.resources | nindent 12 }} volumeMounts: - name: data mountPath: /data - name: scripts mountPath: /scripts - name: config mountPath: /etc/redis-ro - name: redis-runtime mountPath: /etc/redis-runtime {{- if $.Values.externalAccess.enabled }} - name: external-endpoint mountPath: /external-endpoint readOnly: true {{- end }} {{- if $.Values.metrics.enabled }} - name: metrics image: "{{ $.Values.metrics.image.repository }}:{{ $.Values.metrics.image.tag }}" imagePullPolicy: {{ $.Values.metrics.image.pullPolicy }} securityContext: {{- toYaml $.Values.containerSecurityContext | nindent 12 }} env: - name: REDIS_ADDR value: "redis://localhost:{{ $.Values.redis.port }}" {{- if $.Values.auth.enabled }} - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: {{ include "redis-cluster.secretName" $root }} key: {{ include "redis-cluster.secretKey" $root }} {{- end }} ports: - name: http-metrics containerPort: {{ $.Values.metrics.port }} livenessProbe: tcpSocket: port: http-metrics initialDelaySeconds: 10 periodSeconds: 15 readinessProbe: tcpSocket: port: http-metrics periodSeconds: 15 resources: {{- toYaml $.Values.metrics.resources | nindent 12 }} {{- end }} volumes: - name: scripts configMap: name: {{ $fullname }} defaultMode: 0555 - name: config configMap: name: {{ $fullname }} items: - key: redis.conf path: redis.conf - name: redis-runtime emptyDir: {} {{- if $.Values.externalAccess.enabled }} - name: external-endpoint emptyDir: {} - name: kube-api-access projected: defaultMode: 0444 sources: - serviceAccountToken: path: token expirationSeconds: 3600 - configMap: name: kube-root-ca.crt items: - key: ca.crt path: ca.crt {{- end }} {{- if not $.Values.persistence.enabled }} - name: data emptyDir: {} {{- end }} {{- if $.Values.persistence.enabled }} volumeClaimTemplates: - metadata: name: data {{- with $.Values.persistence.annotations }} annotations: {{- toYaml . | nindent 10 }} {{- end }} spec: accessModes: {{- toYaml $.Values.persistence.accessModes | nindent 10 }} {{- with $.Values.persistence.storageClass }} storageClassName: {{ . | quote }} {{- end }} resources: requests: storage: {{ $.Values.persistence.size }} {{- end }} {{- end }} {{- end }}