From 211ba13770d3c3b297645b03237900f1076d9ed5 Mon Sep 17 00:00:00 2001 From: Zak Peirce Date: Tue, 9 Dec 2025 13:27:30 -0800 Subject: [PATCH] feat: add topologySpreadConstraints support and respect user-provided affinity This commit introduces two related scheduling enhancements: 1. Add topologySpreadConstraints field to BaseSpec (aggregated cluster) and CommonSpec (disaggregated cluster) for more flexible pod distribution control. 2. Change affinity behavior to fully respect user-provided configuration: - If user provides affinity, use it as-is without injecting defaults - Only apply default soft podAntiAffinity when user hasn't specified any affinity This allows users to configure scenarios like hard zone spreading with soft node spreading using topologySpreadConstraints, which was not possible before. Files modified: - api/doris/v1/types.go: Add TopologySpreadConstraints to BaseSpec - api/disaggregated/v1/types.go: Add TopologySpreadConstraints to CommonSpec - pkg/common/utils/resource/pod.go: Pass through TopologySpreadConstraints, simplify constructAffinity to respect user config - pkg/controller/sub_controller/disaggregated_subcontroller.go: Simplify ConstructDefaultAffinity to respect user config --- api/disaggregated/v1/types.go | 5 ++ api/doris/v1/types.go | 5 ++ pkg/common/utils/resource/pod.go | 58 ++++++++----------- .../disaggregated_subcontroller.go | 20 ++----- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/api/disaggregated/v1/types.go b/api/disaggregated/v1/types.go index 2712ce30..b29bdc1f 100644 --- a/api/disaggregated/v1/types.go +++ b/api/disaggregated/v1/types.go @@ -177,6 +177,11 @@ type CommonSpec struct { //+optional Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + // (Optional) TopologySpreadConstraints describes how a group of pods ought to spread across topology + // domains. Scheduler will schedule pods in a way which abides by the constraints. + // +optional + TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` + // export service for accessing from outside k8s. Service *ExportService `json:"service,omitempty"` diff --git a/api/doris/v1/types.go b/api/doris/v1/types.go index f86d8557..9504ce08 100644 --- a/api/doris/v1/types.go +++ b/api/doris/v1/types.go @@ -245,6 +245,11 @@ type BaseSpec struct { //+optional Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + // (Optional) TopologySpreadConstraints describes how a group of pods ought to spread across topology + // domains. Scheduler will schedule pods in a way which abides by the constraints. + // +optional + TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` + //+optional // podLabels for user selector or classify pods PodLabels map[string]string `json:"podLabels,omitempty"` diff --git a/pkg/common/utils/resource/pod.go b/pkg/common/utils/resource/pod.go index 53dd1076..9f63d620 100644 --- a/pkg/common/utils/resource/pod.go +++ b/pkg/common/utils/resource/pod.go @@ -178,15 +178,16 @@ func NewPodTemplateSpec(dcr *v1.DorisCluster, config map[string]interface{}, com }, Spec: corev1.PodSpec{ - ImagePullSecrets: spec.ImagePullSecrets, - NodeSelector: spec.NodeSelector, - Volumes: volumes, - ServiceAccountName: spec.ServiceAccount, - Affinity: spec.Affinity.DeepCopy(), - Tolerations: spec.Tolerations, - HostAliases: spec.HostAliases, - InitContainers: defaultInitContainers, - SecurityContext: SecurityContext, + ImagePullSecrets: spec.ImagePullSecrets, + NodeSelector: spec.NodeSelector, + Volumes: volumes, + ServiceAccountName: spec.ServiceAccount, + Affinity: spec.Affinity.DeepCopy(), + Tolerations: spec.Tolerations, + TopologySpreadConstraints: spec.TopologySpreadConstraints, + HostAliases: spec.HostAliases, + InitContainers: defaultInitContainers, + SecurityContext: SecurityContext, }, } @@ -210,15 +211,16 @@ func NewPodTemplateSpecWithCommonSpec(skipDefaultInit bool, cs *dv1.CommonSpec, }, Spec: corev1.PodSpec{ - ImagePullSecrets: cs.ImagePullSecrets, - NodeSelector: cs.NodeSelector, - ServiceAccountName: cs.ServiceAccount, - Affinity: cs.Affinity.DeepCopy(), - Tolerations: cs.Tolerations, - HostAliases: cs.HostAliases, - InitContainers: defaultInitContainers, - SecurityContext: cs.SecurityContext, - Volumes: vs, + ImagePullSecrets: cs.ImagePullSecrets, + NodeSelector: cs.NodeSelector, + ServiceAccountName: cs.ServiceAccount, + Affinity: cs.Affinity.DeepCopy(), + Tolerations: cs.Tolerations, + TopologySpreadConstraints: cs.TopologySpreadConstraints, + HostAliases: cs.HostAliases, + InitContainers: defaultInitContainers, + SecurityContext: cs.SecurityContext, + Volumes: vs, }, } constructDisaggregatedInitContainers(skipDefaultInit, componentType, &pts.Spec, si) @@ -1201,22 +1203,12 @@ func getDefaultAffinity(componentType v1.ComponentType) *corev1.Affinity { } func constructAffinity(dcrAffinity *corev1.Affinity, componentType v1.ComponentType) *corev1.Affinity { - affinity := getDefaultAffinity(componentType) - - if dcrAffinity == nil { - return affinity - } - - dcrPodAntiAffinity := dcrAffinity.PodAntiAffinity - if dcrPodAntiAffinity != nil { - affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = dcrPodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution - affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, dcrPodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution...) + // If user provides affinity, respect it completely without injecting defaults + if dcrAffinity != nil { + return dcrAffinity.DeepCopy() } - - affinity.NodeAffinity = dcrAffinity.NodeAffinity - affinity.PodAffinity = dcrAffinity.PodAffinity - - return affinity + // Only apply defaults when user hasn't specified any affinity + return getDefaultAffinity(componentType) } func constructBeDefaultInitContainer(defaultImage string) corev1.Container { diff --git a/pkg/controller/sub_controller/disaggregated_subcontroller.go b/pkg/controller/sub_controller/disaggregated_subcontroller.go index ee3928fc..850756d8 100644 --- a/pkg/controller/sub_controller/disaggregated_subcontroller.go +++ b/pkg/controller/sub_controller/disaggregated_subcontroller.go @@ -189,22 +189,12 @@ func (d *DisaggregatedSubDefaultController) BuildDefaultConfigMapVolumesVolumeMo } func (d *DisaggregatedSubDefaultController) ConstructDefaultAffinity(matchKey, value string, ddcAffinity *corev1.Affinity) *corev1.Affinity { - affinity := d.newDefaultAffinity(matchKey, value) - - if ddcAffinity == nil { - return affinity - } - - ddcPodAntiAffinity := ddcAffinity.PodAntiAffinity - if ddcPodAntiAffinity != nil { - affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = ddcPodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution - affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, ddcPodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution...) + // If user provides affinity, respect it completely without injecting defaults + if ddcAffinity != nil { + return ddcAffinity.DeepCopy() } - - affinity.NodeAffinity = ddcAffinity.NodeAffinity - affinity.PodAffinity = ddcAffinity.PodAffinity - - return affinity + // Only apply defaults when user hasn't specified any affinity + return d.newDefaultAffinity(matchKey, value) } func (d *DisaggregatedSubDefaultController) newDefaultAffinity(matchKey, value string) *corev1.Affinity {