Publish Plex chart and replace direct Kubernetes CD
This commit is contained in:
1 parent
4d2bff4fac
commit
1d84fb0bc4
9 files changed
+179
-147
No files matched your search
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: plex
|
||||
description: Plex Media Server with guarded host-local media and optional monitoring
|
||||
type: application
|
||||
version: 0.0.0
|
||||
appVersion: "1.43.4.10903-e5521bd8c"
|
||||
@@ -0,0 +1,388 @@
|
||||
{{- if not .Values.persistence.existingClaim }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: plex-config
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app.kubernetes.io/name: plex
|
||||
app.kubernetes.io/component: state
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- with .Values.persistence.storageClass }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: plex-scripts
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app.kubernetes.io/name: plex
|
||||
data:
|
||||
check-media.sh: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
filesystem_magic=$(stat -f -c '%t' {{ .Values.media.mountPath }})
|
||||
if [ "${filesystem_magic}" != ca451a4e ]; then
|
||||
echo "Expected bcachefs at {{ .Values.media.mountPath }}, found filesystem magic ${filesystem_magic}" >&2
|
||||
exit 1
|
||||
fi
|
||||
test -d {{ .Values.media.mountPath }}/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: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app.kubernetes.io/name: plex
|
||||
app.kubernetes.io/component: server
|
||||
annotations:
|
||||
deployment.brunner.ninja/revision: manual
|
||||
spec:
|
||||
replicas: 1
|
||||
progressDeadlineSeconds: 3900
|
||||
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:
|
||||
{{- toYaml .Values.nodeSelector | nindent 8 }}
|
||||
tolerations:
|
||||
{{- toYaml .Values.tolerations | nindent 8 }}
|
||||
terminationGracePeriodSeconds: 120
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
- name: media-ready
|
||||
image: docker.io/library/alpine:3.24.2
|
||||
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: {{ .Values.media.mountPath }}
|
||||
readOnly: true
|
||||
- name: media-ready
|
||||
mountPath: /media-ready
|
||||
readOnly: true
|
||||
- name: tools
|
||||
mountPath: /tools
|
||||
readOnly: true
|
||||
- name: exporter-token
|
||||
image: docker.io/library/alpine:3.24.2
|
||||
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: {{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: TZ
|
||||
value: {{ .Values.timezone | quote }}
|
||||
- name: PLEX_UID
|
||||
value: {{ .Values.plexUid | quote }}
|
||||
- name: PLEX_GID
|
||||
value: {{ .Values.plexGid | quote }}
|
||||
- name: CHANGE_CONFIG_DIR_OWNERSHIP
|
||||
value: "false"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 32400
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /identity
|
||||
port: http
|
||||
failureThreshold: 720
|
||||
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:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: media
|
||||
mountPath: {{ .Values.media.mountPath }}
|
||||
readOnly: true
|
||||
- name: transcode
|
||||
mountPath: /transcode
|
||||
- name: plex-exporter
|
||||
image: ghcr.io/cplieger/plex-exporter:v4.4.0
|
||||
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: {{ default "plex-config" .Values.persistence.existingClaim }}
|
||||
- name: media
|
||||
hostPath:
|
||||
path: {{ .Values.media.hostPath }}
|
||||
type: Directory
|
||||
- name: media-ready
|
||||
hostPath:
|
||||
path: {{ .Values.media.hostPath }}/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: {{ .Release.Namespace }}
|
||||
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
|
||||
{{- if .Values.ingress.enabled }}
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: {{ default "plex" .Values.ingress.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app.kubernetes.io/name: plex
|
||||
spec:
|
||||
entryPoints:
|
||||
- {{ .Values.ingress.entryPoint }}
|
||||
routes:
|
||||
- kind: Rule
|
||||
match: Host(`{{ required "ingress.host is required" .Values.ingress.host }}`)
|
||||
services:
|
||||
- kind: Service
|
||||
name: plex
|
||||
port: http
|
||||
passHostHeader: true
|
||||
{{- end }}
|
||||
{{- if .Values.monitoring.enabled }}
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: plex
|
||||
namespace: {{ .Release.Namespace }}
|
||||
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: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app.kubernetes.io/name: plex
|
||||
spec:
|
||||
groups:
|
||||
- name: plex.availability
|
||||
rules:
|
||||
- alert: PlexMetricsTargetDown
|
||||
expr: |-
|
||||
max(kube_deployment_status_replicas_available{namespace="{{ .Release.Namespace }}",deployment="plex"}) >= 1
|
||||
and on()
|
||||
(
|
||||
max(up{namespace="{{ .Release.Namespace }}",service="plex"}) < 1
|
||||
or absent(up{namespace="{{ .Release.Namespace }}",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="{{ .Release.Namespace }}",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="{{ .Release.Namespace }}",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="{{ .Release.Namespace }}",persistentvolumeclaim="{{ default "plex-config" .Values.persistence.existingClaim }}"}
|
||||
/
|
||||
kubelet_volume_stats_capacity_bytes{namespace="{{ .Release.Namespace }}",persistentvolumeclaim="{{ default "plex-config" .Values.persistence.existingClaim }}"} < 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.
|
||||
{{- end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
image:
|
||||
repository: docker.io/plexinc/pms-docker
|
||||
tag: ""
|
||||
media:
|
||||
hostPath: /media
|
||||
mountPath: /media
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
persistence:
|
||||
existingClaim: ""
|
||||
storageClass: ""
|
||||
size: 160Gi
|
||||
timezone: UTC
|
||||
plexUid: "964"
|
||||
plexGid: "964"
|
||||
resources: {}
|
||||
ingress:
|
||||
enabled: false
|
||||
name: ""
|
||||
host: ""
|
||||
entryPoint: websecure
|
||||
monitoring:
|
||||
enabled: false
|
||||
Reference in new issue
Block a user