Update patchmon to 2.1.3

Chart rewrite for the 2.x single-server architecture: one stateless
server deployment (frontend embedded, /api served by the same process),
no PVC, single service and ingress path, optional guacd sidecar
(disabled by default), 2.x env vocabulary (TRUST_PROXY, readonly role),
probes send the public hostname as Host header.

Fixes #29: all three reported spots (PVC storageClass guard, RWX assets
PVC, frontend nginx 'backend' hostname) are removed by this rewrite.
This commit is contained in:
jklein committed 2026-08-22 13:18:59 +02:00
1 parent f12d6409fd
commit fbe0dbac81
10 files changed
+141 -259

No files matched your search

@@ -1,79 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
spec:
replicas: {{ include "patchmon.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.patchmon.frontend.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
template:
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
spec:
automountServiceAccountToken: false
securityContext:
{{- toYaml .Values.patchmon.frontend.securityContext.pod | nindent 8 }}
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
image: "ghcr.io/patchmon/patchmon-frontend:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
securityContext:
{{- toYaml .Values.patchmon.frontend.securityContext.container | nindent 12 }}
ports:
- containerPort: 3000
protocol: TCP
name: http
env:
- name: BACKEND_URL
value: "{{ .Release.Name }}-{{ .Chart.Name }}-backend-service"
- name: BACKEND_PORT
value: "80"
resources:
{{- include "patchmon.resources" .Values.patchmon.frontend.resources | nindent 12 }}
livenessProbe:
httpGet:
path: /
port: 3000
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 3000
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# The startup probe gates liveness/readiness. Startup budget = 5s * 30 = 150s.
startupProbe:
httpGet:
path: /
port: 3000
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
volumeMounts:
- name: patchmon-volume
mountPath: /usr/share/nginx/html/assets
subPath: patchmon-assets
# nginx-unprivileged keeps its pid file and temp dirs in /tmp, and
# its entrypoint renders config templates into /etc/nginx/conf.d;
# both must be writable with a read-only root filesystem.
- name: tmp
mountPath: /tmp
- name: nginx-conf-d
mountPath: /etc/nginx/conf.d
restartPolicy: Always
volumes:
- name: tmp
emptyDir: {}
- name: nginx-conf-d
emptyDir: {}
- name: patchmon-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
@@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend-service
spec:
selector:
app: {{ .Release.Name }}-{{ .Chart.Name }}-frontend
ports:
- protocol: TCP
port: 80
targetPort: http
name: http
type: ClusterIP
@@ -1,30 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-backend
name: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-backend
app: {{ .Release.Name }}-{{ .Chart.Name }}
spec:
replicas: {{ include "patchmon.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.patchmon.backend.replicas) }}
replicas: {{ include "patchmon.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.patchmon.server.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-backend
app: {{ .Release.Name }}-{{ .Chart.Name }}
template:
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-backend
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-backend
app: {{ .Release.Name }}-{{ .Chart.Name }}
spec:
automountServiceAccountToken: false
# Pin a stable pod hostname: the server identifies itself in the agent
# presence registry (Redis) by hostname, and a Deployment pod's default
# hostname changes on every rollout, which would leave stale presence
# records behind (upstream pins "patchmon-server" in its compose file
# for the same reason).
hostname: {{ .Release.Name }}-{{ .Chart.Name }}-server
securityContext:
fsGroup: 1000
fsGroupChangePolicy: Always
{{- toYaml .Values.patchmon.server.securityContext.pod | nindent 8 }}
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-backend
image: "ghcr.io/patchmon/patchmon-backend:{{ .Chart.AppVersion }}"
- name: {{ .Release.Name }}-{{ .Chart.Name }}-server
image: "ghcr.io/patchmon/patchmon-server:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
securityContext:
{{- toYaml .Values.patchmon.server.securityContext.container | nindent 12 }}
ports:
- containerPort: 3001
- containerPort: 3000
protocol: TCP
name: http
env:
@@ -58,8 +65,6 @@ spec:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
key: db
- name: ASSETS_DIR
value: /app/assets
- name: JWT_SECRET
valueFrom:
secretKeyRef:
@@ -104,8 +109,11 @@ spec:
key: requested-scopes
- name: OIDC_AUTO_CREATE_USERS
value: "true"
# 2.x role vocabulary: superadmin, admin, host_manager, readonly,
# user. "viewer" (the 1.x default of this chart) no longer exists;
# readonly is its least-privilege equivalent.
- name: OIDC_DEFAULT_ROLE
value: "viewer"
value: "readonly"
- name: OIDC_DISABLE_LOCAL_AUTH
value: "true"
- name: OIDC_BUTTON_TEXT
@@ -125,51 +133,100 @@ spec:
secretKeyRef:
name: {{ .Release.Name }}-{{ .Chart.Name }}-oidc-secret
key: admin-group-name
- name: SERVER_PROTOCOL
value: "https"
- name: SERVER_HOST
value: "{{ .Values.ingress.domain }}"
- name: SERVER_PORT
value: "443"
- name: CORS_ORIGIN
value: "https://{{ .Values.ingress.domain }}"
- name: TRUST_PROXIES
value: "1"
# The backend exposes GET /health returning {"status": "ok"}.
value: "https://{{ required "A Domain/Host must be provided" .Values.ingress.domain }}"
# Renamed upstream in 2.x (was TRUST_PROXIES=<count> in 1.x): the
# server sits behind the ingress-nginx reverse proxy.
- name: TRUST_PROXY
value: "true"
{{- if .Values.patchmon.guacd.enabled }}
- name: GUACD_ADDRESS
value: "127.0.0.1:4822"
{{- end }}
# The server exposes GET /health returning {"status": "ok"}. The
# 2.x server rejects requests whose Host header is neither loopback
# nor a CORS_ORIGIN host with 403 — kubelet probes target the pod
# IP, so they must send the public hostname as Host header.
livenessProbe:
httpGet:
path: /health
port: 3001
port: 3000
httpHeaders:
- name: Host
value: {{ .Values.ingress.domain }}
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3001
port: 3000
httpHeaders:
- name: Host
value: {{ .Values.ingress.domain }}
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# The startup probe gates liveness/readiness. The backend waits for
# the database and runs migrations on startup: budget 10s * 30 = 300s.
# The startup probe gates liveness/readiness. The server waits for
# the database and runs migrations on startup; upstream warns that
# the first 2.x boot rebuilds database indexes, which can take
# minutes on slow storage: budget 10s * 60 = 600s.
startupProbe:
httpGet:
path: /health
port: 3001
port: 3000
httpHeaders:
- name: Host
value: {{ .Values.ingress.domain }}
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 60
resources:
{{- include "patchmon.resources" .Values.patchmon.server.resources | nindent 12 }}
volumeMounts:
# The image is stateless, but the Go server needs a writable /tmp
# for scratch files with a read-only root filesystem.
- name: tmp
mountPath: /tmp
{{- if .Values.patchmon.guacd.enabled }}
- name: {{ .Release.Name }}-{{ .Chart.Name }}-guacd
image: {{ .Values.patchmon.guacd.image }}
imagePullPolicy: IfNotPresent
securityContext:
{{- toYaml .Values.patchmon.guacd.securityContext.container | nindent 12 }}
ports:
- containerPort: 4822
protocol: TCP
name: guacd
livenessProbe:
tcpSocket:
port: 4822
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 4822
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
tcpSocket:
port: 4822
periodSeconds: 5
timeoutSeconds: 5
failureThreshold: 30
resources:
{{- include "patchmon.resources" .Values.patchmon.backend.resources | nindent 12 }}
{{- include "patchmon.resources" .Values.patchmon.guacd.resources | nindent 12 }}
volumeMounts:
- mountPath: /app/assets
name: patchmon-volume
subPath: patchmon-assets
- mountPath: /app/agents
name: patchmon-volume
subPath: patchmon-agents
- name: guacd-tmp
mountPath: /tmp
{{- end }}
restartPolicy: Always
volumes:
- name: patchmon-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
- name: tmp
emptyDir: {}
{{- if .Values.patchmon.guacd.enabled }}
- name: guacd-tmp
emptyDir: {}
{{- end }}
+3 -8
View File
@@ -10,20 +10,15 @@ spec:
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
http:
paths:
# The 2.x server serves the frontend and /api/* from one process,
# so a single path covers everything (WebSockets included).
- backend:
service:
name: {{ .Release.Name }}-{{ .Chart.Name }}-frontend-service
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
port:
name: http
path: /
pathType: Prefix
- backend:
service:
name: {{ .Release.Name }}-{{ .Chart.Name }}-backend-service
port:
name: http
path: /api
pathType: Prefix
tls:
- hosts:
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
-13
View File
@@ -1,13 +0,0 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: {{ .Values.patchmon.storage.size }}
{{- if .Values.patchmon.storage.storageClass }}
storageClassName: {{ .Values.patchmon.storage.class }}
{{- end }}
@@ -1,10 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-backend-service
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
spec:
selector:
app: {{ .Release.Name }}-{{ .Chart.Name }}-backend
app: {{ .Release.Name }}-{{ .Chart.Name }}
ports:
- protocol: TCP
port: 80