对于运行各种负载(如Service、Job)的中等规模或者大规模的集群来说,出于各种原因,我们需要尽可能提高集群的资源利用率。而提高资源利用率的常规做法是采用优先级方案,即不同类型的负载对应不同的优先级,同时允许集群中的所有负载所需的资源总量超过集群可提供的资源,在这种情况下,当发生资源不足的情况时,系统可以选择释 放一些不重要的负载(优先级最低的),保障最重要的负载能够获取足够的资源稳定运行。
在Kubernetes 1.8
版本之前,当集群的可用资源不足时,在用户提交新的Pod
创建请求后,该Pod
会一直处于Pending
状态,即使这个Pod
是一个很重要(很有身份)的Pod
,也只能被动等待其他Pod被删除并释放资源,才能有机会被调度成功。
Kubernetes 1.8
版本引入了基于Pod
优先级 抢占Pod Priority Preemption
的调度策略,此时Kubernetes
会尝试释放目标节点上低优先级的Pod
,以腾出空间(资源)安置高优先级的Pod
,这种调度方式被称为“抢占式调度”。在Kubernetes 1.11
版本中,该特性升级为Beta
版本,默认开启,在后继的Kubernetes 1.14
版本中正式Release
。如何声明一个负载相对其他负载“更重要”?我们可以通过以下几个维度来定义:
- Priority,优先级
- QoS,服务质量等级
- 系统定义的其他度量指标
优先级抢占调度策略的核心行为分别是驱逐Eviction
与抢占Preemption
,这两种行为的使用场景不同,效果相同。Eviction
是kubelet
进程的行为,即当一个Node
发生资源不足under resource pressure
的情况时,该节点上的kubelet
进程会执行驱逐动作,此时Kubelet
会综合考虑Pod
的优先级、资源申请量与实际使用量等信息来计算哪些Pod
需要被驱逐;当同样优先级的Pod
需要被驱逐时,实际使用的资源量超过申请量最大倍数的高耗能Pod
会被首先驱逐。对于QoS
等级为Best Effort
的Pod
来说,由于没有定义资源申请CPU/Memory Request
,所以它们实际使用的资源可能非常大。Preemption
则是Scheduler
执行的行为,当一个新的Pod
因为资源无法满足而不能被调度时,Scheduler
可能(有权决定)选择驱逐部分低优先级的Pod
实例来满足此Pod
的调度目标,这就是Preemption
机制。
需要注意的是,Scheduler
可能会驱逐Node A
上的一个Pod
以满足Node B
上的一个新Pod
的调度任务。比如下面的这个例子:
一个低优先级的Pod A在Node A(属于机架R)上运行,此时有一个高优先级的 Pod B等待调度,目标节点是同属机架R的Node B,他们中的一个或全部都定义了antiaffinity规则,不允许在同一个机架上运行,此时Scheduler只好“丢车保帅”,驱逐低优 先级的Pod A以满足高优先级的Pod B的调度。
Pod
优先级调度示例如下
首先,由集群管理员创建PriorityClasses
,PriorityClass
不属于任何命名空间:
---
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."
上述YAML
文件定义了一个名为high-priority
的优先级类别,优先级为100000
,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。
可以在任意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
字段会记录目标节点N
的名称。需要注意,高优先级Pod
仍然无法保证最终被调度到节点N
上,在节点N
上低优先级Pod
被驱逐的过程中,如果有新的节点满足高优先级Pod
的需求,就会把它调度到新的Node
上。而如果在等待低优先级的Pod
退出的过程中,又出现了优先级更高的Pod
,调度器将会调度这个更高优先级的Pod
到节点N
上,并重新调度之前等待的高优先级Pod
。
优先级抢占的调度方式可能会导致调度陷入“死循环”状态。当Kubernetes
集群配置了多个调度器Scheduler
时,这一行为可能就会发生,比如下面这个例子:
Scheduler A为了调度一个(批)Pod,特地驱逐了一些Pod,因此在集群中有了空 余的空间可以用来调度,此时Scheduler B恰好抢在Scheduler A之前调度了一个新的 Pod,消耗了相应的资源,因此,当Scheduler A清理完资源后正式发起Pod的调度时, 却发现资源不足,被目标节点的kubelet进程拒绝了调度请求!这种情况的确无解,因 此最好的做法是让多个Scheduler相互协作来共同实现一个目标。
最后要指出一点:使用优先级抢占的调度策略可能会导致某些Pod
永远无法被成功调度。因此优先级调度不但增加了系统的复杂性,还可能带来额外不稳定的因素。因此,一旦发生资源紧张的局面,首先要考 虑的是集群扩容,如果无法扩容,则再考虑有监管的优先级调度特性, 比如结合基于Namespace
的资源配额限制来约束任意优先级抢占行为。
文章参考来源:《kubernetes权威指南-第4版》