mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 03:57:41 +00:00
Adding service affinity predicate
This commit is contained in:
parent
9dd7d2a0a1
commit
3f722a3d8e
@ -183,7 +183,7 @@ func NewNodeLabelPredicate(info NodeInfo, labels []string, presence bool) FitPre
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckNodeLabelPresence checks whether a particular label exists on a minion or not, regardless of its value
|
// CheckNodeLabelPresence checks whether a particular label exists on a minion or not, regardless of its value
|
||||||
// Consider the cases where the minions are places in regions/zones/racks and these are identified by labels
|
// Consider the cases where the minions are placed in regions/zones/racks and these are identified by labels
|
||||||
// In some cases, it is required that only minions that are part of ANY of the defined regions/zones/racks be selected
|
// In some cases, it is required that only minions that are part of ANY of the defined regions/zones/racks be selected
|
||||||
//
|
//
|
||||||
// Alternately, eliminating minions that have a certain label, regardless of value, is also useful
|
// Alternately, eliminating minions that have a certain label, regardless of value, is also useful
|
||||||
@ -204,6 +204,82 @@ func (n *NodeLabelChecker) CheckNodeLabelPresence(pod api.Pod, existingPods []ap
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServiceAffinity struct {
|
||||||
|
podLister PodLister
|
||||||
|
serviceLister ServiceLister
|
||||||
|
nodeInfo NodeInfo
|
||||||
|
labels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceAffinityPredicate(podLister PodLister, serviceLister ServiceLister, nodeInfo NodeInfo, labels []string) FitPredicate {
|
||||||
|
affinity := &ServiceAffinity{
|
||||||
|
podLister: podLister,
|
||||||
|
serviceLister: serviceLister,
|
||||||
|
nodeInfo: nodeInfo,
|
||||||
|
labels: labels,
|
||||||
|
}
|
||||||
|
return affinity.CheckServiceAffinity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceAffinity) CheckServiceAffinity(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
|
||||||
|
var affinitySelector labels.Selector
|
||||||
|
|
||||||
|
// check if the pod being scheduled has the affinity labels specified
|
||||||
|
affinityLabels := map[string]string{}
|
||||||
|
labelsExist := true
|
||||||
|
for _, l := range s.labels {
|
||||||
|
if labels.Set(pod.Labels).Has(l) {
|
||||||
|
affinityLabels[l] = labels.Set(pod.Labels).Get(l)
|
||||||
|
} else {
|
||||||
|
// the current pod does not specify all the labels, look in the existing service pods
|
||||||
|
labelsExist = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip looking at other pods in the service if the current pod defines all the required affinity labels
|
||||||
|
if !labelsExist {
|
||||||
|
service, err := s.serviceLister.GetPodService(pod)
|
||||||
|
if err == nil {
|
||||||
|
selector := labels.SelectorFromSet(service.Spec.Selector)
|
||||||
|
servicePods, err := s.podLister.ListPods(selector)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(servicePods) > 0 {
|
||||||
|
// consider any service pod and fetch the minion its hosted on
|
||||||
|
otherMinion, err := s.nodeInfo.GetNodeInfo(servicePods[0].Status.Host)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, l := range s.labels {
|
||||||
|
// If the pod being scheduled has the label value specified, do not override it
|
||||||
|
if _, exists := affinityLabels[l]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if labels.Set(otherMinion.Labels).Has(l) {
|
||||||
|
affinityLabels[l] = labels.Set(otherMinion.Labels).Get(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no existing pods in the service, consider all minions
|
||||||
|
if len(affinityLabels) == 0 {
|
||||||
|
affinitySelector = labels.Everything()
|
||||||
|
} else {
|
||||||
|
affinitySelector = labels.Set(affinityLabels).AsSelector()
|
||||||
|
}
|
||||||
|
|
||||||
|
minion, err := s.nodeInfo.GetNodeInfo(node)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the minion matches the selector
|
||||||
|
return affinitySelector.Matches(labels.Set(minion.Labels)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
|
func PodFitsPorts(pod api.Pod, existingPods []api.Pod, node string) (bool, error) {
|
||||||
existingPorts := getUsedPorts(existingPods...)
|
existingPorts := getUsedPorts(existingPods...)
|
||||||
wantPorts := getUsedPorts(pod)
|
wantPorts := getUsedPorts(pod)
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package scheduler
|
package scheduler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -31,7 +32,22 @@ func (n FakeNodeInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
|
|||||||
return &node, nil
|
return &node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
func makeResources(milliCPU int64, memory int64) api.NodeResources {
|
func makeResources(milliCPU int64, memory int64) api.NodeResources {
|
||||||
|
=======
|
||||||
|
type FakeNodeListInfo []api.Node
|
||||||
|
|
||||||
|
func (nodes FakeNodeListInfo) GetNodeInfo(nodeName string) (*api.Node, error) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Name == nodeName {
|
||||||
|
return &node, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Unable to find node: %s", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeResources(milliCPU int, memory int) api.NodeResources {
|
||||||
|
>>>>>>> e0101c2... Adding service affinity predicate
|
||||||
return api.NodeResources{
|
return api.NodeResources{
|
||||||
Capacity: api.ResourceList{
|
Capacity: api.ResourceList{
|
||||||
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
api.ResourceCPU: *resource.NewMilliQuantity(milliCPU, resource.DecimalSI),
|
||||||
@ -446,3 +462,115 @@ func TestNodeLabelPresence(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceAffinity(t *testing.T) {
|
||||||
|
selector := map[string]string{"foo": "bar"}
|
||||||
|
labels1 := map[string]string{
|
||||||
|
"region": "r1",
|
||||||
|
"zone": "z11",
|
||||||
|
}
|
||||||
|
labels2 := map[string]string{
|
||||||
|
"region": "r1",
|
||||||
|
"zone": "z12",
|
||||||
|
}
|
||||||
|
labels3 := map[string]string{
|
||||||
|
"region": "r2",
|
||||||
|
"zone": "z21",
|
||||||
|
}
|
||||||
|
labels4 := map[string]string{
|
||||||
|
"region": "r2",
|
||||||
|
"zone": "z22",
|
||||||
|
}
|
||||||
|
node1 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: labels1}}
|
||||||
|
node2 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: labels2}}
|
||||||
|
node3 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: labels3}}
|
||||||
|
node4 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine4", Labels: labels4}}
|
||||||
|
node5 := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine5", Labels: labels4}}
|
||||||
|
tests := []struct {
|
||||||
|
pod api.Pod
|
||||||
|
pods []api.Pod
|
||||||
|
services []api.Service
|
||||||
|
node string
|
||||||
|
labels []string
|
||||||
|
fits bool
|
||||||
|
test string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
node: "machine1",
|
||||||
|
fits: true,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "nothing scheduled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: map[string]string{"region": "r1"}}},
|
||||||
|
node: "machine1",
|
||||||
|
fits: true,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "pod with region label match",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: map[string]string{"region": "r2"}}},
|
||||||
|
node: "machine1",
|
||||||
|
fits: false,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "pod with region label mismatch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||||
|
pods: []api.Pod{{Status: api.PodStatus{Host: "machine1"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||||
|
node: "machine1",
|
||||||
|
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||||
|
fits: true,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "service pod on same minion",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||||
|
pods: []api.Pod{{Status: api.PodStatus{Host: "machine2"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||||
|
node: "machine1",
|
||||||
|
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||||
|
fits: true,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "service pod on different minion, region match",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||||
|
pods: []api.Pod{{Status: api.PodStatus{Host: "machine3"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||||
|
node: "machine1",
|
||||||
|
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||||
|
fits: false,
|
||||||
|
labels: []string{"region"},
|
||||||
|
test: "service pod on different minion, region mismatch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||||
|
pods: []api.Pod{{Status: api.PodStatus{Host: "machine2"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||||
|
node: "machine1",
|
||||||
|
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||||
|
fits: false,
|
||||||
|
labels: []string{"region", "zone"},
|
||||||
|
test: "service pod on different minion, multiple labels, not all match",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pod: api.Pod{ObjectMeta: api.ObjectMeta{Labels: selector}},
|
||||||
|
pods: []api.Pod{{Status: api.PodStatus{Host: "machine5"}, ObjectMeta: api.ObjectMeta{Labels: selector}}},
|
||||||
|
node: "machine4",
|
||||||
|
services: []api.Service{{Spec: api.ServiceSpec{Selector: selector}}},
|
||||||
|
fits: true,
|
||||||
|
labels: []string{"region", "zone"},
|
||||||
|
test: "service pod on different minion, multiple labels, all match",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
nodes := []api.Node{node1, node2, node3, node4, node5}
|
||||||
|
serviceAffinity := ServiceAffinity{FakePodLister(test.pods), FakeServiceLister(test.services), FakeNodeListInfo(nodes), test.labels}
|
||||||
|
fits, err := serviceAffinity.CheckServiceAffinity(test.pod, []api.Pod{}, test.node)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if fits != test.fits {
|
||||||
|
t.Errorf("%s: expected: %v got %v", test.test, test.fits, fits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
46
plugin/pkg/scheduler/algorithmprovider/affinity/affinity.go
Normal file
46
plugin/pkg/scheduler/algorithmprovider/affinity/affinity.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This algorithm provider has predicates and priorities related to affinity/anti-affinity for the scheduler.
|
||||||
|
package affinity
|
||||||
|
|
||||||
|
import (
|
||||||
|
algorithm "github.com/GoogleCloudPlatform/kubernetes/pkg/scheduler"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Provider string = "AffinityProvider"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
factory.RegisterAlgorithmProvider(Provider, defaultPredicates(), defaultPriorities())
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPredicates() util.StringSet {
|
||||||
|
return util.NewStringSet(
|
||||||
|
// Fit is defined based on whether the minion has the specified label values as the pod being scheduled
|
||||||
|
// Alternately, if the pod does not specify any/all labels, the other pods in the service are looked at
|
||||||
|
factory.RegisterFitPredicate("ServiceAffinity", algorithm.NewServiceAffinityPredicate(factory.PodLister, factory.ServiceLister, factory.MinionLister, []string{"region"})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultPriorities() util.StringSet {
|
||||||
|
return util.NewStringSet(
|
||||||
|
// spreads pods belonging to the same service across minions in different zones
|
||||||
|
// region and zone can be nested infrastructure topology levels and defined by labels on minions
|
||||||
|
factory.RegisterPriorityFunction("ZoneSpreadingPriority", algorithm.NewServiceAntiAffinityPriority(factory.ServiceLister, "zone"), 1),
|
||||||
|
)
|
||||||
|
}
|
@ -48,9 +48,6 @@ func defaultPriorities() util.StringSet {
|
|||||||
factory.RegisterPriorityFunction("LeastRequestedPriority", algorithm.LeastRequestedPriority, 1),
|
factory.RegisterPriorityFunction("LeastRequestedPriority", algorithm.LeastRequestedPriority, 1),
|
||||||
// spreads pods by minimizing the number of pods (belonging to the same service) on the same minion.
|
// spreads pods by minimizing the number of pods (belonging to the same service) on the same minion.
|
||||||
factory.RegisterPriorityFunction("ServiceSpreadingPriority", algorithm.NewServiceSpreadPriority(factory.ServiceLister), 1),
|
factory.RegisterPriorityFunction("ServiceSpreadingPriority", algorithm.NewServiceSpreadPriority(factory.ServiceLister), 1),
|
||||||
// spreads pods belonging to the same service across minions in different zones
|
|
||||||
// TODO: remove the hardcoding of the "zone" label and move it to a constant
|
|
||||||
factory.RegisterPriorityFunction("ZoneSpreadingPriority", algorithm.NewServiceAntiAffinityPriority(factory.ServiceLister, "zone"), 1),
|
|
||||||
// EqualPriority is a prioritizer function that gives an equal weight of one to all minions
|
// EqualPriority is a prioritizer function that gives an equal weight of one to all minions
|
||||||
factory.RegisterPriorityFunction("EqualPriority", algorithm.EqualPriority, 0),
|
factory.RegisterPriorityFunction("EqualPriority", algorithm.EqualPriority, 0),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user