Skip to content

Add spilo_run_as_non_root option to keep pod spec in sync with webhook-injected security context - #3209

Open
sled30 wants to merge 1 commit into
zalando:masterfrom
sled30:feat/spilo-run-as-non-root
Open

sled30 wants to merge 1 commit into
zalando:masterfrom
sled30:feat/spilo-run-as-non-root

Conversation

@sled30

@sled30 sled30 commented Oct 30, 2026 •

Copy link
Copy Markdown

Motivation

Mutating admission webhooks (e.g. Kyverno Pod Security policies) may inject
runAsNonRoot into the pod and the Spilo container security context of the
operator-generated StatefulSet. The operator cannot emit this field: the
configuration offers spilo_runasuser / spilo_runasgroup /
spilo_fsgroup, spilo_privileged and spilo_allow_privilege_escalation -
but no runAsNonRoot knob. Therefore the desired pod template never matches
the live state, and on every resync the operator detects "pod changes" and
performs a full rolling update with a Patroni switchover:

reason: new statefulset containers's postgres (index 0) security context does not match the current one
reason: new statefulset's pod template security context in spec does not match the current one
mark rolling update annotation for banking-pg-0: reason pod changes

Newly created pods are mutated by the webhook again, so the cluster never
converges: in our environment the whole PostgreSQL cluster restarted with a
leader switchover on every resync period (30 minutes, ~48 restarts/day per
cluster). This is the same class of problem as PR #3095 (webhook-injected
sysctls) and issue #1638 (GKE Autopilot mutating resources/annotations).

Fixes #3210
Related: #3095, #1638

Change

Add a spilo_run_as_non_root operator option (OperatorConfiguration,
kubernetes section) and a spiloRunAsNonRoot cluster manifest field that
overrides the global option. When set, the operator includes runAsNonRoot
in both the pod security context (generatePodTemplate) and the Spilo
container security context (generateContainer; the logical backup container
follows the existing spilo_privileged / spilo_allow_privilege_escalation
pattern). Unset by default: runAsNonRoot is omitted from the generated pod
spec, preserving the current behavior.

Files

  • pkg/util/config/config.go — SpiloRunAsNonRoot *bool (spilo_run_as_non_root)
  • pkg/apis/acid.zalan.do/v1/operator_configuration_type.go — matching CRD config type
  • pkg/apis/acid.zalan.do/v1/postgresql_type.go — spiloRunAsNonRoot in PostgresSpec
  • pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go — deepcopy for the new field
  • manifests/postgresql.crd.yaml, manifests/operatorconfiguration.crd.yaml,
    pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml,
    pkg/apis/acid.zalan.do/v1/operatorconfiguration.crd.yaml,
    charts/postgres-operator/crds/* — regenerated CRDs
  • pkg/cluster/k8sres.go — emit RunAsNonRoot at pod and container level
  • pkg/cluster/k8sres_test.go — TestSpiloRunAsNonRoot
  • manifests/postgresql-operator-default-configuration.yaml,
    docs/reference/operator_parameters.md,
    docs/reference/cluster_manifest.md — examples and reference docs

Test

go build ./..., go vet ./pkg/cluster/ ./pkg/util/config/ and the full
./pkg/cluster/, ./pkg/util/... suites pass. New focused test:

go test ./pkg/cluster/ -run TestSpiloRunAsNonRoot -v -count=1
--- PASS: TestSpiloRunAsNonRoot (0.01s)
    --- PASS: TestSpiloRunAsNonRoot/unset_leaves_runAsNonRoot_out_of_the_pod_spec
    --- PASS: TestSpiloRunAsNonRoot/operator_option_sets_runAsNonRoot_on_pod_and_container_level
    --- PASS: TestSpiloRunAsNonRoot/cluster_manifest_overrides_the_operator_option

Scope / operations notes

  • the option works in both configuration modes (ConfigMap and
    OperatorConfiguration CRD), like the other scalar spilo_* options;
  • clusters already running with webhook-injected fields get one final
    rolling update after enabling the option (the generated spec changes), then
    the loop stops;
  • the option covers runAsNonRoot, which is the field our enforcing policy
    injects; other webhook-injected fields (e.g. sysctls, see feat: add pod_sysctls operator config option #3095) would
    need analogous options.

…k-injected security context

Mutating admission webhooks (e.g. Kyverno Pod Security policies) may inject
`runAsNonRoot` into the pod and the Spilo container security context of the
operator-generated StatefulSet. The operator cannot emit this field, so the
desired pod template never matches the live state: on every resync the
operator detects "pod changes" and triggers a full rolling update with a
switchover, creating a restart loop for the whole PostgreSQL cluster.

Add a `spilo_run_as_non_root` operator option and a `spiloRunAsNonRoot`
cluster manifest field. When set, the operator includes `runAsNonRoot` in
both the pod security context and the Spilo container security context, so
the generated spec matches the webhook-enforced state and the rolling update
loop stops. The manifest field overrides the global option. Unset by default:
`runAsNonRoot` is omitted from the generated pod spec, preserving the current
behavior.

CRDs, deepcopy code and documentation are regenerated accordingly.
@yyvess

yyvess commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Good lock, I wait from 2022 to fix it on my PR :-(
#2082

yyvess pushed a commit to yyvess/postgres-operator that referenced this pull request Nov 7, 2026
Align the option name with the one proposed in zalando#3209.

runAsNonRoot is now also filled into the Spilo, sidecar, init and logical
backup containers, unless they define it themselves or run privileged.
Admission webhooks enforcing runAsNonRoot (e.g. Kyverno) inject it on
container level too; with only the pod field set, the generated
StatefulSet kept differing from the mutated one and the operator rolled
the cluster on every sync (zalando#3210).
@yyvess

yyvess commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

I try to address all security option in one new clean PR => #3224
Let see if someone have change to look on

@sled30

sled30 commented Nov 9, 2026

Copy link
Copy Markdown
Author

I try to address all security option in one new clean PR => #3224 Let see if someone have change to look on

Thanks a lot! For now, I’ll keep the PR open-just in case something doesn’t look right.

@FxKu FxKu added this to the 2.1.0 milestone Nov 10, 2026
@FxKu FxKu added the minor label Nov 10, 2026
@FxKu FxKu moved this to Waiting for review in Postgres Operator Nov 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Waiting for review

Development

Successfully merging this pull request may close these issues.

Rolling update loop on every resync when a mutating admission webhook injects security context fields (e.g. runAsNonRoot)

3 participants