Files
argocd-apps/apps/base/arrstack/radarr/deployment.yaml
T
unkin-agent bdf6114794 arrstack: S3 buckets + wiring for app backups and sonarr MediaCover (#389)
## Why

Servarr backups (periodic config+DB zips) and sonarr MediaCover posters still live on each replica's local `/config` emptyDir: backups are leader-local and lost on reschedule, and sonarr covers render inconsistently across the 3 active-active replicas. #387 moved radarr covers to a shared Ceph RGW bucket; this extends that pattern to backups for all three apps and to sonarr's covers.

## Changes

- Add an `arrstack-backups` ObjectStoreUser + Bucket (cephrgw-operator); the operator mints the `arrstack-backups-s3` credential Secret in-namespace (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / S3_ENDPOINT), so no Vault KV seeding is required. Backups get their own bucket (not `arrstack-media`) because they carry secrets (Config.xml API keys, the DB) and warrant separate credentials/lifecycle from poster art; the three apps share it, isolated by a per-app key prefix.
- Wire sonarr to the shared `arrstack-media` bucket via `Sonarr__MediaCoverS3__*` (prefix `sonarr`, partitioned from radarr's `radarr` prefix to avoid movie/series id collisions), plus the estate CA mount at `/etc/ssl/vault-ca/ca.crt` for RGW TLS validation.
- Wire radarr/sonarr/prowlarr backups via `<App>__BackupS3__*` to `arrstack-backups` (per-app prefix); add the CA mount to sonarr and prowlarr.

## Notes

- Credentials come only from the operator-minted Secret via env; no secrets in manifests.
- The env keys are consumed by the -unkin3+ images (separate app PRs) and ignored by older images, so this is safe to apply ahead of the image bump.
- `kubectl kustomize apps/base/arrstack` builds clean.

Reviewed-on: #389
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-08-22 18:39:40 +10:00

198 lines
6.8 KiB
YAML

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: radarr
namespace: arrstack
spec:
# Active-active: the -unkin2 fork keeps all state in the shared Postgres
# (arrstack-postgres) and coordinates via Postgres advisory locks, so N
# replicas run concurrently behind the radarr Service. RollingUpdate is safe —
# no SQLite, no RWO lock.
replicas: 3
strategy:
type: RollingUpdate
selector:
matchLabels:
app: radarr
template:
metadata:
labels:
app: radarr
spec:
securityContext:
# Fork image has no USER (runs as root by default); pin it to a non-root
# UID and group-write the shared RWX CephFS /config (MediaCover etc.).
# OnRootMismatch avoids a recursive chown of the whole media tree.
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
initContainers:
# Gate the app on its own Postgres database+role being reachable, instead
# of relying on ArgoCD sync-waves (which deadlock if apps aren't Healthy).
# waitfordb reads the PG* env as a libpq fallback, so the password never lands in argv.
- name: wait-for-db
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/waitfordb:v0.1.0
env:
- name: WAITFORDB_TIMEOUT
value: 5m
- name: WAITFORDB_SSLMODE
value: disable
- name: PGHOST
value: arrstack-postgres-rw.arrstack.svc.cluster.local
- name: PGPORT
value: "5432"
- name: PGDATABASE
value: radarr-main
- name: PGUSER
valueFrom:
secretKeyRef:
name: radarr-db
key: username
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: radarr-db
key: password
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 100m
memory: 64Mi
containers:
- name: radarr
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/radarr:v6.4.2-unkin3
imagePullPolicy: IfNotPresent
command:
- /app/Radarr
args:
- -nobrowser
- -data=/config
# Required: bypass the single-instance guard so multiple replicas
# can share one /config. Cross-replica safety is the Postgres layer,
# not a local lock file.
- -nosingleinstancecheck
ports:
- name: http
containerPort: 7878
protocol: TCP
envFrom:
- configMapRef:
name: radarr-env
env:
- name: Radarr__Postgres__User
valueFrom:
secretKeyRef:
name: radarr-db
key: username
- name: Radarr__Postgres__Password
valueFrom:
secretKeyRef:
name: radarr-db
key: password
- name: Radarr__Auth__ApiKey
valueFrom:
secretKeyRef:
name: radarr-apikey
key: apitoken
# MediaCover object store (shared Ceph RGW bucket). Serves posters/fanart
# from S3 so any replica can render them instead of the leader-local
# emptyDir /config. Consumed by the -unkin3+ image; older images ignore
# these unknown config keys. Creds Secret is minted by cephrgw-operator.
- name: Radarr__MediaCover__S3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: S3_ENDPOINT
- name: Radarr__MediaCover__S3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_ACCESS_KEY_ID
- name: Radarr__MediaCover__S3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-media-s3
key: AWS_SECRET_ACCESS_KEY
- name: Radarr__MediaCover__S3__Bucket
value: arrstack-media
- name: Radarr__MediaCover__S3__Prefix
value: radarr
- name: Radarr__MediaCover__S3__ForcePathStyle
value: "true"
- name: Radarr__MediaCover__S3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
# Backup object store (shared arrstack-backups Ceph RGW bucket,
# per-app key prefix). Routes the periodic config+DB zip backups off
# the ephemeral /config so any replica can write and restore them.
- name: Radarr__BackupS3__Endpoint
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: S3_ENDPOINT
- name: Radarr__BackupS3__AccessKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_ACCESS_KEY_ID
- name: Radarr__BackupS3__SecretKey
valueFrom:
secretKeyRef:
name: arrstack-backups-s3
key: AWS_SECRET_ACCESS_KEY
- name: Radarr__BackupS3__Bucket
value: arrstack-backups
- name: Radarr__BackupS3__Prefix
value: radarr
- name: Radarr__BackupS3__ForcePathStyle
value: "true"
- name: Radarr__BackupS3__CaCertPath
value: /etc/ssl/vault-ca/ca.crt
livenessProbe:
httpGet:
path: /radarr/ping
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /radarr/ping
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: config
mountPath: /config
- name: media-movies
mountPath: /media/movies
- name: vault-ca
mountPath: /etc/ssl/vault-ca
readOnly: true
volumes:
- name: config
emptyDir: {}
- name: media-movies
persistentVolumeClaim:
claimName: media-movies
# Estate CA for validating the Ceph RGW (s3.ceph.unkin.net) TLS cert.
- name: vault-ca
secret:
secretName: vault-ca-cert
items:
- key: ca.crt
path: ca.crt