23. 资源的调度——Pod 优先级调度
本章讲解知识点
- Pod 优先级调度
- QoS
<br>
1. Pod 优先级调度
1.1 前言
出于各种原因,对于运行各种负载(如:Deployment、StatefulSet、DeamonSet)的中等规模或大规模集群,我们需要尽可能提高其资源利用率。
一种常见的提高资源利用率的方法是采用优先级方案,即为不同类型的负载分配不同的优先级。同时,允许所有负载所需的资源总量超过集群可提供的资源。在资源不足的情况下,系统可以根据优先级释放一些不重要的负载(优先级最低的),以保障最重要的负载能够获取足够的资源稳定运行。
在 Kubernetes 1.8 版本之前,当集群的可用资源不足时,在用户提交新的 Pod 创建请求后,该 Pod 会一直处于 Pending 状态,即使这个 Pod 是一个很重要的 Pod,也只能被动等待其他 Pod 被删除并释放资源,才能有机会被调度成功。Kubernetes 1.8 版本引入了基于 Pod 优先级抢占的调度策略,此时 Kubernetes 会尝试释放目标节点上低优先级的 Pod,以腾出空间(资源)安置高优先级的 Pod,这种调度方式被称为“抢占式调度”。在 Kubernetes 1.11 以后版本,该特性默认开启。但如何声明一个负载相对其他负载更重要?我们可以通过以下几个维度来定义:Priority:优先级;QoS:服务质量等级;系统定义的其他度量指标。
1.2. Pod 优先级调度概念
Pod 优先级调度是 Kubernetes 中的一种调度机制,它可以根据 Pod 的优先级来调度节点,以确保高优先级的 Pod 能够优先调度到可用资源最充足的节点上。在 Kubernetes 中,每个 Pod 都有一个优先级值,优先级越高的 Pod 会被优先考虑调度。
优先级值定义了 Pod 的相对重要性,范围从 0 到 1000000000。如果没有设置优先级,所有的 Pod 都将被视为默认优先级(0)。
优先级抢占调度策略的核心行为分别是驱逐(Eviction)与抢占(Preemption),这两种行为的使用场景不同,效果相同。
Eviction 是 kubelet 进程的行为,即当一个 Node 资源不足(under resource pressure)时,该节点上的 kubelet 进程会执行驱逐动作(即将 Pod “赶出”此节点,Pod 可能处于 Pending 状态),此时 kubelet 会综合考虑 Pod 的优先级、资源申请量与实际使用量等信息来计算哪些 Pod 需要被驱逐;当同样优先级的 Pod 需要被驱逐时,实际使用的资源量超过申请量(Request)最大倍数的高耗能 Pod 会被首先驱逐。对于 QoS 等级为“Best Effort”的 Pod 来说,由于没有定义资源申请(CPU/Memory Request),所以它们实际使用的资源可能非常大。
Preemption 抢占则是 Scheduler 执行的行为,当一个新的 Pod 因为资源无法满足而不能被调度时,Scheduler 可能(有权决定)选择驱逐部分低优先级的 Pod 实例来满足此 Pod 的调度目标,这就是 Preemption 机制。
1.3. 何如设置 Pod 优先级调度
首先,由集群管理员创建 PriorityClass(PriorityClass 不属于任何命名空间):
apiversion:scheduling.k8s.io/vlbetal 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,数字越大,优先级越高,超过一亿的数字被系统保留,用于指派给系统组件。
创建后,查看 priorityClass 列表,可以看到新建立的 priorityclass:high-priority。优先级项列表中还存在着两个原生的系统优先级,具有很高的优先级权重:
# kubectl get priorityclass NAME VALUE GLOBAL-DEFAULT AGE high-priority 1000000 false 26s system-cluster-critical 2000000000 false 11d system-node-critical 2000001000 false 11d
我们可以在任意 Pod 上引用上述 Pod 优先级类别:
apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent priorityclassName: high-priority // 引用优先级类别
可以定义不同的优先级类别(PriorityClass),设置不同的优先级值(value),供不同的 Pod 使用。
如果发生了需要抢占的调度,高优先级 Pod 就可能抢占节点 N,并将其低优先级 Pod 驱逐出节点 N,高优先级 Pod 的 status 信息中的 nominatedNodeName 字段会记录目标节点的名称。
需要注意,高优先级 Pod 仍然无法保证最终被调度到节点 N 上,在节点 N 上低优先级 Pod 被驱逐的过程中,如果有新的节点满足高优先级 Pod 的需求,就会把它调度到新的 Node 上。
而如果在等待低优先级的 Pod 退出的过程中,又出现了优先级更高的 Pod,调度器就会调度这个更高优先级的 Pod 到节点 N 上,并重新调度之前等待的高优先级 Pod。
优先级抢占的调度方式可能会导致调度陷入“死循环”状态。当 Kubernetes 集群配置了多个调度器(Scheduler)时,这一行为可能就会发生,比如下面这个例子:
为了调度一个(批)Pod,Scheduler A 驱逐了一些 Pod 以腾出资源。然而,在此之后,Scheduler B 却在 Scheduler A 之前调度了一个新的 Pod,消耗了相应的资源。因此,当 Scheduler A 完成资源清理后开始正式发起 Pod 的调度时,却发现资源不足,被目标节点的 kubelet 进程拒绝了调度请求。这种情况无法避免,因此最好的方法是让多个调度器相互协作,共同实现一个目标。
高优先级 Pod 抢占节点并驱逐低优先级的 Pod,对于普通的服务型 Pod 可能不是大问题,但对于执行批处理任务的 Pod 来说,这可能是个灾难。当一个高优先级的批处理任务的 Pod 创建后,正在执行批处理任务的某个低优先级的 Pod 可能会因为资源不足而被驱逐,从而导致对应的批处理任务被搁置。
为了避免这个问题的发生,PriorityClass 增加了一个新的属性,即 preemptionPolicy。当 preemptionPolicy 的值为 preemptionLowerPriority(默认)时,会执行抢占功能,而当它的值被设置为 Never 时,就默认不会抢占资源,而是静静地排队,等待自己的调度机会。
抢占能力是通过 kube-scheduler 的标志 disablePreemption 来控制的,该标志默认为 false。 如果在了解上述提示的前提下仍希望禁用抢占,可以将 disablePreemption 设置为true。
apiVersion: kubescheduler.config.k8s.io/v1alpha1 kind: KubeSchedulerConfiguration algorithmSource: provider: DefaultProvider ... disablePreemption: true
<br>
2. QoS
2.1 前言
在 Kubernetes 的 QoS 体系中,确保高可靠性 Pod 申请可靠资源,而非高可靠性的 Pod 则可以申请较低或不可靠的资源。具体来说,容器的资源配置分为 Requests 和 Limits 两部分,其中 Requests 表示 Kubernetes 在调度时能够为容器提供的完全可保障的资源量,是最低保障的值;而 Limits 则是系统允许容器在运行时可能使用的资源量上限,是最高上限的值。Pod 级别的资源配置是通过计算 Pod 内所有容器的资源配置总和得出的。
Kubernetes 中 Pod 的 Requests 和 Limits 资源配置有如下特点。
- 如果 Pod 配置的 Requests 等于 Limits 值,那么该 Pod 可以获得的资源是完全可靠的。
- 如果 Pod 配置的 Requests 小于 Limits 值,那么该 Pod 可以获得的资源可分为两部分: 完全可靠的资源,资源量的大小等于 Requests 值;不可靠的资源,资源量最大等于 Requests 与 Limits 的差额,这份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余量。
Kubernetes 可以通过节点资源的超售机制实现更高的资源利用率。例如,当某台机器的 CPU 完全充足时,其可用内存为 32GiB,而容器的资源请求值(Requests)为 1GiB,资源限制值(Limits)为 2GiB。在这种情况下,最多可以同时运行 32 个容器,每个容器最多使用 2GiB 内存。如果这些容器的内存使用峰值能够错开,那么所有容器都可以正常运行,这进一步提高了系统的资源利用率。
超售机制能有效提高资源的利用率,也不会影响容器申请的完全可靠资源的可靠性。
2.2 QoS 概念
在一个超用(Over Committed,容器 Limits 总和大于系统容量上限)系统中,容器负载的波动可能导致操作系统的资源不足,最终导致部分容器被杀死。在这种情况下,我们当然会希望优先杀死那些不太重要的容器,那么就要对容器划分重要等级。
QoS(Quality of Service)是指 Kubernetes 中根据 Pod 所需资源的限制(包括 CPU 和内存等)将 Pod 分为三种不同的质量等级(QoS等级),用于控制 Pod 优先级和驱逐行为。
三种 QoS 等级分别为:
- Guaranteed(完全可靠):Pod 被保证有足够的资源(CPU、内存等),即 Pod 的资源请求等于其资源限制(Requests = Limits),并且只有当节点上没有足够资源时,Pod 才会被驱逐。
- Burstable(弹性波动,较为可靠):Pod 的资源请求小于其资源限制(Requests < Limits),即 Pod 的资源限制可以被临时超过,但不能持续超过。Pod 可以被驱逐以确保节点上的其他 Pod 不会因为缺少资源而受到影响。
- BestEffort(尽力而为,不太可靠):Pod 没有任何资源限制或请求(即不设置 Requests 和 Limits),可以利用节点上未被使用的所有资源,但不能保证其可靠性和可用性。在节点资源不足时,首先会驱逐 BestEffort 等级的 Pod。
2.3 示例
下面是三种 QoS 等级的 Pod 定义示例:
1.Guaranteed QoS 等级:Requests = Limits
spec: containers: - name: nginx-container image: nginx:latest resources: requests: cpu: 1000m memory: 1Gi limits: cpu: 1000m memory: 1Gi
2.Burstable QoS 等级:Requests < Limits
spec: containers: - name: nginx-container image: nginx:latest resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1000m memory: 1Gi
3.BestEffort QoS 等级:不设置 Requests 和 Limits
apiVersion: v1 kind: Pod metadata: name: besteffort-pod spec: containers: - name: nginx-container image: nginx:latest
2.4 Kubernetes QoS 的工作特点
在 Pod 的 CPU Requests 无法得到满足(比如节点的系统级任务占用过多的 CPU 导致无法分配足够的 CPU给容器使用)时,容器得到的 CPU 会被压缩限流。
由于内存是不可压缩的资源,所以针对内存资源紧缺的情况,会按照以下逻辑处理。
- BestEffort Pod 的优先级最低,它们没有设置资源 Limits,因此在资源充足时可以充分使用所有闲置资源。但是在系统内存紧缺时,BestEffort Pod 中运行的进程会被系统优先杀死。
- Burstable Pod 的优先级居中,它们在初始时只会被分配较少的可靠资源,但可以按需申请更多的资源。在系统内存紧缺时,如果没有 BestEffort 容器可以被杀死以释放资源,那么这类 Pod 中运行的进程也可能会被杀死。
- Guaranteed Pod 的优先级最高,一般情况下只要不超过其资源 Limits 的限制,这类 Pod 就不会被系统杀死。但是在系统内存紧缺时,如果没有其他更低优先级的容器可以被杀死以释放资源,那么这类 Pod 中运行的进程也可能会被系统优先杀死。
2.5 OOM 计分规则
OOM(Out Of Memory)计分规则是 Kubernetes 中用于计算 Pod 内存使用情况的一种评分规则,通常用于 Pod 的 QoS 等级判断。
计分规则包括如下内容。
- OOM 计分的计算方法:计算进程所使用的内存在系统中所占的百分比,取其中不含百分号的数值,再乘以 10,该结果是进程 OOM 的基础分;将进程 OOM 基础分的分值再加上这个进程的 OOM_SCORE_ADJ(分数调整)值,作为进程 OOM 的最终分值(除 root 启动的进程外)。在系统发生 OOM 时,OOM Killer 会优先杀死 OOM 计分更高的进程。
- 进程的 OOM 计分的基本分数值范围是 0 ~ 1000,如果 A 进程的调整值 OOM_SCORE_ADJ 减去 B 进程的调整值的结果大于 1000,那么 A 进程的 OOM 计分最终值必然大于 B 进程,会优先杀死 A 进程。
- 不论调整 OOM_SCORE_ADJ 值为多少,任何进程的最终分值范围也是 0 ~ 1000。
在 Kubernetes 中,不同 QoS 的 OOM 计分调整值如表所示:
Guaranteed |
-998 |
BestEffort |
1000 |
Burstable |
min(max(2,1000-(1000
memoryRequestBytes)
machineMemoryCapacityBytes),999) |
对表中的说明如下:
- BestEffort Pod 设置 OOM_SCORE_ADJ 调整值为 1000,因此 BestEffort Pod 中容器里所有进程的 OOM 最终分都是 1000。
- Guaranteed Pod 设置 OOM_SCORE_ADJ 调整值为 -998,因此 Guaranteed Pod 中容器里所有进程的 OOM 最终分一般是 0 或者 1。
- 如果 Burstable Pod 请求的内存超过系统可用内存的 99.8%,那么该 Pod 的 OOM_SCORE_ADJ 调整值将被固定为 2。否则,OOM_SCORE_ADJ 调整值将设置为 1000 - 10 x (% of memory requested)。如果内存请求为 0,则 OOM_SCORE_ADJ 调整值将被固定为 999。这样的规则确保了 OOM_SCORE_ADJ 调整值的范围为 2 到 999,而 Burstable Pod 中所有进程的 OOM 最终得分范围为 2 到 1000。由于 Burstable Pod 进程的 OOM 最终得分始终高于 Guaranteed Pod 进程的得分,因此它们会被优先杀死。如果 Burstable Pod 使用的内存少于请求的内存,则可以确定其所有进程的 OOM 最终得分将小于 1000。在这种情况下,确
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专刊适合于立志转行云计算的小白,有一定的编程、操作系统、计算机网络、数据结构、算法基础。 本专刊同时也适合于面向云计算(Docker + Kubernetes)求职的从业者。 本专刊囊括了云计算、VMWare、Docker、Kubernetes、Containerd等一系列知识点的讲解,并且最后总