examples/demo: fix two real bugs this exact demo just hit live
CI / chart (push) Successful in 1s
CI / security (push) Successful in 3m24s
CI / test (push) Successful in 10m45s

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.
This commit is contained in:
Niklas Ye
2026-10-02 13:25:47 +02:00
parent 5b45cf72e1
commit d9315322fc
6 changed files with 44 additions and 28 deletions
+21 -16
View File
@@ -10,7 +10,7 @@
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: terdut-demo-postgres name: terdut-operator-demo-postgres
type: Opaque type: Opaque
stringData: stringData:
password: demo-not-a-real-password password: demo-not-a-real-password
@@ -18,9 +18,9 @@ stringData:
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: terdut-demo-postgres name: terdut-operator-demo-postgres
labels: labels:
app: terdut-demo-postgres app: terdut-operator-demo-postgres
spec: spec:
replicas: 1 replicas: 1
# Recreate, not RollingUpdate: emptyDir means a new pod starts with an # Recreate, not RollingUpdate: emptyDir means a new pod starts with an
@@ -30,27 +30,32 @@ spec:
type: Recreate type: Recreate
selector: selector:
matchLabels: matchLabels:
app: terdut-demo-postgres app: terdut-operator-demo-postgres
template: template:
metadata: metadata:
labels: labels:
app: terdut-demo-postgres app: terdut-operator-demo-postgres
spec: spec:
containers: containers:
- name: postgres - name: postgres
image: postgres:17-alpine image: postgres:17-alpine
# Partial, deliberately: the official image's entrypoint needs to # 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 # privileges itself (gosu, to the postgres user) -- forcing
# runAsNonRoot here would just refuse to start the container. A # runAsNonRoot would just refuse to start the container, and
# "restricted" PodSecurity namespace warns on that gap rather # dropping all capabilities (an earlier version of this file did)
# than blocking (confirmed server-side against this operator's # takes CAP_CHOWN/CAP_FOWNER away from that same root user, which
# own dev cluster), which is an acceptable tradeoff for Postgres # is a different way of breaking the identical startup step:
# that exists only to be thrown away with the rest of this demo. # 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: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
ports: ports:
@@ -64,7 +69,7 @@ spec:
- name: POSTGRES_PASSWORD - name: POSTGRES_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: terdut-demo-postgres name: terdut-operator-demo-postgres
key: password key: password
volumeMounts: volumeMounts:
- name: data - name: data
@@ -81,10 +86,10 @@ spec:
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: terdut-demo-postgres name: terdut-operator-demo-postgres
spec: spec:
selector: selector:
app: terdut-demo-postgres app: terdut-operator-demo-postgres
ports: ports:
- name: postgres - name: postgres
port: 5432 port: 5432
+4 -4
View File
@@ -10,19 +10,19 @@
apiVersion: terdut.ryuvia.com/v1alpha1 apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutServer kind: TerdutServer
metadata: metadata:
name: terdut-demo name: terdut-operator-demo
spec: spec:
image: image:
repository: git.ryuvia.com/niklas/terdut-server repository: git.ryuvia.com/niklas/terdut-server
tag: v0.33.2 tag: v0.33.2
replicas: 1 replicas: 1
networking: networking:
hostname: terdut-demo.example hostname: terdut-operator-demo.example
servicePort: 8080 servicePort: 8080
database: database:
dsn: "postgres://terdut@terdut-demo-postgres:5432/terdut?sslmode=disable" dsn: "postgres://terdut@terdut-operator-demo-postgres:5432/terdut?sslmode=disable"
passwordSecretRef: passwordSecretRef:
name: terdut-demo-postgres name: terdut-operator-demo-postgres
key: password key: password
sweeper: sweeper:
staleAfter: 6h staleAfter: 6h
+2 -2
View File
@@ -7,11 +7,11 @@ kind: TerdutTeam
metadata: metadata:
name: terdutteam-platform name: terdutteam-platform
spec: 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 # live in whatever namespace you apply this directory into, which is the
# common case and needs no allowedTeams consent on the TerdutServer side # common case and needs no allowedTeams consent on the TerdutServer side
# (DESIGN.md §4.1, §4.6). # (DESIGN.md §4.1, §4.6).
serverRef: serverRef:
name: terdut-demo name: terdut-operator-demo
displayName: Platform displayName: Platform
# No oidc block: this demo is password-login only (01-server.yaml). # No oidc block: this demo is password-login only (01-server.yaml).
+1 -1
View File
@@ -4,5 +4,5 @@ metadata:
name: terdutteam-payments name: terdutteam-payments
spec: spec:
serverRef: serverRef:
name: terdut-demo name: terdut-operator-demo
displayName: Payments displayName: Payments
+15 -4
View File
@@ -18,6 +18,17 @@ directory. Throw the whole namespace away when you're done.
throwaway resources in. A `kind` cluster is the easy choice. throwaway resources in. A `kind` cluster is the easy choice.
- `kubectl`, `jq`, `curl` on your path. - `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 ## Apply it
```sh ```sh
@@ -40,10 +51,10 @@ kubectl get terdutservers,terdutteams,terdutescalationrules,terdutdeadmanswitche
The operator's own Deployment template now carries a `wait-for-postgres` 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), 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. 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. of it should settle within a reconcile interval or two.
## See the web UI ## 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: — `spec.networking.hostname` is accepted but nothing acts on it), so:
```sh ```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. 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 # The Secret holding the operator's own admin token for this TerdutServer
# (cross-namespace from terdut-operator-demo, per DESIGN.md §7): # (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}') -o jsonpath='{.status.credentialsSecretRef.name}')
token=$(kubectl -n <operator-namespace-from-above> get secret "$secretname" \ token=$(kubectl -n <operator-namespace-from-above> get secret "$secretname" \
-o jsonpath='{.data.token}' | base64 -d) -o jsonpath='{.data.token}' | base64 -d)
+1 -1
View File
@@ -20,7 +20,7 @@
# #
# Prerequisites: kubectl context pointed at the demo namespace, jq, curl, # Prerequisites: kubectl context pointed at the demo namespace, jq, curl,
# and (in another terminal) a running: # 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 set -euo pipefail
NAMESPACE="${NAMESPACE:-}" NAMESPACE="${NAMESPACE:-}"