diff --git a/valkey/templates/haproxy-deployment.yaml b/valkey/templates/haproxy-deployment.yaml index 51fa5177..4d594d51 100644 --- a/valkey/templates/haproxy-deployment.yaml +++ b/valkey/templates/haproxy-deployment.yaml @@ -6,6 +6,10 @@ metadata: labels: {{- include "valkey.labels" . | nindent 4 }} app.kubernetes.io/component: haproxy + {{- with .Values.workloadAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: replicas: {{ .Values.haproxy.replicas }} selector: diff --git a/valkey/templates/init_config.yaml b/valkey/templates/init_config.yaml index cb712c5b..6e4c919d 100644 --- a/valkey/templates/init_config.yaml +++ b/valkey/templates/init_config.yaml @@ -5,6 +5,78 @@ metadata: labels: {{- include "valkey.labels" . | nindent 4 }} data: + prestop.sh: |- + # preStop hook to detect master role and request failover via Sentinel + # This runs before the pod is terminated and uses the existing TLS/auth mounts. + #!/bin/sh + set -eu + + log() { + echo "$(date +'%Y-%m-%dT%H:%M:%SZ') $*" + } + + log "Starting Valkey preStop hook..." + + # Build TLS arguments only when TLS is enabled in the chart values. + TLS_ARGS="" + if [ "{{ .Values.tls.enabled }}" = "true" ]; then + TLS_ARGS="--tls --cacert /tls/{{ .Values.tls.caPublicKey }}" + fi + + # Function to fetch the password for a given Valkey username. + # It checks mounted secrets in the order configured by the chart. + get_password_for_user() { + username="$1" + password_key="${2:-$username}" + + if [ -f "/valkey-users-secret/$password_key" ]; then + cat "/valkey-users-secret/$password_key" + return 0 + fi + if [ -f "/valkey-auth-secret/${username}-password" ]; then + cat "/valkey-auth-secret/${username}-password" + return 0 + fi + return 1 + } + + # Determine the user used for ROLE queries and failover. + # Prefer values from replica.sentinel if configured, otherwise fall back to default. + ROLE_USERNAME="${VALKEY_MONITOR_USERNAME:-{{ .Values.replica.sentinel.monitorUser | default "default" }}}" + SENTINEL_USERNAME="${VALKEY_SENTINEL_USERNAME:-{{ .Values.replica.sentinel.interSentinelUser | default "default" }}}" + # Prepare auth args for the ROLE and Sentinel commands. + ROLE_AUTH_ARGS="" + SENTINEL_AUTH_ARGS="" + if [ "{{ .Values.auth.enabled }}" = "true" ]; then + if ROLE_PASSWORD=$(get_password_for_user "$ROLE_USERNAME"); then + ROLE_AUTH_ARGS="--user $ROLE_USERNAME -a $ROLE_PASSWORD" + else + log "WARN: auth enabled but password for role user '$ROLE_USERNAME' was not found." + fi + if SENTINEL_PASSWORD=$(get_password_for_user "$SENTINEL_USERNAME"); then + SENTINEL_AUTH_ARGS="--user $SENTINEL_USERNAME -a $SENTINEL_PASSWORD" + else + log "WARN: auth enabled but password for sentinel user '$SENTINEL_USERNAME' was not found." + fi + fi + + ROLE_OUTPUT=$(sh -c "valkey-cli ${TLS_ARGS} ${ROLE_AUTH_ARGS} -p {{ .Values.service.port }} ROLE" 2>/dev/null || true) + ROLE=$(printf '%s' "$ROLE_OUTPUT" | head -n 1 | tr -d '"') + log "Detected role: $ROLE" + + # If this node is currently master, request controlled failover from Sentinel. + if [ "$ROLE" = "master" ]; then + log "Pausing client writes for {{ .Values.replica.sentinel.clientPauseTimeout }} milliseconds..." + sh -c "valkey-cli ${TLS_ARGS} ${ROLE_AUTH_ARGS} -p {{ .Values.service.port }} CLIENT PAUSE {{ .Values.replica.sentinel.clientPauseTimeout }} WRITE" >/dev/null 2>&1 || log "WARN: failed to pause client writes." + + log "Master node detected; requesting failover from Sentinel..." + sh -c "valkey-cli ${TLS_ARGS} ${SENTINEL_AUTH_ARGS} -p {{ .Values.replica.sentinel.port }} SENTINEL failover {{ .Values.replica.sentinel.masterSet }}" >/dev/null 2>&1 || log "WARN: failed to invoke SENTINEL failover. Cluster may rely on automatic failover." + + log "Failover request completed." + else + log "Not master; skipping failover request." + fi + init.sh: |- #!/bin/sh set -eu diff --git a/valkey/templates/statefulset.yaml b/valkey/templates/statefulset.yaml index a9632537..7f95d1a8 100644 --- a/valkey/templates/statefulset.yaml +++ b/valkey/templates/statefulset.yaml @@ -42,7 +42,6 @@ spec: requests: storage: {{ .Values.replica.sentinel.persistence.size | quote }} {{- end }} - template: metadata: labels: @@ -182,6 +181,13 @@ spec: args: [ "/data/conf/valkey.conf" ] securityContext: {{- toYaml .Values.securityContext | nindent 12 }} + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - /scripts/prestop.sh env: - name: POD_INDEX valueFrom: @@ -216,6 +222,8 @@ spec: volumeMounts: - name: valkey-data mountPath: /data + - name: scripts + mountPath: /scripts {{- if .Values.tls.enabled }} - name: {{ include "valkey.fullname" . }}-tls mountPath: /tls @@ -223,6 +231,16 @@ spec: {{- 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 }} {{- range $secret := .Values.extraValkeySecrets }} - name: {{ $secret.name }}-valkey diff --git a/valkey/values.yaml b/valkey/values.yaml index d9cec4b0..f8063bdf 100644 --- a/valkey/values.yaml +++ b/valkey/values.yaml @@ -306,6 +306,9 @@ replica: # Number of replicas to reconfigure in parallel after failover parallelSyncs: 1 + # Time in milliseconds to pause client writes during failover + clientPauseTimeout: 10000 # milliseconds + # Resource limits/requests for sentinel container resources: {} # Example: @@ -599,4 +602,4 @@ haproxy: # requests: # cpu: 10m # memory: 16Mi - securityContext: {} \ No newline at end of file + securityContext: {}