From 95a7c6b5b000e362dceb027b03e0e76626c7e8d3 Mon Sep 17 00:00:00 2001 From: Nikolai Emil Damm Date: Fri, 29 May 2026 16:38:28 +0200 Subject: [PATCH] feat(cluster-policies): require workloads to spread across nodes The spread-pods ClusterPolicy (synced from upstream kyverno/policies and patched in the cluster-policies base) previously mutated workloads with a soft topology spread constraint (whenUnsatisfiable: ScheduleAnyway). Harden it to DoNotSchedule so spreading every Deployment/StatefulSet across nodes (topologyKey: kubernetes.io/hostname, maxSkew: 1) is strictly required rather than best-effort. Also add matchLabelKeys: [pod-template-hash] so per-node skew is computed per ReplicaSet revision; without it, DoNotSchedule + maxSkew:1 deadlocks rolling updates because the new revision's surge pod counts against the old revision. Requires k8s >=1.27 (GA in 1.34); the cluster runs k8s ~1.34 via Talos v1.12.4. StatefulSets have no pod-template-hash, so the key is ignored there and falls back to the label selector. Change is confined to the kustomize patch; the synced upstream sample is untouched so it keeps receiving updates. Validated with 'ksail workload validate' (local + prod, 256 files each) and kubectl kustomize builds. Co-Authored-By: Claude Opus 4.8 --- .../cluster-policies/kustomization.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/k8s/bases/infrastructure/cluster-policies/kustomization.yaml b/k8s/bases/infrastructure/cluster-policies/kustomization.yaml index 488ec818d..d69e420f9 100644 --- a/k8s/bases/infrastructure/cluster-policies/kustomization.yaml +++ b/k8s/bases/infrastructure/cluster-policies/kustomization.yaml @@ -95,7 +95,7 @@ patches: - op: replace path: /spec/rules/0/mutate/patchStrategicMerge/spec/template/spec/+(affinity)/+(podAntiAffinity)/+(preferredDuringSchedulingIgnoredDuringExecution)/0/podAffinityTerm/labelSelector/matchExpressions/0/values/0 value: "{{request.object.spec.template.metadata.labels.\"app.kubernetes.io/name\"}}" - # --- Topology spread: remove label gate, add StatefulSet, hostname key, ScheduleAnyway, exclude kube-system --- + # --- Topology spread: remove label gate, add StatefulSet, hostname key, DoNotSchedule, exclude kube-system --- - target: kind: ClusterPolicy name: spread-pods @@ -125,7 +125,16 @@ patches: value: "kubernetes.io/hostname" - op: replace path: /spec/rules/0/mutate/patchStrategicMerge/spec/template/spec/+(topologySpreadConstraints)/0/whenUnsatisfiable - value: "ScheduleAnyway" + value: "DoNotSchedule" + # Scope skew per ReplicaSet revision so rolling updates don't deadlock under + # DoNotSchedule (surge pods of the new revision would otherwise count against + # the old revision's skew). Requires k8s >=1.27 (GA in 1.34). For StatefulSets + # the pod-template-hash key is absent, so k8s ignores it and falls back to the + # labelSelector — fine, since StatefulSet updates roll one pod at a time. + - op: add + path: /spec/rules/0/mutate/patchStrategicMerge/spec/template/spec/+(topologySpreadConstraints)/0/matchLabelKeys + value: + - pod-template-hash - op: replace path: /spec/rules/0/mutate/patchStrategicMerge/spec/template/spec/+(topologySpreadConstraints)/0/labelSelector value: