最直白的调度方式,在Pod的资源清单中指定Node的名字,跳过kube-scheduler的调度逻辑,强制将Pod调度到指定的物理节点上。
1 | spec: |
节点选择器,首先通过kubernetes的label-selector机制进行节点选择,把Pod调度到指定的具有匹配标签的物理节点上,之后再由kube-scheduler根据调度算法从匹配的Node中挑选一个可用的Node。比如:
1 | spec: |
如果没有节点具有指定的标签,将直接调度失败。
补充:nodeSelector当前还在继续使用,但随着节点亲和性和反亲和性越来越能体现nodeSelector所具备的功能,最终nodeSelector会被遗弃。
实际业务场景中,总有一些彼此依赖的服务,需要调度到同一个地方,即它们是亲和的,于是有了亲和性;同时考虑到冗余性,也需要将一些服务调度到不同的地方,于是有了反亲和性。
亲和性和反亲和性都是通过标签选择器实现的。
有些场景下,有些服务必须是亲和的,即它们必须在一起;或者必须分开;而有些场景下,并不是硬性要求,只是为了达到更好的效果,它们只要尽量在一起,或者尽量分开一点。于是有了两种策略:
硬策略
Pod的调度必须满足规则,如果不满足,Pod将无法调度,一直处于Pending状态。
软策略
在Pod调度时,可以尽量满足其规则,在无法满足规则时,也可以调度到一个不匹配规则的节点上,并且可以设置不同规则的权重。
为了更灵活地满足各种亲和以及反亲和的场景,kubernetes还将亲和性分为两种维度:
节点维度
标签选择器选择的是节点的标签
Pod维度
标签选择器选择的是Pod的标签
软硬策略可以同时存在,也可以只存在于一个,比如:
1 | spec: |
节点亲和支持以下操作符:
注意:
无论是软策略( preferredDuringSchedulingIgnoredDuringExecution
)还是硬策略( requiredDuringSchedulingIgnoredDuringExecution
),它们的名字后面都有一个 IgnoredDuringExecution
:在Pod执行阶段被忽略,意思是亲和性和反亲和性都只是在Pod的调度阶段被考虑,在Pod的执行过程汇总被忽略,意味着在Pod调度到节点后,即使发生了标签变化等事件,导致其他Pod与该Pod的亲和性或反亲和性规则不再满足,Kubernetes也不会因为这些规则而重新调度或迁移该Pod。
在nodeAffinity的nodeSelectorTerms中,如果设置了多条matchExpressions,那么只需要满足其一即可;如果是在一条matchExpressions中设置了多条规则,需要同时满足。
在节点维度并没有反亲和的字段,因为使用NotIn、DoesNotExist的操作符就可以实现反亲和的功能了。
Pod亲和与节点亲和大致逻辑相同但略有差别,比如:
1 | spec: |
注意:
Pod的反亲和也很好理解,把上面Pod亲和性例子中的podAffinity
字段换成podAntiAffinity
即可,代表新Pod一定不会被调度到一个带有security=s1标签的Pod的拓扑域中。
拓扑域的名号听起来比较高深,本质上也只是一个标签,跟其他的标签并没有什么不同。
虽然技术上并没有什么本质区别,但我们仍然需要一个拓扑域的概念,因为在集群规模比较大的情况下, 我们需要进行跨集群、跨机房、跨地域的调度,比如集群中有10台节点具有标签zone=shanghai,另外10台节点具有标签zone=beijing,那么它们组成了两个拓扑域。
如果在Pod亲和性中指定了topologyKey: zone
,代表以zone为拓扑域,恰好shanghai的拓扑域的10台节点中存在符合规则的Pod,而beijing的拓扑域中不存在这样的Pod,那么这个Pod就会被调度的shanghai拓扑域中的这10台节点中的某一台,具体会调度到这10台的哪一台上面,还要根据设置的其他规则来决定。
在有些场景下,我们可能希望专门的节点有专门的用途,也就是说,某些特殊节点专门留给某些特定Pod使用,虽然说通过NodeSelector也可以实现类似的功能,但是NodeSelector只能实现特定Pod能够调度到特殊节点上,并不能保证其他的Pod不会调度到这些特殊节点上。于是有了污点和容忍度的概念。
污点(taints)是定义在节点上的一组键值型属性数据,污点并不是一个键值,而是一个键值型属性数据,它是依附于键值的(依附于某一标签),用来让节点拒绝Pod调度,除非这些Pod具有容纳相应污点的容忍度(tolerations)。
为节点打上污点:
1 | kubectl taint nodes <nodeName> key=value:effect |
为节点去除污点
1 | kubectl taint nodes <nodeName> key=value:effect- |
污点有三种类型:
PreferNoSchedule
尽量不被调度,属于软策略,如果没有其他满足要求的节点,也可以被调度在上面。
NoSchedule
一定不被调度,除非Pod具备相应的Tolerations,但是现存的Pod不受影响。
NoExecute
不允许运行,只有具有相应Tolerations的Pod可以被调度在上面,并且该节点上没有对应Tolerations的Pod都会被驱逐出去。
容忍度是定义在Pod的资源清单中的,比如:
1 | spec: |
容忍度具有四个字段:
在Pod的容忍度字段中,如果effect定义了NoExecute
,那么还需要定义一个字段tolerationSeconds
,它的意思是驱逐时间,代表在指定的时间过后才会被驱逐。
此时会有一个疑问,既然Pod具备了相应的容忍度,为什么还会被驱逐呢?既然要驱逐,为什么不是立刻驱逐,反而要设定一个驱逐时间,过了指定的时间后才驱逐?
这是因为NoExecute
这种污点通常并不是我们手动添加的,而是在节点挂掉或者节点上的kubelet挂掉时,kubernetes自动给节点添加的,此时该节点上的Pod一定会被驱逐出去,这就是第一个疑问的答案。
当上述情况发生之后,Pod会被驱逐到其他节点上,但是仍然可能会有一些具备NoExecute
容忍度的Pod,再次被调度到该节点上,然后又会发生驱逐,然后Pod再次被调度到该节点上,陷入死循环。此时设置tolerationSeconds
就很有必要了,它可以让Pod不会被快速驱逐,而是在指定的时间内继续留在该节点上,避免因为kubelet挂掉等临时性问题,导致重要服务频繁异常波动。
基于上面的场景,kubernetes会自动为Pod添加下面两种Toleration:
上面的Pod驱逐时间都是在kube-apiserver中定义的
1 | $ vim /etc/kubernetes/manifests/kube-apiserver.yam1 |
Pod均匀调度的意思是:让同一服务分散到不同的拓扑域中。之前的所有调度方案,无论是亲和或者反亲和,都是相对极端的方案,要么集中,要么互斥。无法让Pod均匀分布到所有的拓扑域中。
均匀调度并不是空想出来的需求,相反,同一服务在不同机房或者地域的均匀分布是实现容灾和高可用的核心,将业务 Pod 尽可能均匀的分布在不同可用拓扑域中是非常重要的。
在kubernetes 1.16版本中首次引入了 Even Pod Spreading 特性,通过识别拓扑域属性,设置参数 topologySpreadConstraints
来将Pod调度到不同的拓扑域。该特性在kubernetes 1.18版本中提升到beta阶段并默认启用,在此之前需要手动启用。具体使用方法如下:
1 | spec: |
具体字段的解释如下:
skew = 当前拓扑域中存在的具备选定标签的Pod个数 - 所有拓扑域中具备选定标签的Pod的最小个数
,所有的拓扑域的skew值都不可以超过这个maxSkew值,达到均匀分布的效果。虽然kubernetes中提供了均匀调度的机制,但是仍然存在局限性,比如它会把具有污点的节点上运行着的具备对应容忍度的Pod也考虑在内,而实际场景下这种Pod往往是作为特殊情况处理的,再比如均匀调度也只是再新建并调度Pod时生效,如果所见某Deployment中Pod的规模导致分布不再均匀,这时均匀调度机制无法自动调谐;再比如停机维护某个节点,事先驱逐排空了所有的Pod,维护完成之后,Pod并不会自动回到原来的节点上,以上等等场景,都会导致集群在一段时间内处于不均衡的状态。
为了真正解决上述问题,可以引入一个均衡器 descheduler,它会对集群内的Pod进行调度优化,以解决实际运行过程中集群资源无法充分利用的问题。
descheduler的核心原理时根据其配置的策略,找到应该被移除的Pod然后驱逐它们,从而触发Pod的重新调度。也就是说,descheduler本身并不会参与调度,它的作用是驱逐Pod,被驱逐的Pod会由默认的调度器重新调度。
部署descheduler
1 | # helm方式部署,添加descheduler仓库 |
查看descheduler的资源清单,可以发现其中定义的一些规则
1 | plugins: # 启用的相关插件/策略 |
使用descheduler的注意事项:
关键性 Pod 不会被驱逐,比如 priorityClassName
设置为 system-cluster-critical
或 system-node-critical
的 Pod。
只有 ReplicaSet、Deployment 或 Job 管理的 Pod 会被驱逐,其他控制器比如DaemonSet、StatefulSet管理的Pod不会被驱逐。
使用 LocalStorage
的 Pod 不会被驱逐,除非设置了 evictLocalStoragePods: true
具有 PVC 的 Pods 不会被驱逐,除非设置 ignorePvcPods: true
Pod是按照优先级进行驱逐的,参考Qos质量等级划分。
annotations
中带有 descheduler.alpha.kubernetes.io/evict
字段的 Pod 都可以被驱逐,该注释用于覆盖阻止驱逐的检查。
如果驱逐违反 PDB 约束,则不会驱逐这类 Pod。如果 Pod 驱逐失败,可以设置 --v=4
从 descheduler
日志中查找原因。
但凡涉及驱逐,一定是有风险的,比如对于单副本的Pod,一旦被驱逐服务就中断了。即便是多副本的Pod,也仍有可能多个Pod都被驱逐,出现服务中断情况,这时候需要引入PDB策略。
PDB,Pod Disruption Budget
(pod中断预算) ,设置一个最大不可用的Pod数,或者最小可用的Pod数,如果发生终止pod的情况,需要先通过 labelSelector 机制获取正常运行的pod数目,避免所有的Pod同时不可用。比如
1 | apiVersion: policy/v1 |
如果使用了descheduler来动态平衡集群状态,那么强烈建议给应用创建一个对应的PodDisruptionBudget对象,来对抗descheduler带来的负面效果。
上面介绍了多种Pod调度的策略,实际场景中还会遇到一种情况,就是Pod想要分配到某个节点上,但是该节点的资源不足,然而这些Pod又很重要,不能让它们一直处于Pending状态,此时需要把该节点上一些不太重要的Pod驱逐掉,以便腾出资源给这些重要的Pod使用,为此kubernetes推出了优先级抢占调度。
抢占(Preemption):指的是终止低优先级的Pod,以便于高优先级的Pod可以正常调度运行。
负责优先级抢占调度的组件时kube-scheduler,抢占的依据是Pod的优先级。
要定义Pod的优先级,需要先定义PriorityClass资源,该资源是跨命名空间的,如下:
1 | apiVersion: scheduling.k8s.io/v1 |
定义了PriorityClass资源之后,在Pod的资源清单中指定priorityClassName
即可:
1 | spec: |
注意:
优先级抢占调度在选择抢占目标时不考虑Qos
优先级抢占调度的优先程度高于其他调度规则。
如果低优先级的Pod被PDB策略保护,会考虑抢占更高优先级的Pod。
Pod 驱逐(Pod Eviction)是指将 Pod 从当前运行的节点上移除的过程。这个过程可能会导致 Pod 被终止并重新调度到其他节点。 其实在以下两种情况下会发生pod的驱逐:
节点不可用驱逐是由Kube-controller-manager负责的,Kube-controller-manager会周期性检查所有的节点状态,当节点处于 NotReady 状态超过一段时间后,驱逐该节点上所有 pod。
节点压力驱逐
节点压力驱逐是由kubelet负责的,kubelet会周期性检查当前节点的资源,当资源不足时,按照pod的Qos等级驱逐部分Pod。
节点不可用,可能是由于宕机或者kubelet挂掉引起的,总之一句话就是本节点没有活着的kubelet进程可以上报自己的状态。
节点不可用判断过程:
每个节点上的 kubelet 定期(默认间隔 10 秒)向 kube-apiserver 上报自身的健康状态、资源使用情况等。
节点控制器(node controller,包含在 kube-controller-manager 中)每5秒(node-monitor-period参数)进行一次节点状态检测,主要是通过检查 kube-apiserver 中节点对象的状态信息。
当 node 失联一段时间,默认是超过40s(ode-monitor-grace-period 参数)之后,判定该 node 为 Not Ready状态。
经过300s的故障自愈时间( pod-eviction-timeout 参数)之后,node 仍然是失联状态,此时开始触发驱逐。
注意:在 Kubernetes v1.27 版本中,--pod-eviction-timeout
参数被标记为已弃用,取而代之的是节点挂掉后k8s为节点添加NoExecute的污点,并且在此之前默认为每个pod都添加了容忍且设置了多久后才会驱 逐。
节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。
kubelet 会监控集群节点的内存、磁盘空间和文件系统的 inode 等资源。 当这些资源中的一个或者多个达到特定的消耗水平, kubelet 可以主动地使节点上一个或者多个 Pod 失效,以回收资源防止饥饿。
在k8s集群中,节点最重要的资源包括cpu、内存和磁盘,其中内存和磁盘都属于不可压缩资源,kubelet中设置了一些阈值,比如内存低于多少,磁盘低于多少,就达到了阈值(称为驱逐条件),一旦达到了阈值,kubeket会上报自己的状态,节点控制器会变更这些节点的状态为 MemoryPressure: True
或者 DiskPressure: True
(称为驱逐信号),然后kubelet会根据自己的驱逐机制,一般是Qos质量等级,来做出驱逐决定和操作。
驱逐分为软驱逐和硬驱逐:
软驱逐
软驱逐在 node 的资源达到一定的阈值后,会观察一段时间,如果改善到低于阈值就不进行驱逐,若这段时间一直高于阈值就进行驱逐,软驱逐Kill Pod地时候会设定一个grace-period,等待Pod优雅地退出。软驱逐的一个重要参数是--eviction-pressure-transition-period
,也就是宽限期,默认是5分钟,该参数可以防止在某些情况下,避免因短期波动而做出不必要的驱逐决策。
硬驱逐简单粗暴,没有宽限期,一旦达到阈值配置,kubelet立马回收关联的短缺资源,将pod kill 掉,而不是优雅终止。
驱逐的优先级:
当资源紧缺时,驱逐pod的优先级大体为 : BestEffort > Burstable > Guaranteed,这是大前提