背景
Kubernetes中的调度策略可以大致分为两种,一种是全局的调度策略,要在启动调度器时配置,包括kubernetes调度器自带的各种predicates和priorities算法;另一种是运行时调度策略,包括nodeAffinity(主机亲和性),podAffinity(POD亲和性)以及podAntiAffinity(POD反亲和性)。
podAffinity 主要解决POD可以和哪些POD部署在同一个拓扑域中的问题(拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的cluster、zone等),podAntiAffinity主要解决POD不能和哪些POD部署在同一个拓扑域中的问题。它们处理的是Kubernetes集群内部POD和POD之间的关系。
使用场景介绍
-
podAntiAffinity使用场景:
将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。
给POD对于一个节点的独占访问权限来保证资源隔离,保证不会有其它pod来分享节点资源。
把可能会相互影响的服务的POD分散在不同的主机上。
对于亲和性和反亲和性,每种都有两种规则可以设置:
-
RequiredDuringSchedulingIgnoredDuringExecution:在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则POD不能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。(硬规则)
-
PreferredDuringSchedulingIgnoredDuringExecution:在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,POD也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。(软规则)
使用示例
- podAntiAffinity 使用示例:
使用hostname作为拓扑域,把pod创建在不同主机上,每个主机上最多只有一个同类型的POD(同类型用标签区分)。其中matchExpressions中填写内容对应到RC中POD自身的标签。可以通过修改需要匹配的标签内容来控制把一个服务中的POD和其它服务的POD部署在不同主机上。
yaml中的定义如下:
...
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
Anti_wmonitorTest6: wmonitorTest
topologyKey: kubernetes.io/hostname
...
上边的例子中可以通过修改topologyKey来限制拓扑域的范围,实现把相关服务部署在不同的容灾域等其它功能。
具体实现过程
整个计算过程摘选自Kubernetes 1.7源码,为便于阅读部分函数删除了无关代码
-
对于待调度的Pod,首先分别判断是否存在亲和性限制和反亲和性限制,若有都会调用processTerms方法,若为亲和性则传入参数multiplier = 1,若为反亲和性则传入参数multiplier = -1
if existingHasAffinityConstraints { terms := existingPodAffinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution pm.processTerms(terms, existingPod, pod, existingPodNode, 1) } if existingHasAntiAffinityConstraints { terms := existingPodAffinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution pm.processTerms(terms, existingPod, pod, existingPodNode, -1) }
-
对于具体的processTerms方法会遍历Pod设置的所有affinityTerm或者antiaffinityTerm(即比如某个Pod设置CPU密集型和IO密集型则是两个term),针对每个具体antiaffinityTerm(或者affinityTerm)调用processTerm方法
func (p *podAffinityPriorityMap) processTerms(terms []v1.WeightedPodAffinityTerm, podDefiningAffinityTerm, podToCheck *v1.Pod, fixedNode *v1.Node, multiplier int) { for i := range terms { term := &terms[i] p.processTerm(&term.PodAffinityTerm, podDefiningAffinityTerm, podToCheck, fixedNode, float64(term.Weight*int32(multiplier))) } }
-
带调度的Pod的具体某种亲和性选项(PodAffinityTerm)会将自身的权重(weight)加入一个key为主机名(node.Name)value为当前累计weight和的一个map(p.counts)中,该map可以看作单次优选时候的一个全局的map
func (p *podAffinityPriorityMap) processTerm(term *v1.PodAffinityTerm, podDefiningAffinityTerm, podToCheck *v1.Pod, fixedNode *v1.Node, weight float64) { func() { for _, node := range p.nodes { if p.failureDomains.NodesHaveSameTopologyKey(node, fixedNode, term.TopologyKey) { p.counts[node.Name] += weight } } }() }
-
等到该待调度Pod计算完所有节点的亲和性及反亲和性Term的累计权重值后,开始为所有节点打分数,最终分数在0到10分之间
func (ipa *InterPodAffinity) CalculateInterPodAffinityPriority { for _, node := range nodes { if p.counts[node.Name] > maxCount { maxCount = p.counts[node.Name] } if p.counts[node.Name] < minCount { minCount = p.counts[node.Name] } } for _, node := range nodes { fScore := float64(0) if (maxCount - minCount) > 0 { fScore = 10 * ((p.counts[node.Name] - minCount) / (maxCount - minCount)) } }
总结
对于单个Pod,可以设置多个亲和性项和多个反亲和性项,每个亲和性项和反亲和性项自身都可以设置独立的权重,然后一次亲和性优选调度会将自身的所有亲和项权重加入每个节点的累计权值(或将自身的所有反亲和项权重减去每个节点的累计权值),然后对于每个节点所带的累计权重算出一个0到10分的分数。