From 19f7afac92e791d06b4c6f221061f3f52e552f38 Mon Sep 17 00:00:00 2001 From: Unkin Agent Date: Sun, 23 Aug 2026 12:37:42 +1000 Subject: [PATCH] Add shared arrstack Valkey and wire arr apps' Redis features (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activates the arr forks (sonarr/radarr/prowlarr) #14 Redis features — SignalR backplane, cross-replica cache-invalidation bus, and distributed rate limiter — which ship deployed but inert because no Valkey exists in arrstack and nothing is wired to it. Ben wants ONE Valkey shared by all three apps. ## Changes - Adds a single shared `ValkeyCluster` (`arrstack-valkey`) under `apps/base/arrstack/valkey/`, modeled on `jellyfin-valkey`: `shards:1`/`replicas:2` HA (one primary + two replicas, automatic failover), image via artifactapi (`artifactapi.k8s.syd1.au.unkin.net/dockerhub/valkey/valkey:9.0.0`), operator-default passwordless `default` user, node spread across hosts, cluster-aware PDB, ephemeral `/data`. - Registers the `valkey` component in the arrstack base kustomization. - Wires `__Redis__Host` + `__Redis__Port` into the `sonarr-env` / `radarr-env` / `prowlarr-env` ConfigMaps, all pointing at the shared service `valkey-arrstack-valkey.arrstack.svc.cluster.local:6379`. ## Notes - Setting `Host` is the activation switch: the fork's `RedisOptions.IsConfigured` gates purely on a non-empty `Host`, so there is no separate Enabled flag. - Passwordless (jellyfin parity — the operator leaves the `default` user without auth), so no `Password`/`Ssl` is wired and there is no secret to reference. - One cluster is safe for all three: each fork namespaces its keys and pub/sub channels by a per-app prefix (`sonarr:ratelimit:` / `radarr:ratelimit:` / `prowlarr:ratelimit:`), so their state never collides. - App `image:` (-unkin5), the waitfordb initContainer, and S3 buckets are untouched. - Validated: `kubectl kustomize` renders clean for both `apps/base/arrstack` and `apps/overlays/au-syd1/arrstack`. Follow-up: after merge, Valkey must come up and the three apps must roll (pick up the new env) before the #14 features can be validated live. --------- Co-authored-by: Ben Vincent Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/394 Co-authored-by: Unkin Agent Co-committed-by: Unkin Agent --- apps/base/arrstack/kustomization.yaml | 1 + apps/base/arrstack/prowlarr/configmap.yaml | 9 ++++ apps/base/arrstack/prowlarr/deployment.yaml | 7 +++ apps/base/arrstack/radarr/configmap.yaml | 9 ++++ apps/base/arrstack/radarr/deployment.yaml | 7 +++ apps/base/arrstack/sonarr/configmap.yaml | 9 ++++ apps/base/arrstack/sonarr/deployment.yaml | 7 +++ apps/base/arrstack/valkey/kustomization.yaml | 6 +++ apps/base/arrstack/valkey/valkeycluster.yaml | 47 ++++++++++++++++++++ 9 files changed, 102 insertions(+) create mode 100644 apps/base/arrstack/valkey/kustomization.yaml create mode 100644 apps/base/arrstack/valkey/valkeycluster.yaml diff --git a/apps/base/arrstack/kustomization.yaml b/apps/base/arrstack/kustomization.yaml index 762a342..2798a6b 100644 --- a/apps/base/arrstack/kustomization.yaml +++ b/apps/base/arrstack/kustomization.yaml @@ -14,6 +14,7 @@ resources: - media-bucket.yaml - backups-bucket.yaml - postgres + - valkey - sonarr - radarr - prowlarr diff --git a/apps/base/arrstack/prowlarr/configmap.yaml b/apps/base/arrstack/prowlarr/configmap.yaml index 353c02b..18b943d 100644 --- a/apps/base/arrstack/prowlarr/configmap.yaml +++ b/apps/base/arrstack/prowlarr/configmap.yaml @@ -22,3 +22,12 @@ data: Prowlarr__Server__Port: "9696" Prowlarr__Server__UrlBase: /prowlarr Prowlarr__Update__Mechanism: External + # Shared arrstack Valkey (valkey-operator). Setting Host is what activates the + # fork's #14 Redis features (SignalR backplane, cross-replica cache-invalidation + # bus, distributed rate limiter): RedisOptions.IsConfigured gates purely on a + # non-empty Host, so there is no separate Enabled flag. The operator leaves the + # default user passwordless (jellyfin parity), so no Password/Ssl is wired. + # Channels/keys are namespaced by this fork's prowlarr:ratelimit: prefix, so the + # one cluster is safe to share with sonarr/radarr. + Prowlarr__Redis__Host: valkey-arrstack-valkey.arrstack.svc.cluster.local + Prowlarr__Redis__Port: "6379" diff --git a/apps/base/arrstack/prowlarr/deployment.yaml b/apps/base/arrstack/prowlarr/deployment.yaml index bdc3499..6e41040 100644 --- a/apps/base/arrstack/prowlarr/deployment.yaml +++ b/apps/base/arrstack/prowlarr/deployment.yaml @@ -4,6 +4,13 @@ kind: Deployment metadata: name: prowlarr namespace: arrstack + annotations: + # prowlarr-env is a plain (unhashed) ConfigMap consumed by fixed-name envFrom, + # so editing it does not roll the Deployment on its own. Reloader watches the + # referenced ConfigMap and triggers a rolling restart on change, so adding the + # Redis env activates the #14 features on the next ArgoCD sync without a manual + # `rollout restart`. + configmap.reloader.stakater.com/auto: "true" spec: # Active-active: the -unkin2 fork keeps all state in the shared Postgres # (arrstack-postgres) and coordinates via Postgres advisory locks, so N diff --git a/apps/base/arrstack/radarr/configmap.yaml b/apps/base/arrstack/radarr/configmap.yaml index 8678ced..17eedce 100644 --- a/apps/base/arrstack/radarr/configmap.yaml +++ b/apps/base/arrstack/radarr/configmap.yaml @@ -22,3 +22,12 @@ data: Radarr__Server__Port: "7878" Radarr__Server__UrlBase: /radarr Radarr__Update__Mechanism: External + # Shared arrstack Valkey (valkey-operator). Setting Host is what activates the + # fork's #14 Redis features (SignalR backplane, cross-replica cache-invalidation + # bus, distributed rate limiter): RedisOptions.IsConfigured gates purely on a + # non-empty Host, so there is no separate Enabled flag. The operator leaves the + # default user passwordless (jellyfin parity), so no Password/Ssl is wired. + # Channels/keys are namespaced by this fork's radarr:ratelimit: prefix, so the + # one cluster is safe to share with sonarr/prowlarr. + Radarr__Redis__Host: valkey-arrstack-valkey.arrstack.svc.cluster.local + Radarr__Redis__Port: "6379" diff --git a/apps/base/arrstack/radarr/deployment.yaml b/apps/base/arrstack/radarr/deployment.yaml index b64079b..e3120bb 100644 --- a/apps/base/arrstack/radarr/deployment.yaml +++ b/apps/base/arrstack/radarr/deployment.yaml @@ -4,6 +4,13 @@ kind: Deployment metadata: name: radarr namespace: arrstack + annotations: + # radarr-env is a plain (unhashed) ConfigMap consumed by fixed-name envFrom, + # so editing it does not roll the Deployment on its own. Reloader watches the + # referenced ConfigMap and triggers a rolling restart on change, so adding the + # Redis env activates the #14 features on the next ArgoCD sync without a manual + # `rollout restart`. + configmap.reloader.stakater.com/auto: "true" spec: # Active-active: the -unkin2 fork keeps all state in the shared Postgres # (arrstack-postgres) and coordinates via Postgres advisory locks, so N diff --git a/apps/base/arrstack/sonarr/configmap.yaml b/apps/base/arrstack/sonarr/configmap.yaml index e837b0e..dd2242c 100644 --- a/apps/base/arrstack/sonarr/configmap.yaml +++ b/apps/base/arrstack/sonarr/configmap.yaml @@ -22,3 +22,12 @@ data: Sonarr__Server__Port: "8989" Sonarr__Server__UrlBase: /sonarr Sonarr__Update__Mechanism: External + # Shared arrstack Valkey (valkey-operator). Setting Host is what activates the + # fork's #14 Redis features (SignalR backplane, cross-replica cache-invalidation + # bus, distributed rate limiter): RedisOptions.IsConfigured gates purely on a + # non-empty Host, so there is no separate Enabled flag. The operator leaves the + # default user passwordless (jellyfin parity), so no Password/Ssl is wired. + # Channels/keys are namespaced by this fork's sonarr:ratelimit: prefix, so the + # one cluster is safe to share with radarr/prowlarr. + Sonarr__Redis__Host: valkey-arrstack-valkey.arrstack.svc.cluster.local + Sonarr__Redis__Port: "6379" diff --git a/apps/base/arrstack/sonarr/deployment.yaml b/apps/base/arrstack/sonarr/deployment.yaml index f2331bc..18cd15f 100644 --- a/apps/base/arrstack/sonarr/deployment.yaml +++ b/apps/base/arrstack/sonarr/deployment.yaml @@ -4,6 +4,13 @@ kind: Deployment metadata: name: sonarr namespace: arrstack + annotations: + # sonarr-env is a plain (unhashed) ConfigMap consumed by fixed-name envFrom, + # so editing it does not roll the Deployment on its own. Reloader watches the + # referenced ConfigMap and triggers a rolling restart on change, so adding the + # Redis env activates the #14 features on the next ArgoCD sync without a manual + # `rollout restart`. + configmap.reloader.stakater.com/auto: "true" spec: # Active-active: the -unkin2 fork keeps all state in the shared Postgres # (arrstack-postgres) and coordinates via Postgres advisory locks, so N diff --git a/apps/base/arrstack/valkey/kustomization.yaml b/apps/base/arrstack/valkey/kustomization.yaml new file mode 100644 index 0000000..f4160f4 --- /dev/null +++ b/apps/base/arrstack/valkey/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - valkeycluster.yaml diff --git a/apps/base/arrstack/valkey/valkeycluster.yaml b/apps/base/arrstack/valkey/valkeycluster.yaml new file mode 100644 index 0000000..2c107df --- /dev/null +++ b/apps/base/arrstack/valkey/valkeycluster.yaml @@ -0,0 +1,47 @@ +--- +# Single shared HA Valkey for the arr apps (sonarr/radarr/prowlarr), managed by +# valkey-operator. It activates the fork's #14 Redis features: the SignalR +# backplane, the cross-replica cache-invalidation bus, and the distributed rate +# limiter. One cluster is safe for all three because each fork namespaces its +# keys and pub/sub channels by a per-app prefix (sonarr:ratelimit: / +# radarr:ratelimit: / prowlarr:ratelimit:), so their state never collides. +# +# Modeled on jellyfin-valkey: shards:1 + replicas:2 is one primary with two +# replicas in a single shard group (three ValkeyNodes total); losing the primary +# triggers an automatic failover so a node/pod loss no longer drops the shared +# state the app replicas coordinate through. The operator runs Valkey +# cluster-mode-enabled with protected-mode off and leaves the built-in `default` +# user passwordless, so clients connect with no auth/TLS; StackExchange.Redis +# seeds off the single service and auto-discovers topology plus failovers. +# scheduling.node.spread.shard:Required keeps the three nodes on distinct hosts, +# so one host loss removes at most one node; podDisruptionBudget.mode:Cluster +# lets the operator manage a quorum-aware PDB. Persistence is omitted (/data is an +# emptyDir): the coordination state is ephemeral (short TTLs / transient pub/sub), +# replication+failover already provide redundancy, and an operator-managed PVC +# cannot carry the k8up.io/backup:"false" annotation the namespace k8up Schedule +# needs to skip in-use RWO volumes. +apiVersion: valkey.io/v1alpha1 +kind: ValkeyCluster +metadata: + name: arrstack-valkey + namespace: arrstack +spec: + shards: 1 + replicas: 2 + image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/valkey/valkey:9.0.0 + exporter: + enabled: false + scheduling: + node: + spread: + shard: + mode: Required + podDisruptionBudget: + mode: Cluster + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi