optionally ignore preferred terms of existing pods unless incoming pod

has inter-pod affinities
This commit is contained in:
Daniel Vega-Myhre
2022-12-08 01:49:43 +00:00
parent 3f752b5edf
commit 41817b1888
12 changed files with 342 additions and 8 deletions

View File

@@ -54133,7 +54133,16 @@ func schema_k8sio_kube_scheduler_config_v1_InterPodAffinityArgs(ref common.Refer
Format: "int32",
},
},
"ignorePreferredTermsOfExistingPods": {
SchemaProps: spec.SchemaProps{
Description: "IgnorePreferredTermsOfExistingPods configures the scheduler to ignore existing pods' preferred affinity rules when scoring candidate nodes, unless the incoming pod has inter-pod affinities.",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"ignorePreferredTermsOfExistingPods"},
},
},
}
@@ -55237,7 +55246,16 @@ func schema_k8sio_kube_scheduler_config_v1beta2_InterPodAffinityArgs(ref common.
Format: "int32",
},
},
"ignorePreferredTermsOfExistingPods": {
SchemaProps: spec.SchemaProps{
Description: "IgnorePreferredTermsOfExistingPods configures the scheduler to ignore existing pods' preferred affinity rules when scoring candidate nodes, unless the incoming pod has inter-pod affinities.",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"ignorePreferredTermsOfExistingPods"},
},
},
}
@@ -56348,7 +56366,16 @@ func schema_k8sio_kube_scheduler_config_v1beta3_InterPodAffinityArgs(ref common.
Format: "int32",
},
},
"ignorePreferredTermsOfExistingPods": {
SchemaProps: spec.SchemaProps{
Description: "IgnorePreferredTermsOfExistingPods configures the scheduler to ignore existing pods' preferred affinity rules when scoring candidate nodes, unless the incoming pod has inter-pod affinities.",
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
},
Required: []string{"ignorePreferredTermsOfExistingPods"},
},
},
}

View File

@@ -1134,6 +1134,71 @@ profiles:
},
},
},
{
name: "ignorePreferredTermsOfExistingPods is enabled",
data: []byte(`
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- pluginConfig:
- name: InterPodAffinity
args:
ignorePreferredTermsOfExistingPods: true
`),
wantProfiles: []config.KubeSchedulerProfile{
{
SchedulerName: "default-scheduler",
Plugins: defaults.PluginsV1,
PluginConfig: []config.PluginConfig{
{
Name: "InterPodAffinity",
Args: &config.InterPodAffinityArgs{
HardPodAffinityWeight: 1,
IgnorePreferredTermsOfExistingPods: true,
},
},
{
Name: "DefaultPreemption",
Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100},
},
{
Name: "NodeAffinity",
Args: &config.NodeAffinityArgs{},
},
{
Name: "NodeResourcesBalancedAllocation",
Args: &config.NodeResourcesBalancedAllocationArgs{
Resources: []config.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}},
},
},
{
Name: "NodeResourcesFit",
Args: &config.NodeResourcesFitArgs{
ScoringStrategy: &config.ScoringStrategy{
Type: config.LeastAllocated,
Resources: []config.ResourceSpec{
{Name: "cpu", Weight: 1},
{Name: "memory", Weight: 1},
},
},
},
},
{
Name: "PodTopologySpread",
Args: &config.PodTopologySpreadArgs{
DefaultingType: config.SystemDefaulting,
},
},
{
Name: "VolumeBinding",
Args: &config.VolumeBindingArgs{
BindTimeoutSeconds: 600,
},
},
},
},
},
},
}
decoder := Codecs.UniversalDecoder()
for _, tt := range testCases {
@@ -1255,6 +1320,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1beta2
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1360,6 +1426,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1beta2
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1475,6 +1542,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1beta3
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1578,6 +1646,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1beta3
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1693,6 +1762,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1796,6 +1866,7 @@ profiles:
- args:
apiVersion: kubescheduler.config.k8s.io/v1
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: false
kind: InterPodAffinityArgs
name: InterPodAffinity
- args:
@@ -1820,6 +1891,57 @@ profiles:
foo: bar
name: OutOfTreePlugin
schedulerName: ""
`,
},
{
name: "v1 ignorePreferredTermsOfExistingPods is enabled",
version: v1.SchemeGroupVersion,
obj: &config.KubeSchedulerConfiguration{
Parallelism: 8,
Profiles: []config.KubeSchedulerProfile{
{
PluginConfig: []config.PluginConfig{
{
Name: "InterPodAffinity",
Args: &config.InterPodAffinityArgs{
HardPodAffinityWeight: 5,
IgnorePreferredTermsOfExistingPods: true,
},
},
},
},
},
},
want: `apiVersion: kubescheduler.config.k8s.io/v1
clientConnection:
acceptContentTypes: ""
burst: 0
contentType: ""
kubeconfig: ""
qps: 0
enableContentionProfiling: false
enableProfiling: false
kind: KubeSchedulerConfiguration
leaderElection:
leaderElect: false
leaseDuration: 0s
renewDeadline: 0s
resourceLock: ""
resourceName: ""
resourceNamespace: ""
retryPeriod: 0s
parallelism: 8
podInitialBackoffSeconds: 0
podMaxBackoffSeconds: 0
profiles:
- pluginConfig:
- args:
apiVersion: kubescheduler.config.k8s.io/v1
hardPodAffinityWeight: 5
ignorePreferredTermsOfExistingPods: true
kind: InterPodAffinityArgs
name: InterPodAffinity
schedulerName: ""
`,
},
}

View File

@@ -52,6 +52,10 @@ type InterPodAffinityArgs struct {
// HardPodAffinityWeight is the scoring weight for existing pods with a
// matching hard affinity to the incoming pod.
HardPodAffinityWeight int32
// IgnorePreferredTermsOfExistingPods configures the scheduler to ignore existing pods' preferred affinity
// rules when scoring candidate nodes, unless the incoming pod has inter-pod affinities.
IgnorePreferredTermsOfExistingPods bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@@ -375,6 +375,7 @@ func autoConvert_v1_InterPodAffinityArgs_To_config_InterPodAffinityArgs(in *v1.I
if err := metav1.Convert_Pointer_int32_To_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}
@@ -387,6 +388,7 @@ func autoConvert_config_InterPodAffinityArgs_To_v1_InterPodAffinityArgs(in *conf
if err := metav1.Convert_int32_To_Pointer_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}

View File

@@ -375,6 +375,7 @@ func autoConvert_v1beta2_InterPodAffinityArgs_To_config_InterPodAffinityArgs(in
if err := v1.Convert_Pointer_int32_To_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}
@@ -387,6 +388,7 @@ func autoConvert_config_InterPodAffinityArgs_To_v1beta2_InterPodAffinityArgs(in
if err := v1.Convert_int32_To_Pointer_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}

View File

@@ -375,6 +375,7 @@ func autoConvert_v1beta3_InterPodAffinityArgs_To_config_InterPodAffinityArgs(in
if err := v1.Convert_Pointer_int32_To_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}
@@ -387,6 +388,7 @@ func autoConvert_config_InterPodAffinityArgs_To_v1beta3_InterPodAffinityArgs(in
if err := v1.Convert_int32_To_Pointer_int32(&in.HardPodAffinityWeight, &out.HardPodAffinityWeight, s); err != nil {
return err
}
out.IgnorePreferredTermsOfExistingPods = in.IgnorePreferredTermsOfExistingPods
return nil
}

View File

@@ -142,6 +142,15 @@ func (pl *InterPodAffinity) PreScore(
hasPreferredAffinityConstraints := affinity != nil && affinity.PodAffinity != nil && len(affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0
hasPreferredAntiAffinityConstraints := affinity != nil && affinity.PodAntiAffinity != nil && len(affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0
// Optionally ignore calculating preferences of existing pods' affinity rules
// if the incoming pod has no inter-pod affinities.
if pl.args.IgnorePreferredTermsOfExistingPods && !hasPreferredAffinityConstraints && !hasPreferredAntiAffinityConstraints {
cycleState.Write(preScoreStateKey, &preScoreState{
topologyScore: make(map[string]map[string]int64),
})
return nil
}
// Unless the pod being scheduled has preferred affinity terms, we only
// need to process nodes hosting pods with affinity.
var allNodes []*framework.NodeInfo

View File

@@ -369,12 +369,13 @@ func TestPreferredAffinity(t *testing.T) {
}
tests := []struct {
pod *v1.Pod
pods []*v1.Pod
nodes []*v1.Node
expectedList framework.NodeScoreList
name string
wantStatus *framework.Status
pod *v1.Pod
pods []*v1.Pod
nodes []*v1.Node
expectedList framework.NodeScoreList
name string
ignorePreferredTermsOfExistingPods bool
wantStatus *framework.Status
}{
{
name: "all nodes are same priority as Affinity is nil",
@@ -736,13 +737,41 @@ func TestPreferredAffinity(t *testing.T) {
},
expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}},
},
{
name: "Ignore preferred terms of existing pods",
pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}},
pods: []*v1.Pod{
{Spec: v1.PodSpec{NodeName: "node1", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}},
{Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}},
},
nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}},
},
expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 0}},
ignorePreferredTermsOfExistingPods: true,
},
{
name: "Do not ignore preferred terms of existing pods",
pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}},
pods: []*v1.Pod{
{Spec: v1.PodSpec{NodeName: "node1", Affinity: stayWithS1InRegionAwayFromS2InAz}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS1}},
{Spec: v1.PodSpec{NodeName: "node2", Affinity: stayWithS2InRegion}, ObjectMeta: metav1.ObjectMeta{Labels: podLabelSecurityS2}},
},
nodes: []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1", Labels: labelRgChina}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2", Labels: labelRgIndia}},
},
expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: framework.MaxNodeScore}},
ignorePreferredTermsOfExistingPods: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
state := framework.NewCycleState()
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: 1}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
p := plugintesting.SetupPluginWithInformers(ctx, t, New, &config.InterPodAffinityArgs{HardPodAffinityWeight: 1, IgnorePreferredTermsOfExistingPods: test.ignorePreferredTermsOfExistingPods}, cache.NewSnapshot(test.pods, test.nodes), namespaces)
status := p.(framework.PreScorePlugin).PreScore(ctx, state, test.pod, test.nodes)
if !status.IsSuccess() {
if !strings.Contains(status.Message(), test.wantStatus.Message()) {