386 lines
11 KiB
YAML
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.
|