From d9315322fca043bbd37e808bb0652156d70ca953 Mon Sep 17 00:00:00 2001 From: Niklas Ye Date: Fri, 2 Oct 2026 13:25:47 +0200 Subject: [PATCH] examples/demo: fix two real bugs this exact demo just hit live 1. Renamed every object this demo creates (TerdutServer, Postgres Secret/Deployment/Service) from terdut-demo[-postgres] to terdut-operator-demo[-postgres]. The user applied this kit into the already-live "terdut-demo" namespace -- the real operator exercise from earlier in this repo's own history -- and this demo's own TerdutServer/Postgres objects shared that exact name. The TerdutServer apply was rejected outright (DatabaseSpec's own CEL rule: adding dsn while the live object already had postgresClusterRef violates "exactly one of" and the API server refused it), and the real Postgres Service was never touched (confirmed live: still Zalando's own spilo selector, endpoint still the real StatefulSet pod) -- but the Postgres Secret and Deployment, having no such protection, were created as brand new, extra, crash-looping objects sitting right next to the real ones. Prefixing every name this demo creates means a repeat of this exact mistake no longer collides with anything, documented directly in README.md now. 2. The actual crash itself, independent of (1): capabilities.drop: ["ALL"] (added responding to a PodSecurity "restricted" warning) took CAP_CHOWN/CAP_FOWNER away from the root user postgres:17-alpine's own entrypoint needs to chown/chmod the data directory before it drops privileges itself -- confirmed in a real crashed pod's logs: `chmod: /var/run/postgresql: Operation not permitted`. kubectl apply --dry-run=server, which is as far as this got verified before, only checks admission policy; it was never actually booted. Removed the capability drop and verified for real this time: applied just 00-postgres.yaml alone into a disposable namespace, waited for the pod to go Ready, read its logs ("database system is ready to accept connections"), then deleted that namespace. --- examples/demo/00-postgres.yaml | 37 ++++++++++++++++------------- examples/demo/01-server.yaml | 8 +++---- examples/demo/02-team-platform.yaml | 4 ++-- examples/demo/03-team-payments.yaml | 2 +- examples/demo/README.md | 19 +++++++++++---- examples/demo/fire-alerts.sh | 2 +- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/examples/demo/00-postgres.yaml b/examples/demo/00-postgres.yaml index d95e5fa..c2f8838 100644 --- a/examples/demo/00-postgres.yaml +++ b/examples/demo/00-postgres.yaml @@ -10,7 +10,7 @@ apiVersion: v1 kind: Secret metadata: - name: terdut-demo-postgres + name: terdut-operator-demo-postgres type: Opaque stringData: password: demo-not-a-real-password @@ -18,9 +18,9 @@ stringData: apiVersion: apps/v1 kind: Deployment metadata: - name: terdut-demo-postgres + name: terdut-operator-demo-postgres labels: - app: terdut-demo-postgres + app: terdut-operator-demo-postgres spec: replicas: 1 # Recreate, not RollingUpdate: emptyDir means a new pod starts with an @@ -30,27 +30,32 @@ spec: type: Recreate selector: matchLabels: - app: terdut-demo-postgres + app: terdut-operator-demo-postgres template: metadata: labels: - app: terdut-demo-postgres + app: terdut-operator-demo-postgres spec: containers: - name: postgres image: postgres:17-alpine # Partial, deliberately: the official image's entrypoint needs to - # start as root to chown the data directory before it drops + # start as root to chown/chmod the data directory before it drops # privileges itself (gosu, to the postgres user) -- forcing - # runAsNonRoot here would just refuse to start the container. A - # "restricted" PodSecurity namespace warns on that gap rather - # than blocking (confirmed server-side against this operator's - # own dev cluster), which is an acceptable tradeoff for Postgres - # that exists only to be thrown away with the rest of this demo. + # runAsNonRoot would just refuse to start the container, and + # dropping all capabilities (an earlier version of this file did) + # takes CAP_CHOWN/CAP_FOWNER away from that same root user, which + # is a different way of breaking the identical startup step: + # confirmed the hard way, as `chmod: /var/run/postgresql: + # Operation not permitted` in a real pod's logs, not caught by + # `kubectl apply --dry-run=server` -- that only checks admission + # policy, never whether the container actually boots. A + # "restricted" PodSecurity namespace warns on the remaining gap + # (no runAsNonRoot) rather than blocking, which is an acceptable + # tradeoff for Postgres that exists only to be thrown away with + # the rest of this demo. securityContext: allowPrivilegeEscalation: false - capabilities: - drop: ["ALL"] seccompProfile: type: RuntimeDefault ports: @@ -64,7 +69,7 @@ spec: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: - name: terdut-demo-postgres + name: terdut-operator-demo-postgres key: password volumeMounts: - name: data @@ -81,10 +86,10 @@ spec: apiVersion: v1 kind: Service metadata: - name: terdut-demo-postgres + name: terdut-operator-demo-postgres spec: selector: - app: terdut-demo-postgres + app: terdut-operator-demo-postgres ports: - name: postgres port: 5432 diff --git a/examples/demo/01-server.yaml b/examples/demo/01-server.yaml index c49dada..abcfd02 100644 --- a/examples/demo/01-server.yaml +++ b/examples/demo/01-server.yaml @@ -10,19 +10,19 @@ apiVersion: terdut.ryuvia.com/v1alpha1 kind: TerdutServer metadata: - name: terdut-demo + name: terdut-operator-demo spec: image: repository: git.ryuvia.com/niklas/terdut-server tag: v0.33.2 replicas: 1 networking: - hostname: terdut-demo.example + hostname: terdut-operator-demo.example servicePort: 8080 database: - dsn: "postgres://terdut@terdut-demo-postgres:5432/terdut?sslmode=disable" + dsn: "postgres://terdut@terdut-operator-demo-postgres:5432/terdut?sslmode=disable" passwordSecretRef: - name: terdut-demo-postgres + name: terdut-operator-demo-postgres key: password sweeper: staleAfter: 6h diff --git a/examples/demo/02-team-platform.yaml b/examples/demo/02-team-platform.yaml index 56445fb..39c1063 100644 --- a/examples/demo/02-team-platform.yaml +++ b/examples/demo/02-team-platform.yaml @@ -7,11 +7,11 @@ kind: TerdutTeam metadata: name: terdutteam-platform spec: - # serverRef.namespace omitted: both this and terdut-demo (01-server.yaml) + # serverRef.namespace omitted: both this and terdut-operator-demo (01-server.yaml) # live in whatever namespace you apply this directory into, which is the # common case and needs no allowedTeams consent on the TerdutServer side # (DESIGN.md §4.1, §4.6). serverRef: - name: terdut-demo + name: terdut-operator-demo displayName: Platform # No oidc block: this demo is password-login only (01-server.yaml). diff --git a/examples/demo/03-team-payments.yaml b/examples/demo/03-team-payments.yaml index 207e9b5..21f1b8a 100644 --- a/examples/demo/03-team-payments.yaml +++ b/examples/demo/03-team-payments.yaml @@ -4,5 +4,5 @@ metadata: name: terdutteam-payments spec: serverRef: - name: terdut-demo + name: terdut-operator-demo displayName: Payments diff --git a/examples/demo/README.md b/examples/demo/README.md index c11e782..bfb6dd9 100644 --- a/examples/demo/README.md +++ b/examples/demo/README.md @@ -18,6 +18,17 @@ directory. Throw the whole namespace away when you're done. throwaway resources in. A `kind` cluster is the easy choice. - `kubectl`, `jq`, `curl` on your path. +**Apply this into a namespace of its own.** Every object name in this +directory is prefixed `terdut-operator-demo` specifically so applying it +by mistake into some other namespace that already has unrelated objects +doesn't collide with them -- but that only helps if this directory's own +objects don't collide with *each other* across two applies. Applying it +twice into two different namespaces is fine; applying it a second time +into a namespace that already has something else named `terdut-demo` (a +real install from following `terdut-operator`'s own repo along, say) is +exactly the mistake this prefix exists to avoid, and it only works if you +don't override these names yourself. + ## Apply it ```sh @@ -40,10 +51,10 @@ kubectl get terdutservers,terdutteams,terdutescalationrules,terdutdeadmanswitche The operator's own Deployment template now carries a `wait-for-postgres` init container (same fix as `charts/terdut-server`'s chart as of v0.33.2), -so `terdut-demo`'s pod should come up clean even against this brand-new +so `terdut-operator-demo`'s pod should come up clean even against this brand-new Postgres doing its very first boot — no `CrashLoopBackOff` expected here. -Once `terdut-demo`'s own `Ready` condition is `True`, everything downstream +Once `terdut-operator-demo`'s own `Ready` condition is `True`, everything downstream of it should settle within a reconcile interval or two. ## See the web UI @@ -53,7 +64,7 @@ The operator doesn't create any external exposure yet — `spec.networking.hostname` is accepted but nothing acts on it), so: ```sh -kubectl -n terdut-operator-demo port-forward svc/terdut-demo 8080:8080 +kubectl -n terdut-operator-demo port-forward svc/terdut-operator-demo 8080:8080 ``` and open http://localhost:8080. @@ -72,7 +83,7 @@ kubectl get deploy -A -l control-plane=controller-manager # The Secret holding the operator's own admin token for this TerdutServer # (cross-namespace from terdut-operator-demo, per DESIGN.md §7): -secretname=$(kubectl -n terdut-operator-demo get terdutserver terdut-demo \ +secretname=$(kubectl -n terdut-operator-demo get terdutserver terdut-operator-demo \ -o jsonpath='{.status.credentialsSecretRef.name}') token=$(kubectl -n get secret "$secretname" \ -o jsonpath='{.data.token}' | base64 -d) diff --git a/examples/demo/fire-alerts.sh b/examples/demo/fire-alerts.sh index 6be5e1f..b8a8ac9 100755 --- a/examples/demo/fire-alerts.sh +++ b/examples/demo/fire-alerts.sh @@ -20,7 +20,7 @@ # # Prerequisites: kubectl context pointed at the demo namespace, jq, curl, # and (in another terminal) a running: -# kubectl port-forward svc/terdut-demo 8080:8080 +# kubectl port-forward svc/terdut-operator-demo 8080:8080 set -euo pipefail NAMESPACE="${NAMESPACE:-}"