{{- if .Values.redisHelm }} --- apiVersion: v1 kind: ConfigMap metadata: { name: "{{ .Values.redis }}-config", namespace: "{{ .Values.namespace }}" } data: redis.conf: | bind 0.0.0.0 port 6379 dir /data # --- ACL --- aclfile /data-acl/{{ .Values.redisFileUsersAcl }} # --- all in one DB --- databases 1 # --- network --- protected-mode yes tcp-backlog 1024 tcp-keepalive 300 timeout 0 # --- connections --- maxclients {{ .Values.profiles.redis.maxClients }} # --- memory (tune) --- maxmemory {{ .Values.profiles.redis.maxmemory }} maxmemory-policy allkeys-lfu maxmemory-samples 5 maxmemory-eviction-tenacity 10 maxmemory-clients 5% client-query-buffer-limit 256mb client-output-buffer-limit normal 0 0 0 client-output-buffer-limit replica 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # --- replication --- replica-serve-stale-data yes replica-read-only yes repl-backlog-size 64mb repl-backlog-ttl 3600 min-replicas-to-write 0 #min-replicas-max-lag 10 # --- persistence --- appendonly yes appendfsync everysec aof-rewrite-incremental-fsync yes rdb-save-incremental-fsync yes save "" # --- log --- slowlog-log-slower-than 10000 slowlog-max-len 128 latency-monitor-threshold 0 --- apiVersion: v1 kind: PersistentVolume metadata: { name: "{{ .Values.redis }}-users-pv" } spec: capacity: { storage: 1Gi } volumeMode: Filesystem accessModes: [ ReadWriteMany ] persistentVolumeReclaimPolicy: Retain hostPath: { path: "{{ .Values.redisPath }}", type: DirectoryOrCreate } --- apiVersion: v1 kind: PersistentVolumeClaim metadata: { name: "{{ .Values.redis }}-users-pvc", namespace: "{{ .Values.namespace }}" } spec: volumeName: "{{ .Values.redis }}-users-pv" volumeMode: Filesystem accessModes: [ ReadWriteMany ] resources: { requests: { storage: 1Gi } } storageClassName: "" --- apiVersion: apps/v1 kind: StatefulSet metadata: { name: "{{ .Values.redis }}", namespace: "{{ .Values.namespace }}" } spec: serviceName: "{{ .Values.redis }}" replicas: 2 selector: { matchLabels: { app: "{{ .Values.redis }}" } } persistentVolumeClaimRetentionPolicy: { whenDeleted: Delete, whenScaled: Retain } template: metadata: { labels: { app: "{{ .Values.redis }}" } } spec: terminationGracePeriodSeconds: 30 initContainers: - name: init-redis-acl image: redis:8.6-alpine resources: requests: { cpu: "10m", memory: "32Mi" } limits: { cpu: "100m", memory: "128Mi" } env: - { name: POD_NAME, valueFrom: { fieldRef: { fieldPath: metadata.name } } } - { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } } command: ["/bin/sh","-c"] args: - | set -eu ACL="/data-acl/{{ .Values.redisFileUsersAcl }}" HASH=$(printf '%s' "$REDIS_PASSWORD" | sha256sum | awk '{print $1}') mkdir -p /data-acl if [ "$POD_NAME" = "{{ .Values.redis }}-0" ]; then tmp="${ACL}.tmp" if [ -f "$ACL" ]; then awk '!(tolower($1)=="user" && $2=="default")' "$ACL" > "$tmp" else : > "$tmp" fi # default user is used by replication auth and HAProxy health checks printf 'user default on sanitize-payload #%s ~* &* +@all\n' "$HASH" >> "$tmp" mv "$tmp" "$ACL" chmod 600 "$ACL" else i=0 while [ ! -f "$ACL" ] && [ $i -lt 300 ]; do sleep 1 i=$((i+1)) done [ -f "$ACL" ] || exit 1 fi volumeMounts: - { name: "{{ .Values.redis }}-users-volume", mountPath: /data-acl } containers: - name: "{{ .Values.redis }}" image: redis:8.6-alpine resources: requests: { cpu: "{{ .Values.profiles.redis.cpuRequest }}", memory: "{{ .Values.profiles.redis.memoryRequest }}" } limits: { cpu: "{{ .Values.profiles.redis.cpuLimit }}", memory: "{{ .Values.profiles.redis.memoryLimit }}" } ports: - { name: redis, containerPort: 6379 } env: - { name: POD_NAME, valueFrom: { fieldRef: { fieldPath: metadata.name } } } - { name: POD_IP, valueFrom: { fieldRef: { fieldPath: status.podIP } } } - { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } } - { name: SENTINEL_HOST, value: "{{ .Values.redisSentinel }}" } - { name: SENTINEL_PORT, value: "26379" } - { name: MASTER_NAME, value: "mymaster" } command: ["/bin/sh","-c"] args: - | set -eu POD_DNS_SHORT="${POD_NAME}.{{ .Values.redis }}" POD_DNS_FQDN="${POD_NAME}.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local" get_master() { REDISCLI_AUTH="$REDIS_PASSWORD" \ redis-cli -h "$SENTINEL_HOST" -p "$SENTINEL_PORT" \ SENTINEL get-master-addr-by-name "$MASTER_NAME" 2>/dev/null | tr -d '\r' } out="" i=0 while [ $i -lt 20 ]; do out="$(get_master || true)" [ -n "$out" ] && break i=$((i+1)) sleep 1 done master_host="$(printf '%s\n' "$out" | sed -n '1p')" master_port="$(printf '%s\n' "$out" | sed -n '2p')" [ -n "$master_port" ] || master_port="6379" is_me_master() { [ "$master_host" = "$POD_IP" ] || [ "$master_host" = "$POD_DNS_SHORT" ] || [ "$master_host" = "$POD_DNS_FQDN" ] } if [ -n "$master_host" ]; then if is_me_master; then exec redis-server /etc/redis/redis.conf --masteruser default --masterauth "$REDIS_PASSWORD" else exec redis-server /etc/redis/redis.conf --replicaof "$master_host" "$master_port" --masteruser default --masterauth "$REDIS_PASSWORD" fi else # bootstrap if sentinel is not ready yet if [ "$POD_NAME" = "{{ .Values.redis }}-0" ]; then exec redis-server /etc/redis/redis.conf else exec redis-server /etc/redis/redis.conf --replicaof {{ .Values.redis }}-0.{{ .Values.redis }} 6379 --masteruser default --masterauth "$REDIS_PASSWORD" fi fi volumeMounts: - { name: "{{ .Values.redis }}-data-volume", mountPath: /data } - { name: "{{ .Values.redis }}-config-volume", mountPath: /etc/redis } - { name: "{{ .Values.redis }}-users-volume", mountPath: /data-acl } volumes: - name: "{{ .Values.redis }}-config-volume" configMap: { name: "{{ .Values.redis }}-config" } - name: "{{ .Values.redis }}-users-volume" persistentVolumeClaim: { claimName: "{{ .Values.redis }}-users-pvc" } volumeClaimTemplates: - metadata: { name: "{{ .Values.redis }}-data-volume" } spec: accessModes: [ ReadWriteOnce ] resources: { requests: { storage: 10Gi } } --- apiVersion: v1 kind: Service metadata: { name: "{{ .Values.redis }}", namespace: "{{ .Values.namespace }}" } spec: clusterIP: None selector: { app: "{{ .Values.redis }}" } ports: - { name: redis, protocol: TCP, port: 6379, targetPort: 6379 } # ------------------------- # Sentinel (1) with PVC # ------------------------- --- apiVersion: v1 kind: ConfigMap metadata: { name: "{{ .Values.redisSentinel }}-config", namespace: "{{ .Values.namespace }}" } data: sentinel.conf.tmpl: | bind 0.0.0.0 port 26379 dir /data sentinel deny-scripts-reconfig yes sentinel resolve-hostnames yes sentinel announce-hostnames yes # quorum=1 (single sentinel) sentinel monitor mymaster {{ .Values.redis }}-0.{{ .Values.redis }} 6379 1 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 sentinel auth-user mymaster default sentinel auth-pass mymaster __PASSWORD__ requirepass __PASSWORD__ --- apiVersion: apps/v1 kind: StatefulSet metadata: { name: "{{ .Values.redisSentinel }}", namespace: "{{ .Values.namespace }}" } spec: replicas: 1 serviceName: "{{ .Values.redisSentinel }}" selector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } } persistentVolumeClaimRetentionPolicy: { whenDeleted: Delete, whenScaled: Retain } template: metadata: { labels: { app: "{{ .Values.redisSentinel }}" } } spec: containers: - name: "{{ .Values.redisSentinel }}" image: redis:8.6-alpine resources: requests: { cpu: "50m", memory: "128Mi" } limits: { cpu: "200m", memory: "256Mi" } ports: - { name: sentinel, containerPort: 26379 } env: - { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } } command: ["/bin/sh","-c"] args: - | set -eu CONF=/data/sentinel.conf if [ ! -f "$CONF" ]; then pwd_escaped=$(printf '%s' "$REDIS_PASSWORD" | sed -e 's/[\/&]/\\&/g') sed "s/__PASSWORD__/${pwd_escaped}/g" /tmpl/sentinel.conf.tmpl > "$CONF" chmod 600 "$CONF" fi exec redis-server "$CONF" --sentinel volumeMounts: - { name: "{{ .Values.redisSentinel }}-tmpl", mountPath: /tmpl } - { name: "{{ .Values.redisSentinel }}-data", mountPath: /data } volumes: - name: "{{ .Values.redisSentinel }}-tmpl" configMap: { name: "{{ .Values.redisSentinel }}-config" } volumeClaimTemplates: - metadata: { name: "{{ .Values.redisSentinel }}-data" } spec: accessModes: [ ReadWriteOnce ] resources: { requests: { storage: 1Gi } } --- apiVersion: v1 kind: Service metadata: { name: "{{ .Values.redisSentinel }}", namespace: "{{ .Values.namespace }}" } spec: selector: { app: "{{ .Values.redisSentinel }}" } ports: - { name: sentinel, port: 26379, targetPort: 26379 } --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: { name: "{{ .Values.redisSentinel }}-allow-only-checker", namespace: "{{ .Values.namespace }}" } spec: podSelector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } } policyTypes: - Ingress ingress: - from: - podSelector: { matchLabels: { app: "{{ .Values.redis }}" } } - podSelector: { matchLabels: { app: "{{ .Values.redisSentinel }}" } } ports: - { protocol: TCP, port: 26379 } # ------------------------- # HAProxy: single master endpoint # ------------------------- --- apiVersion: v1 kind: ConfigMap metadata: { name: "{{ .Values.redis }}-haproxy-config", namespace: "{{ .Values.namespace }}" } data: haproxy.cfg: | global log stdout format raw local0 maxconn 20000 resolvers kubedns nameserver dns1 10.43.0.10:53 accepted_payload_size 8192 resolve_retries 3 timeout resolve 1s timeout retry 1s hold valid 10s hold obsolete 30s defaults mode tcp log global option tcplog option log-health-checks timeout connect 5s timeout client 1m timeout server 1m timeout check 1s frontend fe_redis_master bind *:6379 default_backend be_redis_master backend be_redis_master mode tcp balance first option tcp-check option srvtcpka timeout queue 2s timeout connect 2s timeout check 3s timeout server 10m tcp-check connect tcp-check send-lf "AUTH default $REDIS_PASSWORD\r\n" tcp-check expect string +OK tcp-check send INFO\ replication\r\n tcp-check expect string role:master tcp-check send QUIT\r\n tcp-check expect string +OK server redis0 {{ .Values.redis }}-0.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local:6379 check resolvers kubedns resolve-prefer ipv4 init-addr libc,none inter 5s fall 2 rise 2 server redis1 {{ .Values.redis }}-1.{{ .Values.redis }}.{{ .Values.namespace }}.svc.cluster.local:6379 check resolvers kubedns resolve-prefer ipv4 init-addr libc,none inter 5s fall 2 rise 2 --- apiVersion: apps/v1 kind: Deployment metadata: { name: "{{ .Values.redis }}-haproxy", namespace: "{{ .Values.namespace }}" } spec: replicas: 1 selector: { matchLabels: { app: "{{ .Values.redis }}-haproxy" } } template: metadata: labels: { app: "{{ .Values.redis }}-haproxy" } spec: containers: - name: "{{ .Values.redis }}-haproxy" image: haproxy:2.9-alpine resources: requests: { cpu: "50m", memory: "64Mi" } limits: { cpu: "500m", memory: "256Mi" } ports: - { name: redis, containerPort: 6379 } env: - { name: REDIS_PASSWORD, valueFrom: { secretKeyRef: { name: "{{ .Values.redis }}-secret", key: root-password } } } command: ["/bin/sh","-c"] args: - | set -eu haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg exec haproxy -f /usr/local/etc/haproxy/haproxy.cfg -db readinessProbe: { tcpSocket: { port: 6379 }, initialDelaySeconds: 1, periodSeconds: 2, failureThreshold: 3 } livenessProbe: { tcpSocket: { port: 6379 }, initialDelaySeconds: 10, periodSeconds: 10, failureThreshold: 3 } volumeMounts: # - { name: cfg, mountPath: /usr/local/etc/haproxy/haproxy.cfg, subPath: haproxy.cfg } - { name: cfg, mountPath: /usr/local/etc/haproxy } volumes: - name: cfg configMap: { name: "{{ .Values.redis }}-haproxy-config" } --- apiVersion: v1 kind: Service metadata: { name: "{{ .Values.redis }}-master", namespace: "{{ .Values.namespace }}" } spec: selector: { app: "{{ .Values.redis }}-haproxy" } ports: - { name: redis, port: 6379, targetPort: 6379 } {{- end }}