jellyfin: replace redis Deployment with valkey-operator instance (#367)

## Why

Jellyfin depended on a self-managed `redis` Deployment for its distributed transcode-session store. Now that the valkey-operator is deployed cluster-wide, this hands that store to the operator so it is declaratively managed (rolling upgrades, health, future HA) instead of a hand-rolled Deployment.

## Changes

- Add `apps/base/jellyfin/valkey.yaml`: a `ValkeyCluster` (`valkey.io/v1alpha1`) named `jellyfin-valkey`, single instance (`shards: 1`, `replicas: 0`), image via the artifactapi dockerhub mirror (`valkey/valkey:9.0.0`), metrics exporter disabled, redis-parity resources.
- Omit persistence so `/data` is an `emptyDir`: the transcode-lease state is ephemeral, and the operator-managed PVC cannot carry the `k8up.io/backup: "false"` annotation the namespace-wide k8up Schedule needs to skip an in-use RWO volume (the old redis PVC carried it).
- Remove `redis-deployment.yaml`, `redis-service.yaml`, `redis-pvc.yaml` and drop them from `kustomization.yaml`; add `valkey.yaml`.
- Repoint `Jellyfin__TranscodeStore__RedisConnectionString` at the operator client Service `valkey-jellyfin-valkey:6379`, preserving `abortConnect=false` and `LeaseDurationSeconds`.

## Auth / TLS

None. This operator runs Valkey cluster-mode-enabled with `protected-mode no` and leaves the built-in `default` user passwordless (no `requirepass`), so clients connect unauthenticated over plaintext — parity with the previous open redis. StackExchange.Redis auto-discovers the single node via the headless Service. No `VaultStaticSecret` / plain Secret is introduced.

## Validation

- `kustomize build --enable-helm apps/overlays/au-syd1/jellyfin` renders cleanly: `ValkeyCluster` present, no redis Deployment/Service/PVC, connection string updated.
- pre-commit passes (incl. the plain-secrets guard).
- kubeconform: 10 valid / 0 invalid; the `ValkeyCluster` is skipped (CRD schema not vendored).

## Follow-up

HA (task 6) is intentionally out of scope: it will raise `replicas` (and/or `shards`) and add anti-affinity/topology spread. Enabling auth (ACL `default` user + password via `VaultStaticSecret`) and/or TLS can also be layered on later.

Reviewed-on: #367
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #367.
This commit is contained in:
2026-08-15 14:54:05 +10:00
committed by BenVincent
parent 6c8c0dd9e4
commit 40ba1730de
6 changed files with 33 additions and 108 deletions
+1 -3
View File
@@ -20,8 +20,6 @@ resources:
- statefulset.yaml
- pdb.yaml
- service.yaml
- redis-deployment.yaml
- redis-pvc.yaml
- redis-service.yaml
- valkey.yaml
- gateway.yaml
- httproute.yaml
-66
View File
@@ -1,66 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: jellyfin
spec:
replicas: 1
selector:
matchLabels:
app: redis
strategy:
type: Recreate
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
imagePullPolicy: IfNotPresent
command:
- redis-server
- --save
- "20"
- "1"
ports:
- containerPort: 6379
name: redis
protocol: TCP
livenessProbe:
exec:
command:
- redis-cli
- ping
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
exec:
command:
- redis-cli
- ping
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- mountPath: /data
name: data
restartPolicy: Always
volumes:
- name: data
persistentVolumeClaim:
claimName: jellyfin-redis-data
-19
View File
@@ -1,19 +0,0 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-redis-data
namespace: jellyfin
annotations:
# Exclude from the jellyfin-config k8up Schedule (skipWithoutAnnotation is
# false cluster-wide, so unannotated PVCs are swept in). Redis holds only
# ephemeral transcode-lease state; RWO would also fail to mount while in use.
k8up.io/backup: "false"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: cephrbd-fast-delete
volumeMode: Filesystem
-17
View File
@@ -1,17 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: jellyfin
spec:
internalTrafficPolicy: Cluster
ports:
- name: redis
port: 6379
protocol: TCP
targetPort: redis
selector:
app: redis
sessionAffinity: None
type: ClusterIP
+3 -3
View File
@@ -5,7 +5,7 @@ metadata:
name: jellyfin
namespace: jellyfin
spec:
# HA: two replicas coordinate transcode session ownership through Redis and
# HA: two replicas coordinate transcode session ownership through Valkey and
# resume each other's HLS segments off the shared RWX transcode PVC. Stable
# pod names (jellyfin-0/1) are the lease owner identity, hence StatefulSet.
replicas: 2
@@ -118,7 +118,7 @@ spec:
containerPort: 8096
protocol: TCP
env:
# Pod identity for the Redis transcode lease owner. The fork reads
# Pod identity for the Valkey transcode lease owner. The fork reads
# JELLYFIN_INSTANCE_ID (falling back to MachineName); the stable
# StatefulSet pod name gives each replica a unique lease identity so
# takeover can target a dead replica. JELLYFIN_HA_POD_NAME is set for
@@ -139,7 +139,7 @@ spec:
value: /config/config
# Distributed transcode session store (jellyfin-ha additions).
- name: Jellyfin__TranscodeStore__RedisConnectionString
value: "redis:6379,abortConnect=false"
value: "valkey-jellyfin-valkey:6379,abortConnect=false"
- name: Jellyfin__TranscodeStore__LeaseDurationSeconds
value: "30"
# PostgreSQL main DB via the CNPG-generated app secret, routed through
+29
View File
@@ -0,0 +1,29 @@
---
# Single-instance Valkey managed by valkey-operator, replacing the self-managed
# redis Deployment as Jellyfin's distributed transcode-session store. shards:1 +
# replicas:0 is one primary with no replicas. This operator always runs Valkey
# cluster-mode-enabled with protected-mode off and leaves the built-in `default`
# user passwordless, so clients connect with no auth/TLS (parity with the old
# open redis); StackExchange.Redis auto-discovers the single node. HA (more
# shards/replicas) is a follow-up. Persistence is intentionally omitted so /data
# is an emptyDir: the lease state is ephemeral, and the operator-managed PVC
# cannot carry the k8up.io/backup:"false" annotation the namespace's cluster-wide
# k8up Schedule needs to skip an in-use RWO volume.
apiVersion: valkey.io/v1alpha1
kind: ValkeyCluster
metadata:
name: jellyfin-valkey
namespace: jellyfin
spec:
shards: 1
replicas: 0
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/valkey/valkey:9.0.0
exporter:
enabled: false
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi