05 亲和性与互斥性调度
亲和性与互斥性可以理解为就是相关联的两种或多种Pod是否可以在同一个拓扑域中共存或者互斥。
那么什么是拓扑域?
5.1 拓扑域
拓扑域的概念:
- 一个拓扑域由一些
Node
节点组成,这些Node
节点通常有相同的地理空间坐标,比如在同一个机架、机房或地区; - 一般用
region
表示机架、 机房等的拓扑区域,用Zone
表示地区这样跨度更大的拓扑区域; - 极端情况下, 我们也可以认为一个Node就是一个拓扑区域。
k8s
内置了如下一些常用的默认拓扑域,主要是为了确定各个节点所属的拓扑域:
默认拓扑域 | 描述 |
kubernetes.io/hostname | 在Node节点初始化时,controller–manager会为Node打上该标签 |
topology.kubernetes.io/region | 公有云厂商提供的Kubernetes服务或者使用cloud-controller-manager创建的集群,会给Node打上该标签 |
topology.kubernetes.io/zone | 同上 |
5.2 举例
Pod
亲和与互斥的调度是通过在Pod
的定义上增加topologyKey 属性来声明对应的目标拓扑区域内几种相关联的Pod
要 “在一起或不在一起”。
与节点亲和相同,Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution
和
preferredDuringSchedulingIgnoredDuringExecution
:
- Pod的亲和性被定义于PodSpec的affinity字段的podAffinity子字段中;
- Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中.
下面通过实例来说明Pod间的亲和性和互斥性策略设置。
5.2.1 参照目标pod
首先,创建一个名为pod-flag
的Pod
,带有标签security=S1
和app=nginx
,后面的例子将使用pod-flag
作为Pod
亲和与互斥的目标Pod
:
apiversion:v1 kind:Pod metadata: name:pod-flag labels: security:"S1" app:"nginx" spec: containers: -name:nginx image:nginx
5.2.2 pod的亲和性调度
下面创建第2个Pod来说明Pod的亲和性调度,这里定义的亲和标签是 “security=S1”,对应上面的Pod “pod-flag”,topologyKey的值被设置为 “kubernetes.io/hostname“:
apiVersion:vl kind:Pod metadata: name:pod-affinity spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key:security operator:In values: -S1 topologyKey:kubernetes.io/hostname containers: - name:with-pod-affinity image:gcr.io/google_containers/pause:2.0
创建Pod
之后,使用kubectl get pods -o wide
命令可以看到,这两个Pod
在同
一个Node
上运行。
在创建这个
Pod
之前,删掉这个节点的kubernetes.io/hostname
标签,重复上面的创建步骤,将会发现Pod
一直处于Pending
状态,这是因为找不到满足条件的Node
了。
5.2.3 pod的互斥性调度
创建第3个Pod
,我们希望它不与目标Pod
运行在同一个Node
上:
apiversion:v1 kind:Pod metadata: name:anti-affinity spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key:security operator:In values: -S1 topologyKey:topology.kubernetes.io/zone podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key:app operator:In values: -nginx topologyKey:kubernetes.io/hostname containers: - name:anti-affinity image:gcr.io/google_containers/pause:2.0
这里要求这个新Pod
与security=S1
的Pod为同一个zone
,但是不与app=nginx
的Pod
为同一个Node
。
创建Pod
之后,同样用kubectl get pods -o wide
来查看,会看到新的Pod
被调度到了同一Zone
内的不同Node
上。
5.4 其它
与节点亲和性类似,Pod
亲和性的操作符也包括In、NotIn、Exists、 DoesNotExist、Gt、Lt
。
原则上,topologyKey
可以使用任意合法的标签Key
赋值,但是出于性能和安全方面的考虑,对topologyKey
有如下限制:
- 在Pod亲和性和RequiredDuringScheduling的Pod互斥性的定义中,不允许使用空的topologyKey;
- 如果Admission controller包含了LimitPodHardAntiAffinityTopology,那么针对Required DuringScheduling的Pod互斥性定义就被限制为kubernetes.io/hostname,要使用自定义的topologyKey,就要改写或禁用该控制器。
- 在PreferredDuringScheduling类型的Pod互斥性定义中,空的
topologyKey会被解释为kubernetes.io/hostname、failure-
domain.beta.kubernetes.io/zone 及 failure-domain.beta.kubernetes.io/region的组合。 - 如果不是上述情况,就可以采用任意合法的topologyKey了。
PodAffinity规则设置的注意事项如下:
- 除了设置Label Selector和topologyKey,用户还可以指定Namespace列表
进行限制。同样,使用Label Selector对Namespace进行选择,Namespace的定义 和Label Selector及topologyKey同级,省略Namespace的设置,表示使用定义了 affinity/anti-affinity的Pod所在的命名空间。如果Namespace被设置为空值 (“”),则表示所有命名空间. - 在所有关联requiredDuringSchedulingIgnoredDuringExecution的
matchExpressions 全都满足之后 ,系统才能将Pod调度到某个Node上。
06 污点与容忍
Taint(污点) 则正好相反,它让Node
拒绝Pod
的运行。简单地说,被标记为Taint
的节点就是存在问题的节点,比 如磁盘要满、资源不足、存在安全隐患要进行升级维护,希望新的Pod
不会被调度过来。
但被标记为Taint
的节点并非故障节点,仍是有效的工作节点,所以仍需将某些Pod
调度到这些节点上时,可以通过使用Toleration
属性来实现。
6.1.1 污点与容忍设置
在默认情况下,在Node
上设置一个或多个Taint
之后,除非Pod
明确声明能够容忍这些污点,否则无法在这些Node
上运行。
6.1.1.1 Node设置污点
可以用kubectl taint
命令为Node
设置Taint
信息:
kubectl taint nodes node1 key=value:NoSchedule • 1
描述:这个设置为node1
加上了一个Taint
,该Taint
的键为key
,值为value
,Taint
的效果是NoSchedule
,这意味着除非Pod
明确声明可以容忍这个Taint
,否则不会被调度到node1
上。
6.1.1.2 Pod声明容忍
在Pod
上声明容忍的例子如下,下面的两个Toleration
都被设置为可以容忍(Tolerate
)具有该Taint
的Node
,使得Pod
能够被调度到node1
上:
tolerations: - key: "key" operator: "Equal" value: "value" effect: "NoSchedule"
或者
tolerations: - key: "key" operator: "Exists" effect: "NoSchedule"
6.1.1.3 小结
Pod的Toleration声明中的key
和effect
需要与Taint
的设置保持一致,并且满足以下条件之一:
值 | 条件 |
key | 空的key 配合Exists 操作符能够匹配所有键和值 |
operator | 值是Exists (无须指定value ), operator 的值是Equal 并且value 相等, 如果不指定operator ,则默认值为Equal |
effect | 空的effect 匹配所有effect ,在上面的例子中,effect 的取值为NoSchedule ,还可以取值为PreferNoSchedule ,这个值的意思是优先,也可以算作NoSchedule 的软限制版本 - 一个Pod 如果没有声明容忍这个Taint ,则系统会尽量避免把这个Pod 调度到这一 节点上,但不是强制的 |
系统允许在同一个Node上设置多个Taint,也可以在Pod上设置多个Toleration。
Kubernetes调度器处理多个Taint和Toleration的逻辑顺序为:首先列出节点中所有的Taint,然后忽略Pod的Toleration能够匹配的部分,剩下的没被忽略的Taint就是对Pod的效果了。
6.1.2 特殊情况
下面是几种特殊情况:
- 如果在剩余的
Taint
中存在effect=NoSchedule
,则调度器不会把该Pod
调度到这一节点上; - 如果在剩余的
Taint
中没有NoSchedule
效果,但是有PreferNoSchedule
效果,则调度器会尝试不把这个Pod
指派给这个节点; - 如果在剩余的
Taint
中有NoExecute
效果,并且这个Pod
已经在该节点上运行,则会被驱逐; - 如果没有在该节点上运行,则也不会再被调度到该节点上。
例如,我们这样对一个节点进行Taint设置:
kubectl taint nodes node1 keyl=valuel:NoSchedule kubectl taint nodes node1 keyl=valuel:NoExecute kubectl taint nodes node1 key2=value2:NoSchedule
然后在Pod
上设置两个Toleration
:
tolerations: - key: "key1" operator: "Equal" value: "valuel" effect: "NoSchedule" - key: "key1" operator: "Equal" value: "valuel" effect: "NoExecute"
结果:
- 这样的结果是该
Pod
无法被调度到node1
上,这是因为第3个Taint
没有匹配的Toleration
。 - 但是如果该
Pod
已经在node1
上运行了,那么在运行时设置第3个Taint
,它还能继续在node1
上运行,这是因为Pod
可以容忍前两个Taint。
一般来说,如果给Node
加上effect=NoExecute
的Taint
,那么在该Node
上正在运行的所有无对应Toleration
的Pod
都会被立刻驱逐,而具有相应Toleration
的Pod
永远不会被驱逐。不过,系统允许给具有NoExecute
效果的Toleration
加入一 个可选tolerationSeconds
“字段,这个设置表明Pod
可以在Taint
添加到Node
之后还能在这个Node
上运行多久(单位为s
):
tolerations: - key: "key1" operator: "Equal" value: "valuel" effect: "NoExecute" tolerationSeconds: 3600
上述定义的意思是,如果Pod
正在运行,所在节点都被加入一个匹配的Taint
,则这个Pod
会持续在这个节点上存活3600s
后被逐出。如果在这个宽限期内Taint
被移除,则不会触发驱逐事件。
6.2 应用场景
Taint和Toleration是一种处理节点并且让Pod进行规避或者驱逐Pod的弹性处理方式,下面列举一些常见的用例。
6.2.1 独占节点
如果想要拿出一部分节点专门给一些特定应用使用,则可以为节点添加这样Taint
:
kubectl taint nodes nodename dedicated=groupName:NoSchedule
然后给这些应用的Pod
加入对应的Toleration
,这样,带有合适Toleration
的Pod
就会被允许同使用其他节点一样使用有Taint
的节点。
通过自定义Admission Controller
也可以实现这一目标。如果希望让这些应用独占一批节点,并且确保它们只能使用这些节点,则还可以给这些Taint
节点加入类似的标签dedicated=groupName
,然后Admission Controller
需要加入节点亲和 性设置,要求Pod
只会被调度到具有这一标签的节点上。
6.2.2 具有特殊硬件设备的节点
在集群里可能有一小部分节点安装了特殊的硬件设备(如GPU
芯片),用户自然会希望把不需要占用这类硬件的Pod
排除在外,以确保对这类硬件有需求的Pod
能够被顺利调度到这些节点上。
可以用下面的命令为节点设置Taint
:
kubectl taint nodes nodename special=true:NoSchedule kubectl taint nodes nodename special=true:PreferNoSchedule
然后在Pod
中利用对应的Toleration
来保障特定的Pod
能够使用特定的硬件。
和上面独占节点的示例类似,使用Admission Controller
来完成这一任务会更方便,例如:
- Admission Controller使用Pod的一些特征来判断这些Pod,如果可以使用这些件,就添加Toleration来完成这一工作;
- 要保障需要使用特殊硬件的Pod只被调度到安装这些硬件的节点上,则还需要一些额外的工作,比如将这些特殊资源使用opaque-int-resource的方式对自定义资源进行量化,然后在PodSpec中进行请求;
- 也可以使用标签的方式来标注这些安装有特别硬件的节点,然后在Pod 中定义节点亲和性来实现这个目标。
6.2.3 定义Pod驱逐行为,以应对节点故障
前面提到的NoExecute
这个Taint
效果对节点上正在运行的Pod
有以下影响:
- 没有设置Toleration的Pod会被立刻驱逐;
- 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋值,则会一直留在这一节点中;
- 配置了对应Toleration的Pod且指定了tolerationSeconds值,则会在指定的时间后驱逐(注意,在节点发生故障的情况下,系统将会以限速(rte- limiting)模式逐步给Node设置Taint,这样就能避免在一些特定情况下(比如
Master暂时失联)有大量的Pod被驱逐)。
注意,Kubernetes会自动给Pod添加下面几种Toleration:
- key为node.kubernetes.io/not-ready,并配置tolerationSeconds=300;
- key 为node.kubernetes.io/unreachable,并配置tolerationSeconds=300。
以上添加的这种自动机制保证了在某些节点发生一些临时性问题时,Pod默认能够继续停留在当前节点运行5min等待节点恢复,而不是立即被驱逐,从而避免系统的异常波动。
另外,Kubernetes从1.6版本开始引入两个与Taint相关的新特性,TaintNodesByCondition及TaintBasedEvictions,用来改善异常情况下的Pod调度与驱逐问题,比如在节点内存吃紧、节点磁盘空间已满、节点失联等情况下,是 否自动驱逐某些Pod或者暂时保留这些Pod等待节点恢复正常。这个过程的完整逻 辑基本如下。
- 不断地检查所有Node状态,设置对应的Condition;
- 不断地根据Node Condition设置对应的Taint;
- 不断地根据Taint驱逐Node上的Pod。
其中,检查Node
的状态并设置Node
的Taint
就是TaintNodesByCondition
特性,即在Node满足某些特定的条件时,自动为Node节点添加Taint,目前主要有以下几种条件:
条件 | 描述 |
node.kubernetes.io/not-ready:节点未就绪 | 对应NodeCondition Ready为False的情况 |
node.kubernetes.io/unreachable:节点不可触达 | 对应NodeCondition Ready.为Unknown的情况 |
node.kubernetes.io/out-of-disk | 节点磁盘空间已满 |
node.kubernetes.io/network-unavailable | 节点网络不可用 |
node.kubernetes.io/unschedulable | 节点不可调度 |
node.cloudprovider,kubernetes.io/uninitialized | 如果kubelet是由"外部"云服务商启动的,则该污点用来标识某个节点当前为不可用状态。在云控制器 (cloud-controller-manager)初始化这个节点以后,kubelet会将此污点移除 |
自Kubernetes 1.13开始,上述两个特性被默认启用,TaintNodesByCondition 这个特性只会为节点添加NoSchedule效果的污点,TaintBasedEviction则为节点添加NoExecute效果的污点。
在TaintBasedEvictions特性被开启之后,kubelet会在有资源压力时对相应的Node节点自动加上对应的NoExecute效果的Taint,例如 node.kubernetes.io/memory-pressure、node.kubernetes.io/disk-pressure。
如果Pod没有设置对应的Toleration,则这部分Pod将被驱逐,以确保节点不会崩溃。
07 优先级调度
对于运行各种负载(如:Service
、Job
)的中等规模或者大规模的集群来说,出于各种原因,我们需要尽可能提高集群的资源利用率。
提高资源利用率的常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许集群中的所有负载所需的资源总量超过集群可提供的资源,在这种情况下,当发生资源不足的情况时,系统可以选择释放一些不重要的负载(优先级最低的),保障最重要的负载能够获取足够的资源稳定运行。
7.1 案例
7.1.1 创建PriorityClass
首先,由集群管理员创建PriorityClass
(PriorityClass
不属于任何命名空间):
apiversion:scheduling.k8s.io/vlbetal kind:Priorityclass metadata: name:high-priority va1ue:1000000 globalDefault:false description:"This priority class should be used for XYZ service pods only."
上述YAML
文件定义了一个名为high-priority
的优先级类别,优先级为 100000
,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。
7.1.2 Pod声明优先级类别
可以在任意Pod
上引用上述Pod
优先级类别:
apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent priorityclassName: high-priority
如果发生了需要抢占的调度,高优先级Pod
就可能抢占节点N
,并将其低优先级Pod
驱逐出节点N
,高优先级Pod
的status
信息中的nominatedNodeName
字段会记录目标节点的名称。
需要注意,高优先级Pod
仍然无法保证最终被调度到节点N
上,在节点N
上低优先级Pod
被驱逐的过程中,如果有新的节点满足高优先级Pod
的需求,就会把它调度到新的Node
上。
而如果在等待低优先级的Pod
退出的过程中,又出现了优先级更高的Pod
,调度器就会调度这个更高优先级的Pod
到节点N
上,并重新调度之前等待的高优先级Pod
。
7.1.3 注意事项
优先级抢占的调度方式可能会导致调度陷入“死循环”状态。当Kubernetes
集群配置了多个调度器(Scheduler
)时,这一行为可能就会发生,比如下面这个例子:
Scheduler A
为了调度一个(批)Pod
,特地驱逐了一些Pod
,因此在集群中有了空余的空间可以用来调度,此时Scheduler B
恰好抢在Scheduler A
之前调度了一个新的Pod
,消耗了相应的资源,因此,当Scheduler A
清理完资源后正式发起Pod
的调度时,却发现资源不足,被目标节点的kubelet
进程拒绝了调度请求! 这种情况的确无解,因此最好的做法是让多个Scheduler相互协作来共同实现一个目标。
高优先级Pod
抢占节点并驱逐低优先级的Pod
,这个问题对于普通的服务型的
Pod
来说问题不大,但对于执行批处理任务的Pod
来说就可能是个灾难,当一个高 优先级的批处理任务的Pod
创建后,正在执行批处理任务的某个低优先级的Pod
可 能因为资源不足而被驱逐,从而导致对应的批处理任务被搁置。
为了避免这个问题发生,PriorityClass
增加了一个新的属性一preemptionPolicy
,当它的值为 preemptionLowerPriorty
(默认)时,就执行抢占功能,当它的值被设置为Never
时,就默认不抢占资源,而是静静地排队,等待自己的调度机会。