From e4ae84dfb15b1831e33d2e4ff6975e0f1866d569 Mon Sep 17 00:00:00 2001 From: Yama <10947332+yama6a@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:26:46 +0000 Subject: [PATCH 1/2] Ignore the old revision in anti-affinity so rollouts keep their node --- argo_apps/platform/charts/00_cilium/values.yaml | 2 ++ argo_apps/platform/charts/01_argocd/values.yaml | 7 ++++++- .../platform/charts/01_envoy_gateway/values.yaml | 2 ++ .../charts/01_victoria_metrics_operator/values.yaml | 3 ++- argo_apps/platform/charts/02_cert_manager/values.yaml | 5 ++++- .../platform/charts/02_cnpg_operator/values.yaml | 1 + argo_apps/platform/charts/02_longhorn/values.yaml | 11 +++++++++++ .../platform/charts/02_metrics_server/values.yaml | 3 ++- .../platform/charts/03_redis_operator/values.yaml | 3 ++- argo_apps/platform/charts/05_grafana/values.yaml | 1 + .../charts/05_victoria_metrics_k8s_stack/values.yaml | 1 + .../charts/sample_audit_logger/templates/app.yaml | 1 + .../charts/sample_user_manager/templates/app.yaml | 1 + .../charts/sample_user_signup/templates/app.yaml | 1 + 14 files changed, 37 insertions(+), 5 deletions(-) diff --git a/argo_apps/platform/charts/00_cilium/values.yaml b/argo_apps/platform/charts/00_cilium/values.yaml index 8a4770d1..68f69bcd 100644 --- a/argo_apps/platform/charts/00_cilium/values.yaml +++ b/argo_apps/platform/charts/00_cilium/values.yaml @@ -81,6 +81,7 @@ cilium: labelSelector: matchLabels: k8s-app: hubble-relay + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname ui: enabled: true @@ -100,6 +101,7 @@ cilium: labelSelector: matchLabels: k8s-app: hubble-ui + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname tls: auto: diff --git a/argo_apps/platform/charts/01_argocd/values.yaml b/argo_apps/platform/charts/01_argocd/values.yaml index f6c6354c..07c0b55f 100644 --- a/argo_apps/platform/charts/01_argocd/values.yaml +++ b/argo_apps/platform/charts/01_argocd/values.yaml @@ -13,11 +13,16 @@ argo-cd: logging: level: warn # maxSkew 1 + DoNotSchedule over 3 nodes puts the 2-replica components on distinct nodes. The chart fills - # in each component's labelSelector; singletons satisfy it trivially. + # in each component's labelSelector. topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one + # The spread constraint above already separates replicas. The chart's soft anti-affinity preset cannot take + # matchLabelKeys, so during a rollout it would push every singleton off its node. + affinity: + podAntiAffinity: none dex: enabled: false notifications: diff --git a/argo_apps/platform/charts/01_envoy_gateway/values.yaml b/argo_apps/platform/charts/01_envoy_gateway/values.yaml index de365820..16ee2e8c 100644 --- a/argo_apps/platform/charts/01_envoy_gateway/values.yaml +++ b/argo_apps/platform/charts/01_envoy_gateway/values.yaml @@ -26,6 +26,7 @@ envoyProxy: app.kubernetes.io/name: envoy app.kubernetes.io/component: proxy app.kubernetes.io/managed-by: envoy-gateway + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # Envoy's default cluster stats already carry envoy_cluster_name="httproute///rule/N", so the @@ -59,6 +60,7 @@ gateway-helm: labelSelector: matchLabels: control-plane: envoy-gateway + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # Injects topology.kubernetes.io/zone for zone-aware routing. Bare Pi nodes have no zone label and we use no # zoneAware traffic policies, so it is a webhook round-trip plus cert plumbing for nothing. diff --git a/argo_apps/platform/charts/01_victoria_metrics_operator/values.yaml b/argo_apps/platform/charts/01_victoria_metrics_operator/values.yaml index 87692c65..4a7bc47e 100644 --- a/argo_apps/platform/charts/01_victoria_metrics_operator/values.yaml +++ b/argo_apps/platform/charts/01_victoria_metrics_operator/values.yaml @@ -38,7 +38,7 @@ victoria-metrics-operator: vm: true # Soft + hostname: bare Pi nodes have no zone label, and soft means raising replicaCount never wedges a pod - # Pending on 3 nodes. No-op at the default single replica. + # Pending on 3 nodes. affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -47,4 +47,5 @@ victoria-metrics-operator: labelSelector: matchLabels: app.kubernetes.io/name: victoria-metrics-operator + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname diff --git a/argo_apps/platform/charts/02_cert_manager/values.yaml b/argo_apps/platform/charts/02_cert_manager/values.yaml index f8231733..98791bb9 100644 --- a/argo_apps/platform/charts/02_cert_manager/values.yaml +++ b/argo_apps/platform/charts/02_cert_manager/values.yaml @@ -21,7 +21,7 @@ cert-manager: requests: { cpu: 10m, memory: 49Mi } limits: { memory: 95Mi } # Soft + hostname on all three: bare Pi nodes carry no zone label, and soft means raising replicaCount - # never wedges a pod Pending on a 3-node cluster. No-op at the default single replica. + # never wedges a pod Pending on a 3-node cluster. affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -31,6 +31,7 @@ cert-manager: matchLabels: app.kubernetes.io/name: cert-manager app.kubernetes.io/component: controller + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname webhook: resources: @@ -45,6 +46,7 @@ cert-manager: matchLabels: app.kubernetes.io/name: webhook app.kubernetes.io/component: webhook + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname cainjector: resources: @@ -59,6 +61,7 @@ cert-manager: matchLabels: app.kubernetes.io/name: cainjector app.kubernetes.io/component: cainjector + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # HTTP-01 through Gateway API. Not a feature gate, it only exists in this controller config file. diff --git a/argo_apps/platform/charts/02_cnpg_operator/values.yaml b/argo_apps/platform/charts/02_cnpg_operator/values.yaml index 4242e04d..c2fa2559 100644 --- a/argo_apps/platform/charts/02_cnpg_operator/values.yaml +++ b/argo_apps/platform/charts/02_cnpg_operator/values.yaml @@ -44,4 +44,5 @@ cloudnative-pg: labelSelector: matchLabels: app.kubernetes.io/name: cloudnative-pg + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname diff --git a/argo_apps/platform/charts/02_longhorn/values.yaml b/argo_apps/platform/charts/02_longhorn/values.yaml index 0b3aab72..cfc78621 100644 --- a/argo_apps/platform/charts/02_longhorn/values.yaml +++ b/argo_apps/platform/charts/02_longhorn/values.yaml @@ -75,3 +75,14 @@ longhorn: limits: {memory: 472Mi} # it bursts well past the request, so a limit close to it just OOMKills longhornUI: replicas: 1 # a read-mostly dashboard; the chart's default 2 is wasted footprint here + # The chart's default rule, plus matchLabelKeys. + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - {key: app, operator: In, values: [longhorn-ui]} + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one + topologyKey: kubernetes.io/hostname diff --git a/argo_apps/platform/charts/02_metrics_server/values.yaml b/argo_apps/platform/charts/02_metrics_server/values.yaml index c0df7965..1b104417 100644 --- a/argo_apps/platform/charts/02_metrics_server/values.yaml +++ b/argo_apps/platform/charts/02_metrics_server/values.yaml @@ -18,7 +18,7 @@ metrics-server: memory: 200Mi # Soft + hostname: bare Pi nodes have no zone label, and soft means raising replicas never wedges a pod - # Pending on 3 nodes. No-op at the single replica above. + # Pending on 3 nodes. affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -27,6 +27,7 @@ metrics-server: labelSelector: matchLabels: app.kubernetes.io/name: metrics-server + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname metrics: diff --git a/argo_apps/platform/charts/03_redis_operator/values.yaml b/argo_apps/platform/charts/03_redis_operator/values.yaml index 6b403661..d05d07d4 100644 --- a/argo_apps/platform/charts/03_redis_operator/values.yaml +++ b/argo_apps/platform/charts/03_redis_operator/values.yaml @@ -32,7 +32,7 @@ redis-operator: memory: 92Mi # Soft + hostname: bare Pi nodes have no zone label, and soft means raising replicas never wedges a pod - # Pending on 3 nodes. No-op at the chart's default single replica. + # Pending on 3 nodes. affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -41,6 +41,7 @@ redis-operator: labelSelector: matchLabels: app.kubernetes.io/name: redis-operator + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # No StorageClasses here. Every redis-instance PVC uses longhorn-r2-ephemeral, owned by 02_longhorn. diff --git a/argo_apps/platform/charts/05_grafana/values.yaml b/argo_apps/platform/charts/05_grafana/values.yaml index aa1f9b1f..3378d549 100644 --- a/argo_apps/platform/charts/05_grafana/values.yaml +++ b/argo_apps/platform/charts/05_grafana/values.yaml @@ -128,6 +128,7 @@ grafana: labelSelector: matchLabels: app.kubernetes.io/name: grafana + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # Restarts grafana after a sync that changed files/alerts/, which is what removes a rule that was deleted diff --git a/argo_apps/platform/charts/05_victoria_metrics_k8s_stack/values.yaml b/argo_apps/platform/charts/05_victoria_metrics_k8s_stack/values.yaml index 1f3a56b5..14e9c316 100644 --- a/argo_apps/platform/charts/05_victoria_metrics_k8s_stack/values.yaml +++ b/argo_apps/platform/charts/05_victoria_metrics_k8s_stack/values.yaml @@ -260,6 +260,7 @@ victoria-metrics-k8s-stack: labelSelector: matchLabels: app.kubernetes.io/name: kube-state-metrics + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname kubelet: diff --git a/argo_apps/workloads/charts/sample_audit_logger/templates/app.yaml b/argo_apps/workloads/charts/sample_audit_logger/templates/app.yaml index bfde513a..f77b9b06 100644 --- a/argo_apps/workloads/charts/sample_audit_logger/templates/app.yaml +++ b/argo_apps/workloads/charts/sample_audit_logger/templates/app.yaml @@ -31,6 +31,7 @@ spec: labelSelector: matchLabels: app: {{ .Values.app.name }} + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # PodSecurity `restricted` baseline, the same block on every first-party pod here. 65532 matches the # distroless nonroot uid the sample-app image declares. diff --git a/argo_apps/workloads/charts/sample_user_manager/templates/app.yaml b/argo_apps/workloads/charts/sample_user_manager/templates/app.yaml index ac2abd12..5b0c5894 100644 --- a/argo_apps/workloads/charts/sample_user_manager/templates/app.yaml +++ b/argo_apps/workloads/charts/sample_user_manager/templates/app.yaml @@ -31,6 +31,7 @@ spec: labelSelector: matchLabels: app: {{ .Values.app.name }} + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # PodSecurity `restricted` baseline, the same block on every first-party pod here. 65532 matches the # distroless nonroot uid the sample-app image declares. diff --git a/argo_apps/workloads/charts/sample_user_signup/templates/app.yaml b/argo_apps/workloads/charts/sample_user_signup/templates/app.yaml index bcff2572..31e31592 100644 --- a/argo_apps/workloads/charts/sample_user_signup/templates/app.yaml +++ b/argo_apps/workloads/charts/sample_user_signup/templates/app.yaml @@ -31,6 +31,7 @@ spec: labelSelector: matchLabels: app: {{ .Values.app.name }} + matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname # PodSecurity `restricted` baseline, the same block on every first-party pod here. 65532 matches the # distroless nonroot uid the sample-app image declares. From e7c1e69766ea6df059857cc1919c17804fc98d1e Mon Sep 17 00:00:00 2001 From: Yama <10947332+yama6a@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:26:49 +0000 Subject: [PATCH 2/2] Match the redis-operator pod label in its anti-affinity selector --- argo_apps/platform/charts/03_redis_operator/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/argo_apps/platform/charts/03_redis_operator/values.yaml b/argo_apps/platform/charts/03_redis_operator/values.yaml index d05d07d4..1fbae84f 100644 --- a/argo_apps/platform/charts/03_redis_operator/values.yaml +++ b/argo_apps/platform/charts/03_redis_operator/values.yaml @@ -40,7 +40,7 @@ redis-operator: podAffinityTerm: labelSelector: matchLabels: - app.kubernetes.io/name: redis-operator + name: redis-operator # the chart labels its pod only with `name` matchLabelKeys: [pod-template-hash] # compare within one revision, so a rollout's old pod never repels the new one topologyKey: kubernetes.io/hostname