From cea4f5d9781f0cb3d0c07c2eb81c7bd5a57f1962 Mon Sep 17 00:00:00 2001 From: Shereen Haj Date: Wed, 20 May 2026 13:08:01 +0300 Subject: [PATCH 1/2] Revert "ctrl: sched: add PodAntiAffinity to deployment" PodAntiAffinity is a legacy way to handle HA scenarios and in project state it caused deadlocks and regressions. This reverts commit b4e7fd1ab85aea2e8a57383f5e5882361eaba20e. Signed-off-by: Shereen Haj --- .../numaresourcesscheduler_controller.go | 4 - .../numaresourcesscheduler_controller_test.go | 188 ------------------ pkg/objectupdate/sched/sched.go | 50 ----- pkg/objectupdate/sched/sched_test.go | 123 ------------ 4 files changed, 365 deletions(-) diff --git a/internal/controller/numaresourcesscheduler_controller.go b/internal/controller/numaresourcesscheduler_controller.go index 56e352b161..c43aeca951 100644 --- a/internal/controller/numaresourcesscheduler_controller.go +++ b/internal/controller/numaresourcesscheduler_controller.go @@ -361,10 +361,6 @@ func (r *NUMAResourcesSchedulerReconciler) syncNUMASchedulerResources(ctx contex return nropv1.NUMAResourcesSchedulerStatus{}, err } - if err := schedupdate.DeploymentAffinityWithStrategy(r.SchedulerManifests.Deployment, instance.Spec); err != nil { - return nropv1.NUMAResourcesSchedulerStatus{}, err - } - k8swgrbacupdate.RoleForLeaderElection(r.SchedulerManifests.Role, r.Namespace, nrosched.LeaderElectionResourceName) k8swgrbacupdate.RoleBinding(r.SchedulerManifests.RoleBinding, r.SchedulerManifests.ServiceAccount.Name, r.Namespace) diff --git a/internal/controller/numaresourcesscheduler_controller_test.go b/internal/controller/numaresourcesscheduler_controller_test.go index 0fc708a1cb..9062a53d8f 100644 --- a/internal/controller/numaresourcesscheduler_controller_test.go +++ b/internal/controller/numaresourcesscheduler_controller_test.go @@ -33,7 +33,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/scheme" "k8s.io/klog/v2" "k8s.io/utils/ptr" @@ -1032,193 +1031,6 @@ var _ = Describe("Test NUMAResourcesScheduler Reconcile", func() { Expect(dp.Spec.Template.Spec.Containers[0].Args).To(ContainElement("--tls-cipher-suites=" + updatedSettings.CipherSuites)) }) }) - - Context("when setting the scheduler PodAntiAffinity", func() { - var ( - expectedPodAntiAff *corev1.PodAntiAffinity - expectedStrategy appsv1.DeploymentStrategy - ) - - BeforeEach(func() { - expectedPodAntiAff = &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "secondary-scheduler", - }, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - } - expectedStrategy = appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: ptr.To(intstr.FromInt(1)), - MaxSurge: ptr.To(intstr.FromInt(0)), - }, - } - }) - - Context("set different replicas counts", func() { - type testCase struct { - replicasCount *int32 - expectPodAntiAffinity bool - } - - DescribeTable("should set PodAntiAffinity for scheduler Deployment according to replicas count", func(ctx context.Context, tc testCase) { - nrs := testobjs.NewNUMAResourcesScheduler("numaresourcesscheduler", "some/url:latest", testSchedulerName, 11*time.Second) - nrs.Spec.Replicas = tc.replicasCount - initObjects := []runtime.Object{nrs} - initObjects = append(initObjects, fakeNodes(3, 0)...) - reconciler, err := NewFakeNUMAResourcesSchedulerReconciler(initObjects...) - Expect(err).ToNot(HaveOccurred()) - // Baseline node affinity comes from the scheduler deployment manifest; reconcile must preserve it. - Expect(reconciler.SchedulerManifests.Deployment.Spec.Template.Spec.Affinity).ToNot(BeNil()) - expectedNodeAff := reconciler.SchedulerManifests.Deployment.Spec.Template.Spec.Affinity.NodeAffinity.DeepCopy() - - key := client.ObjectKeyFromObject(nrs) - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - dp := &appsv1.Deployment{} - dpKey := client.ObjectKey{Namespace: testNamespace, Name: "secondary-scheduler"} - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity := dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.NodeAffinity).To(Equal(expectedNodeAff)) - - if !tc.expectPodAntiAffinity { - Expect(affinity.PodAntiAffinity).To(BeNil()) - Expect(dp.Spec.Strategy).To(Equal(appsv1.DeploymentStrategy{})) - - // no changes expected on second reconcile with same input - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity = dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.NodeAffinity).To(Equal(expectedNodeAff)) - Expect(affinity.PodAntiAffinity).To(BeNil()) - Expect(dp.Spec.Strategy).To(Equal(appsv1.DeploymentStrategy{})) - return - } - - Expect(affinity.PodAntiAffinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(Equal(expectedPodAntiAff)) - Expect(dp.Spec.Strategy).To(Equal(expectedStrategy)) - - // no changes expected on second reconcile with same input - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity = dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.NodeAffinity).To(Equal(expectedNodeAff)) - Expect(affinity.PodAntiAffinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(Equal(expectedPodAntiAff)) - Expect(dp.Spec.Strategy).To(Equal(expectedStrategy)) - }, - Entry("when replicas are not set, expected podAntiAffinity", testCase{expectPodAntiAffinity: true}), - Entry("when replicas are set and zero, no podAntiAffinity is set", testCase{replicasCount: ptr.To(int32(0)), expectPodAntiAffinity: false}), - Entry("when replicas are set and non-zero, no podAntiAffinity is set", testCase{replicasCount: ptr.To(int32(1)), expectPodAntiAffinity: false}), - ) - }) - - Context("switch between replicas counts", func() { - It("should reset podAntiAffinity and strategy - transition from autodetect to explicit replicas count", func(ctx context.Context) { - nrs := testobjs.NewNUMAResourcesScheduler("numaresourcesscheduler", "some/url:latest", testSchedulerName, 11*time.Second) - nrs.Spec.Replicas = nil // autodetect - initObjects := []runtime.Object{nrs} - initObjects = append(initObjects, fakeNodes(3, 0)...) - reconciler, err := NewFakeNUMAResourcesSchedulerReconciler(initObjects...) - Expect(err).ToNot(HaveOccurred()) - - key := client.ObjectKeyFromObject(nrs) - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - dp := &appsv1.Deployment{} - dpKey := client.ObjectKey{Namespace: testNamespace, Name: "secondary-scheduler"} - - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity := dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(Equal(expectedPodAntiAff)) - Expect(dp.Spec.Strategy).To(Equal(expectedStrategy)) - - // reconcile with non-nil replicas count - Eventually(func(g Gomega) { - g.Expect(reconciler.Client.Get(ctx, key, nrs)).ToNot(HaveOccurred()) - nrs.Spec.Replicas = ptr.To(int32(2)) - g.Expect(reconciler.Client.Update(ctx, nrs)).ToNot(HaveOccurred()) - }).WithPolling(5 * time.Second).WithTimeout(30 * time.Second).Should(Succeed()) - - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity = dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(BeNil()) - Expect(dp.Spec.Strategy).To(Equal(appsv1.DeploymentStrategy{})) - - // reconcile with a different non-nil replicas count should keep the same result - Eventually(func(g Gomega) { - g.Expect(reconciler.Client.Get(ctx, key, nrs)).ToNot(HaveOccurred()) - nrs.Spec.Replicas = ptr.To(int32(0)) - g.Expect(reconciler.Client.Update(ctx, nrs)).ToNot(HaveOccurred()) - }).WithPolling(5 * time.Second).WithTimeout(30 * time.Second).Should(Succeed()) - - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity = dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(BeNil()) - Expect(dp.Spec.Strategy).To(Equal(appsv1.DeploymentStrategy{})) - - }) - - It("should reset podAntiAffinity and strategy - transition from explicit replicas count to autodetect", func(ctx context.Context) { - nrs := testobjs.NewNUMAResourcesScheduler("numaresourcesscheduler", "some/url:latest", testSchedulerName, 11*time.Second) - nrs.Spec.Replicas = ptr.To(int32(3)) - initObjects := []runtime.Object{nrs} - initObjects = append(initObjects, fakeNodes(3, 0)...) - reconciler, err := NewFakeNUMAResourcesSchedulerReconciler(initObjects...) - Expect(err).ToNot(HaveOccurred()) - - key := client.ObjectKeyFromObject(nrs) - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - dp := &appsv1.Deployment{} - dpKey := client.ObjectKey{Namespace: testNamespace, Name: "secondary-scheduler"} - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity := dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(BeNil()) - Expect(dp.Spec.Strategy).To(Equal(appsv1.DeploymentStrategy{})) - - Eventually(func(g Gomega) { - g.Expect(reconciler.Client.Get(ctx, key, nrs)).ToNot(HaveOccurred()) - nrs.Spec.Replicas = nil // autodetect - g.Expect(reconciler.Client.Update(ctx, nrs)).ToNot(HaveOccurred()) - }).WithPolling(5 * time.Second).WithTimeout(30 * time.Second).Should(Succeed()) - - _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: key}) - Expect(err).ToNot(HaveOccurred()) - - Expect(reconciler.Client.Get(ctx, dpKey, dp)).To(Succeed()) - affinity = dp.Spec.Template.Spec.Affinity - Expect(affinity).ToNot(BeNil()) - Expect(affinity.PodAntiAffinity).To(Equal(expectedPodAntiAff)) - Expect(dp.Spec.Strategy).To(Equal(expectedStrategy)) - }) - }) - }) }) var _ = Describe("Test computeSchedulerReplicas", func() { diff --git a/pkg/objectupdate/sched/sched.go b/pkg/objectupdate/sched/sched.go index ff3c2fec64..796ec77bfe 100644 --- a/pkg/objectupdate/sched/sched.go +++ b/pkg/objectupdate/sched/sched.go @@ -17,16 +17,12 @@ package sched import ( - "errors" "fmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/klog/v2" - "k8s.io/utils/ptr" "github.com/k8stopologyawareschedwg/deployer/pkg/flagcodec" k8swgmanifests "github.com/k8stopologyawareschedwg/deployer/pkg/manifests" @@ -106,52 +102,6 @@ func DeploymentTLSSettings(dp *appsv1.Deployment, tlsSettings objtls.Settings) e return nil } -// DeploymentAffinityWithStrategy configures required pod anti-affinity on the scheduler -// deployment only when the CR leaves replica count to autodetection (Replicas unset). -// An explicit non-nil Replicas value means the user chose a replica count and keeps -// full control (no required pod anti-affinity). -func DeploymentAffinityWithStrategy(dp *appsv1.Deployment, spec nropv1.NUMAResourcesSchedulerSpec) error { - if spec.Replicas != nil { - if dp.Spec.Template.Spec.Affinity != nil { - dp.Spec.Template.Spec.Affinity.PodAntiAffinity = nil - } - dp.Spec.Strategy = appsv1.DeploymentStrategy{} - return nil - } - - labels := dp.Spec.Template.Labels - if len(labels) == 0 { - return errors.New("no labels provided for PodAntiAffinity") - } - - if dp.Spec.Template.Spec.Affinity == nil { - dp.Spec.Template.Spec.Affinity = &corev1.Affinity{} - } - - dp.Spec.Template.Spec.Affinity.PodAntiAffinity = &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - } - klog.V(3).InfoS("Scheduler Deployment affinity", "podAntiAffinity", dp.Spec.Template.Spec.Affinity.PodAntiAffinity.String()) - // NOTE: With replicas=1 and no PodAntiAffinity, the default strategy (surge 1, then remove old) - // works as expected — the new pod can schedule anywhere, no constraints block it. - dp.Spec.Strategy = appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: ptr.To(intstr.FromInt(1)), - MaxSurge: ptr.To(intstr.FromInt(0)), - }, - } - klog.V(3).InfoS("Scheduler Deployment Rollout Strategy", "rolloutStrategy", dp.Spec.Strategy.String()) - return nil -} - func SchedulerConfig(cm *corev1.ConfigMap, name string, params *k8swgmanifests.ConfigParams) error { if cm.Data == nil { return fmt.Errorf("no data found in ConfigMap: %s/%s", cm.Namespace, cm.Name) diff --git a/pkg/objectupdate/sched/sched_test.go b/pkg/objectupdate/sched/sched_test.go index 7446fb45fd..69504e9e27 100644 --- a/pkg/objectupdate/sched/sched_test.go +++ b/pkg/objectupdate/sched/sched_test.go @@ -24,14 +24,10 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/ptr" k8swgmanifests "github.com/k8stopologyawareschedwg/deployer/pkg/manifests" @@ -635,125 +631,6 @@ func TestDeploymentTLSSettingsRepeated(t *testing.T) { } } -func TestDeploymentAffinityWithStrategyNoLabels(t *testing.T) { - dp := dpMinimal.DeepCopy() - err := DeploymentAffinityWithStrategy(dp, nropv1.NUMAResourcesSchedulerSpec{}) - if err == nil { - t.Fatalf("expected error but received nil") - } -} - -func TestDeploymentAffinityWithStrategyExplicitNonNilReplicasCount(t *testing.T) { - testcases := []struct { - name string - nonNilReplicasCount *int32 - }{ - { - name: "replicas count is positive int", - nonNilReplicasCount: ptr.To(int32(2)), - }, - { - name: "replicas count is zero", - nonNilReplicasCount: ptr.To(int32(0)), - }, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - dp := dpMinimal.DeepCopy() - dp.Spec.Template.ObjectMeta.Labels = map[string]string{"app": "scheduler"} - initialNodeAffinity := &corev1.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ - NodeSelectorTerms: []corev1.NodeSelectorTerm{ - { - MatchExpressions: []corev1.NodeSelectorRequirement{ - { - Key: "node-role.kubernetes.io/worker", - Operator: corev1.NodeSelectorOpExists, - Values: []string{""}, - }, - }, - }, - }, - }, - } - dp.Spec.Template.Spec.Affinity = &corev1.Affinity{ - NodeAffinity: initialNodeAffinity, - PodAntiAffinity: &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "scheduler"}, - }, - }, - }, - }, - } - err := DeploymentAffinityWithStrategy(dp, nropv1.NUMAResourcesSchedulerSpec{Replicas: tc.nonNilReplicasCount}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - // check that the node affinity is preserved - if diff := cmp.Diff(initialNodeAffinity, dp.Spec.Template.Spec.Affinity.NodeAffinity); diff != "" { - t.Errorf("should preserve existing NodeAffinity: affinity mismatch (-expected +got):\n%s", diff) - } - if dp.Spec.Template.Spec.Affinity.PodAntiAffinity != nil { - t.Fatalf("expected podAntiAffinity to be reset but got %v", dp.Spec.Template.Spec.Affinity.PodAntiAffinity) - } - if dp.Spec.Strategy != (appsv1.DeploymentStrategy{}) { - t.Fatalf("expected strategy to be reset but got %v", dp.Spec.Strategy) - } - }) - } -} - -func TestDeploymentAffinityWithStrategyWithOverride(t *testing.T) { - labels := map[string]string{"app": "scheduler"} - expectedStrategy := appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: ptr.To(intstr.FromInt(1)), - MaxSurge: ptr.To(intstr.FromInt(0)), - }, - } - expectedPodAntiAffinity := &corev1.PodAntiAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{ - { - LabelSelector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - TopologyKey: "kubernetes.io/hostname", - }, - }, - } - dp := dpMinimal.DeepCopy() - dp.Spec.Template.ObjectMeta.Labels = labels - - err := DeploymentAffinityWithStrategy(dp, nropv1.NUMAResourcesSchedulerSpec{}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if diff := cmp.Diff(expectedPodAntiAffinity, dp.Spec.Template.Spec.Affinity.PodAntiAffinity); diff != "" { - t.Errorf("affinity mismatch (-expected +got):\n%s", diff) - } - if diff := cmp.Diff(expectedStrategy, dp.Spec.Strategy); diff != "" { - t.Errorf("strategy mismatch (-expected +got):\n%s", diff) - } - - // check reset works - err = DeploymentAffinityWithStrategy(dp, nropv1.NUMAResourcesSchedulerSpec{Replicas: ptr.To(int32(2))}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if dp.Spec.Template.Spec.Affinity != nil && dp.Spec.Template.Spec.Affinity.PodAntiAffinity != nil { - t.Fatalf("Override failed: expected no podAntiAffinity but got %v", dp.Spec.Template.Spec.Affinity.PodAntiAffinity) - } - if dp.Spec.Strategy != (appsv1.DeploymentStrategy{}) { - t.Fatalf("expected strategy to be reset but got %v", dp.Spec.Strategy) - } -} - func mustParseResource(t *testing.T, v string) resource.Quantity { t.Helper() qty, err := resource.ParseQuantity(v) From 13fc6e0ef8f007914b3f9ee0ccdfa38f986455b3 Mon Sep 17 00:00:00 2001 From: Shereen Haj Date: Wed, 20 May 2026 13:12:39 +0300 Subject: [PATCH 2/2] WIP: use topologySpreadConstraints for HA Signed-off-by: Shereen Haj --- .../numaresourcesscheduler_controller.go | 6 +++- pkg/objectupdate/sched/sched.go | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/internal/controller/numaresourcesscheduler_controller.go b/internal/controller/numaresourcesscheduler_controller.go index c43aeca951..c5141c5f05 100644 --- a/internal/controller/numaresourcesscheduler_controller.go +++ b/internal/controller/numaresourcesscheduler_controller.go @@ -360,7 +360,11 @@ func (r *NUMAResourcesSchedulerReconciler) syncNUMASchedulerResources(ctx contex if err := schedupdate.DeploymentTLSSettings(r.SchedulerManifests.Deployment, r.TLSSettings); err != nil { return nropv1.NUMAResourcesSchedulerStatus{}, err } - + + if err := schedupdate.DeploymentTopologySpreadConstraints(r.SchedulerManifests.Deployment); err != nil { + return nropv1.NUMAResourcesSchedulerStatus{}, err + } + k8swgrbacupdate.RoleForLeaderElection(r.SchedulerManifests.Role, r.Namespace, nrosched.LeaderElectionResourceName) k8swgrbacupdate.RoleBinding(r.SchedulerManifests.RoleBinding, r.SchedulerManifests.ServiceAccount.Name, r.Namespace) diff --git a/pkg/objectupdate/sched/sched.go b/pkg/objectupdate/sched/sched.go index 796ec77bfe..844277e47d 100644 --- a/pkg/objectupdate/sched/sched.go +++ b/pkg/objectupdate/sched/sched.go @@ -22,6 +22,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog/v2" "github.com/k8stopologyawareschedwg/deployer/pkg/flagcodec" @@ -102,6 +103,35 @@ func DeploymentTLSSettings(dp *appsv1.Deployment, tlsSettings objtls.Settings) e return nil } +func DeploymentTopologySpreadConstraints(dp *appsv1.Deployment) error { + labels := dp.Spec.Template.Labels + if labels == nil { + labels = map[string]string{} + } + /* + topologySpreadConstraints: + - labelSelector: + matchLabels: + app: my-app-2 + matchLabelKeys: + - pod-template-hash + maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + */ + dp.Spec.Template.Spec.TopologySpreadConstraints = []corev1.TopologySpreadConstraint{ + { + LabelSelector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + MaxSkew: 1, + TopologyKey: "kubernetes.io/hostname", + WhenUnsatisfiable: corev1.DoNotSchedule, + MatchLabelKeys: []string{"pod-template-hash"}, + }, + } + return nil +} func SchedulerConfig(cm *corev1.ConfigMap, name string, params *k8swgmanifests.ConfigParams) error { if cm.Data == nil { return fmt.Errorf("no data found in ConfigMap: %s/%s", cm.Namespace, cm.Name)