Files
feedc0de 35ae5557b9
Validate and deploy Plex / Validate manifests and scripts (push) Successful in 12s
Validate and deploy Plex / Deploy to Kubernetes (push) Successful in 18s
Initial commit with the existing deployment files
2026-10-09 09:09:31 +02:00

386 lines
11 KiB
YAML

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: plex-config
namespace: plex
labels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: state
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 160Gi
storageClassName: rook-ceph-block-ec
---
apiVersion: v1
kind: ConfigMap
metadata:
name: plex-scripts
namespace: plex
labels:
app.kubernetes.io/name: plex
data:
check-media.sh: |
#!/bin/sh
set -eu
filesystem_magic=$(stat -f -c '%t' /komposthaufen/multimedia)
if [ "${filesystem_magic}" != ca451a4e ]; then
echo "Expected bcachefs at /komposthaufen/multimedia, found filesystem magic ${filesystem_magic}" >&2
exit 1
fi
test -d /komposthaufen/multimedia/Videos
extract-token.sh: |
#!/bin/sh
set -eu
preferences='/config/Library/Application Support/Plex Media Server/Preferences.xml'
test -s "${preferences}"
token=$(sed -n 's/.*PlexOnlineToken="\([^"]*\)".*/\1/p' "${preferences}")
if [ -z "${token}" ]; then
echo 'PlexOnlineToken is missing from Preferences.xml' >&2
exit 1
fi
umask 077
printf '%s' "${token}" > /token/plex-token
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: plex
namespace: plex
labels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: server
annotations:
deployment.brunner.ninja/revision: manual
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: server
template:
metadata:
labels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: server
spec:
automountServiceAccountToken: false
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
nodeSelector:
kubernetes.io/hostname: arschrock
media.brunner.ninja/multimedia-ready: "true"
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
terminationGracePeriodSeconds: 120
securityContext:
seccompProfile:
type: RuntimeDefault
initContainers:
- name: media-ready
image: docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
imagePullPolicy: IfNotPresent
command:
- /tools/check-media.sh
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
volumeMounts:
- name: media
mountPath: /komposthaufen/multimedia
readOnly: true
- name: media-ready
mountPath: /media-ready
readOnly: true
- name: tools
mountPath: /tools
readOnly: true
- name: exporter-token
image: docker.io/library/alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b
imagePullPolicy: IfNotPresent
command:
- /tools/extract-token.sh
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 964
runAsGroup: 964
volumeMounts:
- name: config
mountPath: /config
readOnly: true
- name: exporter-token
mountPath: /token
- name: tools
mountPath: /tools
readOnly: true
containers:
- name: plex
image: docker.io/plexinc/pms-docker@sha256:f6748983db1054b571b57b4a40f07f53af6c4bfb9edd1fa455f5ebb6e16449bc
imagePullPolicy: IfNotPresent
env:
- name: TZ
value: Europe/Vienna
- name: PLEX_UID
value: "964"
- name: PLEX_GID
value: "964"
- name: CHANGE_CONFIG_DIR_OWNERSHIP
value: "false"
ports:
- name: http
containerPort: 32400
protocol: TCP
startupProbe:
httpGet:
path: /identity
port: http
failureThreshold: 180
periodSeconds: 5
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /identity
port: http
failureThreshold: 3
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
httpGet:
path: /identity
port: http
failureThreshold: 3
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 500m
memory: 2Gi
ephemeral-storage: 1Gi
securityContext:
allowPrivilegeEscalation: false
volumeMounts:
- name: config
mountPath: /config
- name: media
mountPath: /komposthaufen/multimedia
readOnly: true
- name: transcode
mountPath: /transcode
- name: plex-exporter
image: ghcr.io/cplieger/plex-exporter@sha256:07ee6d213698bfb0dc086004f26e1048380caaa229f0429e9aafa2eb487ee826
imagePullPolicy: IfNotPresent
env:
- name: PLEX_URL
value: http://127.0.0.1:32400
- name: PLEX_TOKEN_FILE
value: /token/plex-token
ports:
- name: metrics
containerPort: 9594
protocol: TCP
startupProbe:
httpGet:
path: /api/health
port: metrics
failureThreshold: 60
periodSeconds: 5
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /api/health
port: metrics
failureThreshold: 3
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
httpGet:
path: /api/health
port: metrics
failureThreshold: 3
periodSeconds: 30
timeoutSeconds: 5
resources:
requests:
cpu: 10m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 964
runAsGroup: 964
volumeMounts:
- name: exporter-token
mountPath: /token
readOnly: true
- name: exporter-tmp
mountPath: /tmp
volumes:
- name: config
persistentVolumeClaim:
claimName: plex-config
- name: media
hostPath:
path: /komposthaufen/multimedia
type: Directory
- name: media-ready
hostPath:
path: /komposthaufen/multimedia/Videos
type: Directory
- name: transcode
emptyDir: {}
- name: exporter-token
emptyDir:
medium: Memory
sizeLimit: 1Mi
- name: exporter-tmp
emptyDir:
medium: Memory
sizeLimit: 8Mi
- name: tools
configMap:
name: plex-scripts
defaultMode: 0555
---
apiVersion: v1
kind: Service
metadata:
name: plex
namespace: plex
labels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: service
spec:
selector:
app.kubernetes.io/name: plex
app.kubernetes.io/component: server
ports:
- name: http
port: 32400
targetPort: http
protocol: TCP
- name: metrics
port: 9594
targetPort: metrics
protocol: TCP
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: plex-brunner-ninja
namespace: plex
labels:
app.kubernetes.io/name: plex
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`plex.brunner.ninja`)
services:
- kind: Service
name: plex
port: http
passHostHeader: true
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: plex
namespace: plex
labels:
app.kubernetes.io/name: plex
spec:
selector:
matchLabels:
app.kubernetes.io/name: plex
app.kubernetes.io/component: service
endpoints:
- port: metrics
path: /metrics
interval: 30s
scrapeTimeout: 10s
metricRelabelings:
- action: labeldrop
regex: (pod|endpoint|container)
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: plex
namespace: plex
labels:
app.kubernetes.io/name: plex
spec:
groups:
- name: plex.availability
rules:
- alert: PlexMetricsTargetDown
expr: |-
max(kube_deployment_status_replicas_available{namespace="plex",deployment="plex"}) >= 1
and on()
(
max(up{namespace="plex",service="plex"}) < 1
or absent(up{namespace="plex",service="plex"})
)
for: 10m
labels:
severity: warning
component: exporter
annotations:
summary: Plex metrics are unavailable
description: The Plex Pod is available, but Prometheus cannot scrape its exporter.
- alert: PlexAPIUnreachable
expr: max(plex_http_reachable{namespace="plex",service="plex"}) < 1
for: 10m
labels:
severity: warning
component: plex
annotations:
summary: Plex API is unreachable from its exporter
description: The exporter has failed to poll Plex for at least ten minutes.
- alert: PlexContainerRestarting
expr: |-
sum by (container) (
increase(kube_pod_container_status_restarts_total{namespace="plex",pod=~"plex-.*"}[15m])
) > 2
for: 5m
labels:
severity: warning
component: kubernetes
annotations:
summary: Plex container is repeatedly restarting
description: Container {{ $labels.container }} restarted more than twice in 15 minutes.
- alert: PlexConfigVolumeFilling
expr: |-
kubelet_volume_stats_available_bytes{namespace="plex",persistentvolumeclaim="plex-config"}
/
kubelet_volume_stats_capacity_bytes{namespace="plex",persistentvolumeclaim="plex-config"} < 0.15
for: 30m
labels:
severity: warning
component: storage
annotations:
summary: Plex configuration volume is filling
description: Less than 15 percent of the Plex configuration PVC remains available.