Merge pull request #114393 from danielvegamyhre/myfeature

Option to ignore existing pods' preferred inter-pod affinities if the incoming pod has no preferred inter-pod affinities
This commit is contained in:
Kubernetes Prow Robot 2023-01-13 17:28:15 -08:00 committed by GitHub
commit 3a8e2e399b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 342 additions and 8 deletions

View File

@ -389,6 +389,27 @@ profiles:
t.Fatal(err)
}
// high throughput profile config
highThroughputProfileConfig := filepath.Join(tmpDir, "high-throughput.yaml")
if err := os.WriteFile(highThroughputProfileConfig, []byte(fmt.Sprintf(`
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: '%s'
profiles:
- schedulerName: "high-throughput-profile"
plugins:
preScore:
enabled:
- name: InterPodAffinity
pluginConfig:
- name: InterPodAffinity
args:
ignorePreferredTermsOfExistingPods: true
`, configKubeconfig)), os.FileMode(0600)); err != nil {
t.Fatal(err)
}
// Insulate this test from picking up in-cluster config when run inside a pod
// We can't assume we have permissions to write to /var/run/secrets/... from a unit test to mock in-cluster config for testing
originalHost := os.Getenv("KUBERNETES_SERVICE_HOST")
@ -1525,6 +1546,110 @@ profiles:
expectedError: `key "leaderElect" already set`,
checkErrFn: runtime.IsStrictDecodingError,
},
{
name: "high throughput profile",
options: &Options{
ConfigFile: highThroughputProfileConfig,
Logs: logs.NewOptions(),
},
expectedUsername: "config",
expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
TypeMeta: metav1.TypeMeta{
APIVersion: v1.SchemeGroupVersion.String(),
},
Parallelism: 16,
DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{
EnableProfiling: true,
EnableContentionProfiling: true,
},
LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
LeaderElect: true,
LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
RetryPeriod: metav1.Duration{Duration: 2 * time.Second},
ResourceLock: "leases",
ResourceNamespace: "kube-system",
ResourceName: "kube-scheduler",
},
ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
Kubeconfig: configKubeconfig,
QPS: 50,
Burst: 100,
ContentType: "application/vnd.kubernetes.protobuf",
},
PercentageOfNodesToScore: defaultPercentageOfNodesToScore,
PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds,
PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds,
Profiles: []kubeschedulerconfig.KubeSchedulerProfile{
{
SchedulerName: "high-throughput-profile",
Plugins: &kubeschedulerconfig.Plugins{
QueueSort: defaults.PluginsV1.QueueSort,
PreFilter: defaults.PluginsV1.PreFilter,
Filter: defaults.PluginsV1.Filter,
PostFilter: defaults.PluginsV1.PostFilter,
PreScore: kubeschedulerconfig.PluginSet{
Enabled: []kubeschedulerconfig.Plugin{
{Name: "InterPodAffinity"},
},
},
Score: defaults.PluginsV1.Score,
Bind: defaults.PluginsV1.Bind,
PreBind: defaults.PluginsV1.PreBind,
Reserve: defaults.PluginsV1.Reserve,
MultiPoint: defaults.PluginsV1.MultiPoint,
},
PluginConfig: []kubeschedulerconfig.PluginConfig{
{
Name: "InterPodAffinity",
Args: &kubeschedulerconfig.InterPodAffinityArgs{
HardPodAffinityWeight: 1,
IgnorePreferredTermsOfExistingPods: true,
},
},
{
Name: "DefaultPreemption",
Args: &kubeschedulerconfig.DefaultPreemptionArgs{
MinCandidateNodesPercentage: 10,
MinCandidateNodesAbsolute: 100,
},
},
{
Name: "NodeAffinity",
Args: &kubeschedulerconfig.NodeAffinityArgs{},
},
{
Name: "NodeResourcesBalancedAllocation",
Args: &kubeschedulerconfig.NodeResourcesBalancedAllocationArgs{
Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}},
},
},
{
Name: "NodeResourcesFit",
Args: &kubeschedulerconfig.NodeResourcesFitArgs{
ScoringStrategy: &kubeschedulerconfig.ScoringStrategy{
Type: kubeschedulerconfig.LeastAllocated,
Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}},
},
},
},
{
Name: "PodTopologySpread",
Args: &kubeschedulerconfig.PodTopologySpreadArgs{
DefaultingType: kubeschedulerconfig.SystemDefaulting,
},
},
{
Name: "VolumeBinding",
Args: &kubeschedulerconfig.VolumeBindingArgs{
BindTimeoutSeconds: 600,
},
},
},
},
},
},
},
}
for _, tc := range testcases {
@ -1550,7 +1675,7 @@ profiles:
}
return
}
t.Errorf("unexpected error to create a config: %v", err)
t.Errorf("unexpected error creating config: %v", err)
return
}

View File

@ -53495,7 +53495,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"},
},
},
}
@ -54599,7 +54608,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"},
},
},
}
@ -55710,7 +55728,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()) {

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 `json:"hardPodAffinityWeight,omitempty"`
// 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 `json:"ignorePreferredTermsOfExistingPods"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

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 `json:"hardPodAffinityWeight,omitempty"`
// 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 `json:"ignorePreferredTermsOfExistingPods"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

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 `json:"hardPodAffinityWeight,omitempty"`
// 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 `json:"ignorePreferredTermsOfExistingPods"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object