mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 13:29:11 +01:00
Check whether pod matches the inter-pod anti-affinity of another Pod in a given Node in NodeFit() (#1356)
* Check if Pod matches inter-pod anti-affinity of other pod on node as part of NodeFit() * Add unit tests for checking inter-pod anti-affinity match in NodeFit() * Export setPodAntiAffinity() helper func to test utils * Add docs for inter-pod anti-affinity in README * Refactor logic for inter-pod anti-affinity to use in multiple pkgs * Move logic for finding match between pods with antiaffinity out of framework to reuse in other pkgs * Move interpod antiaffinity funcs to pkg/utils/predicates.go * Add unit tests for inter-pod anti-affinity check * Test logic in GroupByNodeName * Test NodeFit() case where pods matches inter-pod anti-affinity * Test for inter-pod anti-affinity pods match terms, have label selector * NodeFit inter-pod anti-affinity check returns early if affinity spec not set
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"sort"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/component-helpers/scheduling/corev1"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -299,3 +300,84 @@ func GetNodeWeightGivenPodPreferredAffinity(pod *v1.Pod, node *v1.Node) (int32,
|
||||
}
|
||||
return sumWeights, nil
|
||||
}
|
||||
|
||||
func CreateNodeMap(nodes []*v1.Node) map[string]*v1.Node {
|
||||
m := make(map[string]*v1.Node, len(nodes))
|
||||
for _, node := range nodes {
|
||||
m[node.GetName()] = node
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// CheckPodsWithAntiAffinityExist checks if there are other pods on the node that the current pod cannot tolerate.
|
||||
func CheckPodsWithAntiAffinityExist(pod *v1.Pod, pods map[string][]*v1.Pod, nodeMap map[string]*v1.Node) bool {
|
||||
affinity := pod.Spec.Affinity
|
||||
if affinity != nil && affinity.PodAntiAffinity != nil {
|
||||
for _, term := range getPodAntiAffinityTerms(affinity.PodAntiAffinity) {
|
||||
namespaces := getNamespacesFromPodAffinityTerm(pod, &term)
|
||||
selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Unable to convert LabelSelector into Selector")
|
||||
return false
|
||||
}
|
||||
for namespace := range namespaces {
|
||||
for _, existingPod := range pods[namespace] {
|
||||
if existingPod.Name != pod.Name && podMatchesTermsNamespaceAndSelector(existingPod, namespaces, selector) {
|
||||
node, ok := nodeMap[pod.Spec.NodeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
nodeHavingExistingPod, ok := nodeMap[existingPod.Spec.NodeName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if hasSameLabelValue(node, nodeHavingExistingPod, term.TopologyKey) {
|
||||
klog.V(1).InfoS("Found Pods matching PodAntiAffinity", "pod with anti-affinity", klog.KObj(pod))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getPodAntiAffinityTerms gets the antiaffinity terms for the given pod.
|
||||
func getPodAntiAffinityTerms(podAntiAffinity *v1.PodAntiAffinity) (terms []v1.PodAffinityTerm) {
|
||||
if podAntiAffinity != nil {
|
||||
if len(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 {
|
||||
terms = podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||
}
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
// hasSameLabelValue checks if the pods are in the same topology zone.
|
||||
func hasSameLabelValue(node1, node2 *v1.Node, key string) bool {
|
||||
if node1.Name == node2.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
// no match if node has empty labels
|
||||
node1Labels := node1.Labels
|
||||
if node1Labels == nil {
|
||||
return false
|
||||
}
|
||||
node2Labels := node2.Labels
|
||||
if node2Labels == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// no match if node has no topology zone label with given key
|
||||
value1, ok := node1Labels[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
value2, ok := node2Labels[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return value1 == value2
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/descheduler/test"
|
||||
)
|
||||
|
||||
func TestUniqueSortTolerations(t *testing.T) {
|
||||
@@ -1041,3 +1042,71 @@ func TestPodNodeAffinityWeight(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckPodsWithAntiAffinityExist(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *v1.Pod
|
||||
podsInNamespace map[string][]*v1.Pod
|
||||
nodeMap map[string]*v1.Node
|
||||
affinity *v1.PodAntiAffinity
|
||||
expMatch bool
|
||||
}{
|
||||
{
|
||||
name: "found pod matching pod anti-affinity",
|
||||
pod: test.PodWithPodAntiAffinity(test.BuildTestPod("p1", 1000, 1000, "node", nil), "foo", "bar"),
|
||||
podsInNamespace: map[string][]*v1.Pod{
|
||||
"default": {
|
||||
test.PodWithPodAntiAffinity(test.BuildTestPod("p2", 1000, 1000, "node", nil), "foo", "bar"),
|
||||
},
|
||||
},
|
||||
nodeMap: map[string]*v1.Node{
|
||||
"node": test.BuildTestNode("node", 64000, 128*1000*1000*1000, 2, func(node *v1.Node) {
|
||||
node.ObjectMeta.Labels = map[string]string{
|
||||
"region": "main-region",
|
||||
}
|
||||
}),
|
||||
},
|
||||
expMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no match with invalid label selector",
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Affinity: &v1.Affinity{
|
||||
PodAntiAffinity: &v1.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"wrong": "selector",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
podsInNamespace: map[string][]*v1.Pod{},
|
||||
nodeMap: map[string]*v1.Node{},
|
||||
expMatch: false,
|
||||
},
|
||||
{
|
||||
name: "no match if pod does not match terms namespace",
|
||||
pod: test.PodWithPodAntiAffinity(test.BuildTestPod("p1", 1000, 1000, "node", func(pod *v1.Pod) {
|
||||
pod.Namespace = "other"
|
||||
}), "foo", "bar"),
|
||||
podsInNamespace: map[string][]*v1.Pod{},
|
||||
nodeMap: map[string]*v1.Node{},
|
||||
expMatch: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if match := CheckPodsWithAntiAffinityExist(test.pod, test.podsInNamespace, test.nodeMap); match != test.expMatch {
|
||||
t.Errorf("exp %v got %v", test.expMatch, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
|
||||
const SystemCriticalPriority = 2 * int32(1000000000)
|
||||
|
||||
// GetNamespacesFromPodAffinityTerm returns a set of names
|
||||
// getNamespacesFromPodAffinityTerm returns a set of names
|
||||
// according to the namespaces indicated in podAffinityTerm.
|
||||
// If namespaces is empty it considers the given pod's namespace.
|
||||
func GetNamespacesFromPodAffinityTerm(pod *v1.Pod, podAffinityTerm *v1.PodAffinityTerm) sets.Set[string] {
|
||||
func getNamespacesFromPodAffinityTerm(pod *v1.Pod, podAffinityTerm *v1.PodAffinityTerm) sets.Set[string] {
|
||||
names := sets.New[string]()
|
||||
if len(podAffinityTerm.Namespaces) == 0 {
|
||||
names.Insert(pod.Namespace)
|
||||
@@ -27,9 +27,9 @@ func GetNamespacesFromPodAffinityTerm(pod *v1.Pod, podAffinityTerm *v1.PodAffini
|
||||
return names
|
||||
}
|
||||
|
||||
// PodMatchesTermsNamespaceAndSelector returns true if the given <pod>
|
||||
// podMatchesTermsNamespaceAndSelector returns true if the given <pod>
|
||||
// matches the namespace and selector defined by <affinityPod>`s <term>.
|
||||
func PodMatchesTermsNamespaceAndSelector(pod *v1.Pod, namespaces sets.Set[string], selector labels.Selector) bool {
|
||||
func podMatchesTermsNamespaceAndSelector(pod *v1.Pod, namespaces sets.Set[string], selector labels.Selector) bool {
|
||||
if !namespaces.Has(pod.Namespace) {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user