Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions valkey/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,11 @@ Validate sentinel configuration
{{- if not .Values.replica.enabled }}
{{- fail "Sentinel mode requires replication to be enabled. Please set replica.enabled=true along with sentinel.enabled=true" }}
{{- end }}
{{- if lt (add (int .Values.replica.replicas) 1) 3 }}
{{- fail "Sentinel mode requires at least 3 Valkey pods (replicas: 2) for a stable quorum." }}
{{- if lt (int .Values.replica.sentinel.replicas) 3 }}
{{- fail "Sentinel mode requires at least 3 pods for a stable quorum." }}
{{- end }}
{{- if gt (int .Values.replica.sentinel.quorum) (int .Values.replica.sentinel.replicas) }}
{{- fail (printf "Sentinel quorum (%d) cannot be greater than sentinels count (%d)." (int .Values.replica.sentinel.quorum) (int .Values.replica.sentinel.replicas)) }}
{{- end }}
{{- if and .Values.auth.enabled (not (hasKey .Values.auth.aclUsers .Values.replica.replicationUser)) }}
{{- fail (printf "Sentinel with auth requires replication user '%s' to be defined in auth.aclUsers" .Values.replica.replicationUser) }}
Expand All @@ -219,3 +222,11 @@ Sentinel headless service name
{{ include "valkey.fullname" . }}-sentinel-headless
{{- end -}}

{{/*
Validate haproxy is used in replica mode
*/}}
{{- define "valkey.validateHaproxyRequirements" -}}
{{- if and .Values.haproxy.enabled (or (not .Values.replica.enabled) (not .Values.replica.sentinel.enabled)) }}
{{- fail "Haproxy requires replica mode and sentinel to handle incompatible sentinel clients usescases." }}
{{- end }}
{{- end -}}
1 change: 1 addition & 0 deletions valkey/templates/deploy_valkey.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{{- $storage := .Values.dataStorage }}
{{- $createPVC := and $storage.enabled (not (empty $storage.requestedSize)) (empty $storage.persistentVolumeClaimName) }}
{{- include "valkey.validateAuthConfig" . }}
{{- include "valkey.validateHaproxyRequirements" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down
27 changes: 12 additions & 15 deletions valkey/templates/haproxy-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,15 @@ data:
timeout server {{ .Values.haproxy.config.timeout.server }}
retries 3

frontend valkey_frontend_write
bind *:{{ .Values.haproxy.service.port | default 6379 }}
{{- range .Values.haproxy.service.ports }}
frontend valkey_frontend_{{ .name }}
bind *:{{ .port }}
mode tcp
option tcplog
default_backend valkey_backend_master

frontend valkey_frontend_read
bind *:{{ .Values.haproxy.service.readPort | default 6380 }}
mode tcp
option tcplog
default_backend valkey_backend_read
default_backend valkey_backend_{{ .name }}
{{- end }}

backend valkey_backend_master
backend valkey_backend_{{ (index .Values.haproxy.service.ports 0).name }}
mode tcp
# no-check: sentinel-watcher solely controls which server is active via
# the runtime socket (set server addr + enable/disable server).
Expand All @@ -41,7 +37,7 @@ data:
server valkey-{{ $i }} 127.0.0.1:6379 check-send-proxy disabled
{{- end }}

backend valkey_backend_read
backend valkey_backend_{{ (index .Values.haproxy.service.ports 1).name }}
mode tcp
option tcp-check

Expand Down Expand Up @@ -72,7 +68,7 @@ data:
# Sentinel watcher: polls Sentinel for master changes and updates HAProxy
# via the runtime socket.
#
# KEY DESIGN: All valkey_backend_master servers are configured as
# KEY DESIGN: All valkey_backend_{{ (index .Values.haproxy.service.ports 0).name }} servers are configured as
# `no-check disabled` in haproxy.cfg. This watcher is the SOLE controller
# of which server is active. It resolves the master hostname to a Pod IP
# and uses `set server addr` so HAProxy connects directly to the IP,
Expand All @@ -85,7 +81,8 @@ data:
POLL_INTERVAL="{{ .Values.haproxy.config.checkInterval | default 2 }}"
VALKEY_PORT="{{ .Values.service.port }}"
HAPROXY_SOCKET="/var/run/haproxy/admin.sock"
BACKEND="valkey_backend_master"
BACKEND="valkey_backend_{{ (index .Values.haproxy.service.ports 0).name }}"
TOTAL_SENTINELS="{{ .Values.replica.sentinel.replicas }}"
TOTAL_SERVERS="{{ add (int .Values.replica.replicas) 1 }}"

# Logging to stderr prevents polluting stdout
Expand Down Expand Up @@ -148,8 +145,8 @@ data:

# 1. Ask Sentinels who the master is
i=0
while [ "${i}" -lt "${TOTAL_SERVERS}" ]; do
S_HOST="{{ include "valkey.fullname" . }}-${i}.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}"
while [ "${i}" -lt "${TOTAL_SENTINELS}" ]; do
S_HOST="{{ include "valkey.fullname" . }}-sentinel-${i}.{{ include "valkey.sentinel.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}"

RESP=$(sentinel_cmd "${S_HOST}" sentinel get-master-addr-by-name "${MASTER_SET}" 2>/dev/null) || {
i=$((i + 1))
Expand Down
10 changes: 5 additions & 5 deletions valkey/templates/haproxy-deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{{- if .Values.haproxy.enabled }}
{{- include "valkey.validateHaproxyRequirements" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -47,12 +48,11 @@ spec:
{{- end }}
imagePullPolicy: {{ .Values.haproxy.image.pullPolicy }}
ports:
- name: valkey-write
containerPort: 6379
protocol: TCP
- name: valkey-read
containerPort: 6380
{{- range .Values.haproxy.service.ports }}
- name: {{ .name }}
containerPort: {{ .port }}
protocol: TCP
{{- end }}
volumeMounts:
- name: haproxy-config
mountPath: /usr/local/etc/haproxy/haproxy.cfg
Expand Down
19 changes: 12 additions & 7 deletions valkey/templates/haproxy-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ metadata:
spec:
type: {{ .Values.haproxy.service.type }}
ports:
- port: {{ .Values.haproxy.service.port | default 6379 }}
targetPort: {{ .Values.haproxy.service.port | default 6379 }}
{{- $serviceType := .Values.haproxy.service.type }}
{{- range .Values.haproxy.service.ports }}
- port: {{ .port }}
targetPort: {{ .port }}
protocol: TCP
name: valkey-write
- port: {{ .Values.haproxy.service.readPort | default 6380 }}
targetPort: {{ .Values.haproxy.service.readPort | default 6380 }}
protocol: TCP
name: valkey-read
name: {{ .name }}
{{- if and (eq $serviceType "NodePort") .nodePort }}
nodePort: {{ .nodePort }}
{{- end }}
{{- if .appProtocol }}
appProtocol: {{ .appProtocol }}
{{- end }}
{{- end }}
selector:
{{- include "valkey.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: haproxy
Expand Down
7 changes: 6 additions & 1 deletion valkey/templates/init_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data:

# Default config paths
VALKEY_CONFIG=${VALKEY_CONFIG_PATH:-/data/conf/valkey.conf}
BOOTSTRAP_FILE="/data/conf/.bootstrap"

LOGFILE="/data/init.log"
DATA_DIR="/data/conf"
Expand Down Expand Up @@ -175,7 +176,10 @@ data:
# 1. Determine Initial Topology or Restored State
REPLICA_TARGET=""
if [ "${SENTINEL_MODE:-false}" = "true" ]; then
if [ -n "$SAVED_REPLICA_LINE" ]; then
if [ -f "$BOOTSTRAP_FILE" ] && [ -z "$SAVED_REPLICA_LINE" ] ; then
log "Sentinel mode: Restoring master state from previous run"
REPLICA_TARGET=""
elif [ -n "$SAVED_REPLICA_LINE" ]; then
# CRITICAL: Restore topology written by Sentinel (CONFIG REWRITE)
log "Sentinel Mode: Restoring replication state from previous run: $SAVED_REPLICA_LINE"
REPLICA_TARGET="$SAVED_REPLICA_LINE"
Expand Down Expand Up @@ -269,3 +273,4 @@ data:
log "Appending files in /extravalkeyconfigs/"
cat /extravalkeyconfigs/* >>"$VALKEY_CONFIG"
fi
touch $BOOTSTRAP_FILE
43 changes: 36 additions & 7 deletions valkey/templates/sentinel-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,46 @@ data:
FAILOVER_TIMEOUT="{{ .Values.replica.sentinel.failoverTimeout }}"
PARALLEL_SYNCS="{{ .Values.replica.sentinel.parallelSyncs }}"

# Master is always pod-0 of the valkey statefulset initially
MASTER_HOST="{{ include "valkey.fullname" . }}-0.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}"
MASTER_PORT="{{ .Values.service.port }}"

SENTINEL_CONF="/data/sentinel.conf"

# Logging function (outputs to stderr to avoid polluting stdout command substitutions)
log() {
echo "$(date) $1" >&2
}

has_master() {
for i in $(seq 0 "{{ (int .Values.replica.replicas) }}"); do
valkey-cli -h {{ include "valkey.fullname" . }}-$i.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} INFO replication 2>/dev/null \
| grep -q "^role:master" && return 0
done
return 1
}

# Wait until a master is UP
until has_master; do
log "Master not ready"
sleep 5
done

# Discover who is the current master
MASTER_HOST=""
for i in $(seq 0 "{{ (int .Values.replica.replicas) }}"); do
HOST="{{ include "valkey.fullname" . }}-$i.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}"

ROLE=$(redis-cli -h "$HOST" INFO replication 2>/dev/null \
| awk -F: '/^role:/ {print $2}' \
| tr -d '\r')

if [ "$ROLE" = "master" ]; then
MASTER_HOST="$HOST"
break
fi

i=$((i + 1))
done
MASTER_PORT="{{ .Values.service.port }}"
log "Found existing master $MASTER_HOST"

SENTINEL_CONF="/sentinel-data/sentinel.conf"

log "Initializing Sentinel configuration..."

{{- if .Values.auth.enabled }}
Expand Down Expand Up @@ -78,7 +107,7 @@ data:
# Announce IP for discovery in Kubernetes
sentinel resolve-hostnames yes
sentinel announce-hostnames yes
sentinel announce-ip ${POD_NAME}.{{ include "valkey.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}
sentinel announce-ip ${POD_NAME}.{{ include "valkey.sentinel.headlessServiceName" . }}.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}
sentinel announce-port ${SENTINEL_PORT}

# Monitor the master
Expand Down
Loading