Files
helm-charts/valkey/templates/valkey.configmap.yaml
T
jklein 813c796a7b
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 4s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 11s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 1m22s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Valkey Helm Chart / Publish Valkey Helm Chart (push) Successful in 7s
feat(valkey): one Valkey instance per application (1.0.0+up9.1.2.alpine)
A new chart for a single Valkey instance per application, replacing the
shared cluster-redis whose separation rests on one ACL file in one
1Password field - a construction in which every change touches six
applications and which cost a month of Paperless standstill.

Shape: StatefulSet with one replica, ClusterIP Service, no cluster mode,
no Ingress and no value to add one.

The password arrives as a finished configuration line ("requirepass
<secret>") in the 1Password field auth-conf, is mounted as a file and
included by the main configuration - never in argv, never in the
environment. The include is rendered last, so nothing in extraConfig can
override it. Because a misspelled or empty field would leave an open
server that looks perfectly healthy, an init container greps the mounted
file for the line and refuses to start the pod without it; it never
prints the file.

maxmemory is derived from the computed memory limit (maxmemoryFactor,
default 0.75) and a configured value above that limit aborts the render:
otherwise the kubelet's OOM kill decides what the eviction policy was
configured to decide. Persistence and eviction are documented as pairs -
queue (PVC + noeviction, the default) and cache (emptyDir +
allkeys-lru) - and the combination "no persistence + noeviction" carries
a warning in values.yaml and in NOTES.txt.

Probes: valkey-cli ping as an exec probe, authenticated through
VALKEYCLI_AUTH read out of the mounted file, with "-e" and an assertion
on the answer PONG. Without -e valkey-cli exits 0 on an error reply
(valkey 9.1.2, src/valkey-cli.c: the config.set_errcode branch in
cliReadReply), which would make the probe unable to fail.

Hardening: uid 999 / gid 1000 - the image's own valkey user - with
fsGroup matching, runAsNonRoot, readOnlyRootFilesystem, all capabilities
dropped and seccompProfile RuntimeDefault in the pod context so the init
container is covered too. /data is the only writable path.

NetworkPolicy on by default (first chart in this repo with one), with a
switch and a list of additional namespaces; values.yaml names that it is
only a boundary where the CNI enforces policies.

Wiring: publish-valkey.yml, the chart-name choice list, ci/valkey
fixtures for the persistent variant, a plain docker-versioning Renovate
block, README chart row.
2026-09-27 11:24:55 +00:00

165 lines
8.1 KiB
YAML

{{- $maxmemory := include "valkey.maxmemoryBytes" (dict "resources" .Values.valkey.resources "maxmemory" .Values.valkey.maxmemory "maxmemoryFactor" .Values.valkey.maxmemoryFactor) -}}
{{- $limitBytes := include "valkey.memoryLimitBytes" .Values.valkey.resources -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Release.Name }}-{{ .Chart.Name }}-configmap"
data:
# The server's whole configuration. It is mounted as a directory at
# /etc/valkey and passed to the image as an argument, so the entrypoint starts
# "valkey-server /etc/valkey/valkey.conf".
#
# The pod template carries checksum/config over this ConfigMap: without it a
# changed configuration would restart nothing, and Valkey reads its
# configuration exactly once, at start.
valkey.conf: |
# Rendered by the valkey Helm chart - do not edit in the cluster, the next
# helm upgrade overwrites it. Every value below comes from values.yaml.
port 6379
# The data directory. It is also the image's WORKDIR, so this is only made
# explicit for whoever reads the file; it is a PVC when
# persistence.enabled is true and an emptyDir otherwise.
dir /data
# maxmemory is derived from the container's memory limit
# ({{ $limitBytes }} bytes = {{ include "valkey.humanBytes" $limitBytes }}), not configured independently of it:
# {{ if kindIs "invalid" .Values.valkey.maxmemory }}maxmemoryFactor {{ .Values.valkey.maxmemoryFactor | default 0.75 }} x that limit{{ else }}valkey.maxmemory was set explicitly and checked against that limit{{ end }}.
# The remainder of the limit is for what maxmemory does NOT cover: client
# output buffers, the AOF buffer, and the copy-on-write pages of the fork()
# that writes a snapshot or rewrites the AOF.
maxmemory {{ $maxmemory }}
maxmemory-policy {{ required "valkey.maxmemoryPolicy must be set (see values.yaml for the two sensible pairs)" .Values.valkey.maxmemoryPolicy }}
{{- if .Values.persistence.enabled }}
{{- if .Values.persistence.appendonly }}
# Append-only file: every write is appended, so a crash loses at most the
# last fsync interval instead of everything since the last snapshot. The
# RDB save points stay at their defaults, as a second, cheaper copy.
appendonly yes
{{- else }}
# persistence.appendonly is false: the dataset survives only as periodic RDB
# snapshots (Valkey's default save points), so a crash loses every write
# since the last one - and it does so silently, because the instance comes
# back looking healthy, only shorter.
appendonly no
{{- end }}
{{- else }}
# persistence.enabled is false: /data is an emptyDir, so nothing written
# there survives a pod restart anyway. Both persistence mechanisms are
# switched off rather than left writing into ephemeral storage - "save """
# disables the RDB save points, which are on by default.
appendonly no
save ""
{{- end }}
{{- with .Values.valkey.extraConfig }}
# valkey.extraConfig, verbatim.
{{ tpl . $ | trim | nindent 4 }}
{{- end }}
# The password, as a complete configuration line out of the 1Password item
# (field "auth-conf"), mounted as a file. Two things about this line are
# deliberate:
#
# - it is an "include" rather than a "requirepass" written here, so the
# password is never part of this ConfigMap, of the process list, or of
# anything Helm renders;
# - it is LAST. In a Valkey configuration the last occurrence of a
# directive wins, so nothing in valkey.extraConfig above can switch the
# password off, by accident or otherwise.
#
# An init container has already verified that this file carries a
# "requirepass <password>" line before the server ever starts.
include /etc/valkey-auth/auth.conf
# The probe command for all three probes. A script rather than an inline shell
# one-liner, because the two things that make it a real check need to be
# written down next to them.
ping.sh: |
#!/bin/sh
# Authenticated liveness/readiness/startup check for Valkey.
#
# WHY "-e" AND grep, i.e. two assertions rather than one: a bare
# "valkey-cli ping" probe CANNOT FAIL. Read off the source (valkey 9.1.2,
# src/valkey-cli.c): cliReadReply turns an error reply into exit(1) in
# exactly one branch, guarded by config.set_errcode, and "-e" is the only
# thing that sets it ("-e Return exit error code when command execution
# fails."). Without -e the command prints "NOAUTH Authentication required."
# and exits 0 - green, forever, whatever the server does.
#
# Either half alone would catch that: "grep -qx PONG" asserts the ANSWER, so
# it fails on an error reply even without -e. Both are here on purpose. The
# grep is the assertion; "-e" makes the failure explicit and, because the
# error then goes to stderr instead of stdout, puts the server's own message
# ("NOAUTH...", "WRONGPASS...") into the probe's failure output where
# kubectl describe shows it.
#
# WHY THE PASSWORD COMES THROUGH THE ENVIRONMENT: "valkey-cli -a <password>"
# puts the password in the process list, where every container in this pod
# can read it. VALKEYCLI_AUTH is valkey-cli's own environment variable for
# it (REDISCLI_AUTH is still honoured as the legacy spelling), and it is set
# only for the duration of this one command.
#
# Nothing here prints the password: it only ever lives in a shell variable,
# and valkey-cli's own error messages do not echo it.
set -eu
AUTH_FILE=/etc/valkey-auth/auth.conf
# The same file the server was configured from, so probe and server can
# never disagree about the password. sed's character class is written the
# POSIX way (no \+): this runs in the image's BusyBox shell.
pass="$(sed -n 's/^requirepass[[:space:]][[:space:]]*//p' "$AUTH_FILE" | head -n 1)"
# Valkey's configuration parser allows the value to be quoted; strip one
# surrounding pair of double quotes if present.
case "$pass" in
'"'*'"') pass="${pass#\"}"; pass="${pass%\"}" ;;
esac
VALKEYCLI_AUTH="$pass" valkey-cli -e -p 6379 ping | grep -qx PONG
# The init container's check. It validates the mounted auth file and does NOT
# assemble it - the password is a finished configuration line from the vault.
check-auth.sh: |
#!/bin/sh
# Refuse to start unless the mounted auth file really carries a
# "requirepass <password>" line.
#
# The failure this prevents: the password arrives as a ready-made
# configuration line from a 1Password field, and a misspelled key
# ("requirepas"), a stray quote or an empty field would leave Valkey running
# WITHOUT any password - no render error, no crash, a green pod, and an open
# server (the official image compiles protected-mode off). Of all the ways
# this chart can be misconfigured, that is the only one nothing else would
# report.
#
# It never prints the file: an error message containing the password would
# put it in the pod's event log and in every CI diagnostics dump.
set -eu
AUTH_FILE=/etc/valkey-auth/auth.conf
if [ ! -r "$AUTH_FILE" ]; then
echo "valkey: $AUTH_FILE is missing or unreadable." >&2
echo " The 1Password item behind secrets.authSecretRef must carry a field" >&2
echo " named 'auth-conf'. The 1Password operator turns each field of the" >&2
echo " item into one key of the Kubernetes Secret, so a differently named" >&2
echo " field produces no such file." >&2
exit 1
fi
if ! grep -q '^requirepass[[:space:]][[:space:]]*[^[:space:]]' "$AUTH_FILE"; then
echo "valkey: the mounted auth file carries no 'requirepass <password>' line." >&2
echo " Expected the field 'auth-conf' of the 1Password item to contain" >&2
echo " exactly one configuration line:" >&2
echo " requirepass <the password>" >&2
echo " Refusing to start: Valkey would come up without a password, report" >&2
echo " healthy, and accept connections from the whole pod network." >&2
echo " (The file's contents are deliberately not printed.)" >&2
exit 1
fi