Valkey Addon¶
A Redis-compatible key-value store managed by the platform: one
StatefulSet per app, plan-controlled replica count, single
addressable endpoint via a regular ClusterIP Service, and a
REDIS_URL env var injected straight into the app pod via
envFrom.
The wire protocol is Redis-compatible (Valkey is a fork) so any
existing Redis client connects unchanged — the connection URI
scheme stays redis://.
How it works¶
sequenceDiagram
participant U as Operator
participant CP as Control Plane
participant DB as Postgres (app_addons)
participant K as Kubernetes
participant V as Valkey StatefulSet
participant App as App pod
U->>CP: POST /v1/apps/{id}/addons { type:"valkey", plan:"standard" }
CP->>CP: validate plan + type, plan_to_resources / valkey_plan_config
CP->>DB: INSERT app_addons (status='creating')
CP->>K: ensure_valkey(plan) → StatefulSet + ClusterIP Service
K->>V: bootstrap pods (valkey/valkey:7-alpine, --requirepass)
K->>K: Secret {name}-auth (uri, host, port, password)
CP->>K: ensure_redis_url_secret → Secret app-{id}-redis-url
loop until ready
CP->>K: get StatefulSet.status.readyReplicas
CP->>CP: parse_valkey_status → Creating / Ready / Failed
end
K->>App: envFrom: { secretRef: { name: app-{id}-redis-url } }
App->>K: read REDIS_URL on start
Plans¶
| Plan | Replicas | Memory | Persistence |
|---|---|---|---|
free |
1 | 64Mi | none |
standard |
1 | 256Mi | rdb_daily |
pro |
3 (HA) | 1Gi | rdb_aof |
Unknown plans silently fall back to free so a typo can't push the
StatefulSet into a bucket the cluster can't satisfy. Persistence
strings are advisory today (the StatefulSet doesn't yet wire a
PVC); cycle 3 will mount a volume for rdb_* plans.
Service shape — ClusterIP, NOT headless¶
A key difference from Redis: Valkey is exposed as a regular
ClusterIP Service. Apps connect to a single endpoint
({name}.{namespace}.svc.cluster.local:6379); the replication
and failover behaviour is internal to Valkey. Redis ships as a
headless Service (clusterIP: None) so clients pick a specific
pod — that path is preserved for backwards-compatibility with the
existing Redis addon, but Valkey doesn't need it.
REDIS_URL secret¶
ensure_redis_url_secret server-side-applies a Secret named
app-{app_id}-redis-url in the app's tenant namespace, with a
single REDIS_URL key whose value is the full
redis://:{password}@{name}.{namespace}.svc.cluster.local:6379
URI sourced from the {name}-auth Secret create_valkey already
produced. The URI is not re-constructed — the canonical value
lives in one place, in CNPG-style.
The app pod's PodSpec mounts it via:
Result: a process.env.REDIS_URL (or os.environ['REDIS_URL'])
on the app side. Clients that prefer parsed creds can split it
themselves — Redis client libraries accept the URI directly.
Status vocabulary¶
parse_valkey_status(ready, desired) projects StatefulSet
readiness into the operator-facing vocabulary:
| ready vs desired | ValkeyStatus |
|---|---|
ready == desired (both > 0) |
Ready |
ready == 0 |
Creating |
partial (0 < ready < desired) |
Failed |
Failed is the loud signal — a replica died or the rollout
stalled. The operator should kubectl describe statefulset in
the tenant namespace.
Idempotency¶
| Op | Idempotency |
|---|---|
POST /addons (same app_id, type=valkey) |
DB row rotates via ON CONFLICT (app_id, addon_type) DO UPDATE; StatefulSet rotates via ensure_valkey (get-then-create-or-patch on spec.replicas) |
ensure_redis_url_secret |
Patch::Apply(...).force() with field manager paas-control-plane — refreshes if Valkey password rotates |
Naming conventions (pinned by tests)¶
| Resource | Pattern | Source |
|---|---|---|
| StatefulSet | valkey-{tenant_id}-{app_id} (truncated at 53 chars) |
valkey::valkey_name |
| Auth Secret | {name}-auth (single source of uri/password/host/port) |
valkey::create_valkey |
| App-side Secret | app-{app_id}-redis-url |
valkey::redis_url_secret_name |
| Image | valkey/valkey:7-alpine |
valkey::VALKEY_IMAGE_BASE |
Failure modes¶
| Symptom | Likely cause | Recovery |
|---|---|---|
Creating for >5 min |
image pull, scheduling | kubectl describe statefulset … in tenant ns |
Failed (partial readiness) |
replica crashed, OOM, exec liveness failed | kubectl logs the pod that's not ready; if OOM, bump plan to standard or pro |
App restart-loops on missing REDIS_URL |
ensure_redis_url_secret never ran |
re-call POST /addons — server-side apply re-converges |
Phase 2¶
- PVC + persistence wiring — today the
rdb_dailyandrdb_aofplans don't actually persist; cycle 3 mounts a PVC and toggles the Valkey config flags. - Valkey Cluster mode — current
proplan runs 3 replicas but they're standalone; cluster sharding lands in a follow-up. - Eviction policy per plan — today every plan uses Valkey's
default
noeviction. The dashboard might surface amaxmemory-policyknob in cycle 3.
Related¶
- Postgres Addon — same lifecycle, same
app_addonstable, same naming conventions - Add-ons — the unified list view
- Network Policies —
allow-app-redis/allow-app-valkeyis the per-app egress allow that lets pods reach the addon