本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
使用 PriorityClass 保护你的关键任务 Pod 免于驱逐
Kubernetes 已被广泛采用,许多组织将其作为运行需要频繁创建和删除的工作负载的实际编排引擎。
因此,对 Pod 进行适当的调度是确保应用 Pod 在 Kubernetes 集群内正常运行而没有任何问题的关键。本文深入探讨了利用 PriorityClass 对象进行资源管理的用例,以保护任务关键型或高优先级的 Pod 免遭驱逐,并确保应用 Pod 正常运行并提供服务。
Kubernetes 中的资源管理
控制平面由多个组件组成,其中调度器(通常是内置的 kube-scheduler)是负责为 Pod 分配节点的组件之一。
每当一个 Pod 被创建时,它会进入“Pending(悬决)”状态,之后调度器会确定哪个节点最适合放置这个新 Pod。
在后台,调度器以无限循环的方式运行,寻找那些未设置 nodeName
且准备好调度的 Pod。对于每个需要调度的 Pod,调度器会尝试决定哪个节点应该运行该 Pod。
如果调度器找不到任何节点,Pod 将保持在 Pending 状态,这并不理想。
说明
例如,nodeSelector
、污点和容忍度
、nodeAffinity
、基于可用资源(例如 CPU 和内存)对节点进行排序,以及其他一些标准,都被用来确定 Pod 的放置位置。下图从第 1 点到第 4 点解释了请求流程
Kubernetes 中的调度
典型用例
以下是一些可能需要控制 Pod 调度和驱逐的真实场景。
假设你计划部署的 Pod 非常关键,但你有一些资源限制。一个例子是像 Grafana Loki 这样的基础设施组件的 DaemonSet。Loki Pod 必须在其他 Pod 之前在每个节点上运行。在这种情况下,你可以通过手动识别和删除不需要的 Pod 或向集群添加新节点来确保资源可用性。但这两种方法都不合适,因为前者执行起来很繁琐,而后者可能涉及时间和金钱的支出。
另一个用例可能是一个单一集群,其中包含以下环境的 Pod,并具有相关的优先级
- 生产环境 (
prod
): 最高优先级 - 预生产环境 (
preprod
): 中等优先级 - 开发环境 (
dev
): 最低优先级
在集群中资源消耗高的情况下,节点上的 CPU 和内存资源会产生竞争。虽然集群级别的自动扩缩容*可能*会添加更多节点,但这需要时间。在此期间,如果没有更多的节点来扩展集群,一些 Pod 可能会保持在 Pending 状态,或者因为它们争夺资源而导致服务降级。如果 kubelet 确实从节点上驱逐一个 Pod,该驱逐将是随机的,因为 kubelet 没有任何关于驱逐哪些 Pod 和保留哪些 Pod 的特殊信息。
- 生产环境 (
第三个例子可能是一个由队列应用或数据库支持的微服务,当它遇到资源紧张时,队列或数据库被驱逐。在这种情况下,所有其他服务都将变得无用,直到数据库能够再次提供服务。
也可能存在其他你想要控制 Pod 调度顺序或驱逐顺序的场景。
Kubernetes 中的 PriorityClasses
PriorityClass 是 Kubernetes 中的一个集群范围的 API 对象,属于 scheduling.k8s.io/v1
API 组。它包含 PriorityClass 名称(在 .metadata.name
中定义)和整数值(在 .value
中定义)的映射。这代表了调度器用来确定 Pod 相对优先级的数值。
此外,当你使用 kubeadm 或托管的 Kubernetes 服务(例如,Azure Kubernetes Service)创建集群时,Kubernetes 会使用 PriorityClasses 来保护托管在控制平面节点上的 Pod。这确保了像 CoreDNS 和 kube-proxy 这样的关键集群组件即使在资源受限的情况下也能运行。
Pod 的这种可用性是通过使用一个特殊的 PriorityClass 来实现的,该 PriorityClass 确保 Pod 能够正常运行,并且整个集群不受影响。
$ kubectl get priorityclass
NAME VALUE GLOBAL-DEFAULT AGE
system-cluster-critical 2000000000 false 82m
system-node-critical 2000001000 false 82m
下图通过一个例子准确地展示了它的工作原理,这将在接下来的部分中详细介绍。
Pod 调度和抢占
Pod 优先级和抢占
Pod 抢占是 Kubernetes 的一项功能,它允许集群根据优先级抢占 Pod(为了一个新的 Pod 而移除一个现有的 Pod)。Pod 优先级表示一个 Pod 在调度时相对于其他 Pod 的重要性。如果没有足够的资源来运行所有当前的 Pod,调度器会尝试驱逐低优先级的 Pod,而不是高优先级的。
此外,当一个健康的集群遇到节点故障时,通常会抢占低优先级的 Pod,以便为高优先级的 Pod 在可用节点上腾出空间。即使集群能够自动启动一个新节点,这种情况也会发生,因为创建 Pod 通常比启动一个新节点快得多。
PriorityClass 要求
在设置 PriorityClasses 之前,需要考虑一些事情。
- 决定需要哪些 PriorityClasses。例如,基于环境、Pod 类型、应用类型等。
- 为你的集群设置默认的 PriorityClass 资源。没有
priorityClassName
的 Pod 将被视为优先级为 0。 - 为所有 PriorityClasses 使用一致的命名约定。
- 确保你的工作负载的 Pod 使用正确的 PriorityClass 运行。
PriorityClass 实践示例
假设有 3 个应用 Pod:一个用于生产环境,一个用于预生产环境,一个用于开发环境。以下是针对每种环境的三个示例 YAML 清单文件。
---
# development
apiVersion: v1
kind: Pod
metadata:
name: dev-nginx
labels:
env: dev
spec:
containers:
- name: dev-nginx
image: nginx
resources:
requests:
memory: "256Mi"
cpu: "0.2"
limits:
memory: ".5Gi"
cpu: "0.5"
---
# preproduction
apiVersion: v1
kind: Pod
metadata:
name: preprod-nginx
labels:
env: preprod
spec:
containers:
- name: preprod-nginx
image: nginx
resources:
requests:
memory: "1.5Gi"
cpu: "1.5"
limits:
memory: "2Gi"
cpu: "2"
---
# production
apiVersion: v1
kind: Pod
metadata:
name: prod-nginx
labels:
env: prod
spec:
containers:
- name: prod-nginx
image: nginx
resources:
requests:
memory: "2Gi"
cpu: "2"
limits:
memory: "2Gi"
cpu: "2"
你可以使用 kubectl create -f <FILE.yaml>
命令创建这些 Pod,然后使用 kubectl get pods
命令检查它们的状态。你可以看到它们是否已启动并准备好提供流量
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
dev-nginx 1/1 Running 0 55s env=dev
preprod-nginx 1/1 Running 0 55s env=preprod
prod-nginx 0/1 Pending 0 55s env=prod
坏消息。生产环境的 Pod 仍处于 Pending 状态,没有提供任何流量。
让我们看看为什么会发生这种情况
$ kubectl get events
...
...
5s Warning FailedScheduling pod/prod-nginx 0/2 nodes are available: 1 Insufficient cpu, 2 Insufficient memory.
在这个例子中,只有一个工作节点,并且该节点资源紧张。
现在,让我们看看 PriorityClass 如何在这种情况下提供帮助,因为生产环境应该比其他环境具有更高的优先级。
PriorityClass API
在根据这些要求创建 PriorityClasses 之前,让我们看看一个 PriorityClass 的基本清单是什么样的,并概述一些先决条件
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: PRIORITYCLASS_NAME
value: 0 # any integer value between -1000000000 to 1000000000
description: >-
(Optional) description goes here!
globalDefault: false # or true. Only one PriorityClass can be the global default.
以下是 PriorityClasses 的一些先决条件
- PriorityClass 的名称必须是有效的 DNS 子域名。
- 当你创建自己的 PriorityClass 时,名称不应以
system-
开头,因为这些名称由 Kubernetes 本身保留(例如,它们用于两个内置的 PriorityClasses)。 - 其绝对值应在 -1000000000 到 1000000000(10 亿)之间。
- 更大的数字被 PriorityClasses 保留,例如
system-cluster-critical
(此 Pod 对集群至关重要)和system-node-critical
(节点严重依赖此 Pod)。system-node-critical
的优先级高于system-cluster-critical
,因为一个集群关键型 Pod 只有在它运行的节点满足所有节点级别的关键要求时才能正常工作。 - 有两个可选字段
globalDefault
:当为 true 时,此 PriorityClass 用于未指定priorityClassName
的 Pod。一个集群中只能存在一个将globalDefault
设置为 true 的 PriorityClass。
如果没有将 globalDefault 设置为 true 的 PriorityClass,所有没有定义 priorityClassName 的 Pod 都将被视为优先级为 0(即最低优先级)。description
:一个带有有意义值的字符串,以便人们知道何时使用此 PriorityClass。
说明
添加一个将globalDefault
设置为 true
的 PriorityClass 并不意味着它会同样应用于已经运行的现有 Pod。这仅适用于在创建 PriorityClass 之后出现的 Pod。PriorityClass 实际应用
这里有一个例子。接下来,创建一些特定于环境的 PriorityClasses
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: dev-pc
value: 1000000
globalDefault: false
description: >-
(Optional) This priority class should only be used for all development pods.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: preprod-pc
value: 2000000
globalDefault: false
description: >-
(Optional) This priority class should only be used for all preprod pods.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: prod-pc
value: 4000000
globalDefault: false
description: >-
(Optional) This priority class should only be used for all prod pods.
使用 kubectl create -f <FILE.YAML>
命令创建一个 pc,并使用 kubectl get pc
检查其状态。
$ kubectl get pc
NAME VALUE GLOBAL-DEFAULT AGE
dev-pc 1000000 false 3m13s
preprod-pc 2000000 false 2m3s
prod-pc 4000000 false 7s
system-cluster-critical 2000000000 false 82m
system-node-critical 2000001000 false 82m
新的 PriorityClasses 现在已经就位。需要在 Pod 清单或 Pod 模板(在 ReplicaSet 或 Deployment 中)中进行一个小小的更改。换句话说,你需要在 .spec.priorityClassName
(这是一个字符串值)处指定优先级类的名称。
首先更新之前的生产环境 Pod 清单文件,为其分配一个 PriorityClass,然后删除生产环境 Pod 并重新创建它。你不能为已经存在的 Pod 编辑优先级类。
在我的集群中,当我尝试这样做时,发生了以下情况。首先,该更改似乎成功了;Pod 的状态已更新
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
dev-nginx 1/1 Terminating 0 55s env=dev
preprod-nginx 1/1 Running 0 55s env=preprod
prod-nginx 0/1 Pending 0 55s env=prod
dev-nginx Pod 正在被终止。一旦它成功终止并且有足够的资源用于生产 Pod,控制平面就可以调度生产 Pod
Warning FailedScheduling pod/prod-nginx 0/2 nodes are available: 1 Insufficient cpu, 2 Insufficient memory.
Normal Preempted pod/dev-nginx by default/prod-nginx on node node01
Normal Killing pod/dev-nginx Stopping container dev-nginx
Normal Scheduled pod/prod-nginx Successfully assigned default/prod-nginx to node01
Normal Pulling pod/prod-nginx Pulling image "nginx"
Normal Pulled pod/prod-nginx Successfully pulled image "nginx"
Normal Created pod/prod-nginx Created container prod-nginx
Normal Started pod/prod-nginx Started container prod-nginx
强制执行
当你设置 PriorityClasses 时,它们会按照你定义的方式存在。然而,对你的集群进行更改的人(和工具)可以自由设置任何 PriorityClass,或者根本不设置任何 PriorityClass。但是,你可以使用 Kubernetes 的其他功能来确保你想要的优先级确实被应用。
作为一个 Alpha 功能,你可以定义一个 ValidatingAdmissionPolicy 和一个 ValidatingAdmissionPolicyBinding,以便例如进入 prod
命名空间的 Pod 必须使用 prod-pc
PriorityClass。通过另一个 ValidatingAdmissionPolicyBinding,你可以确保 preprod
命名空间使用 preprod-pc
PriorityClass,依此类推。在*任何*集群中,你都可以使用外部项目(如 Kyverno 或 Gatekeeper)通过验证准入 Webhook 来实施类似的控制。
无论你如何做,Kubernetes 都为你提供了选项,以确保 PriorityClasses 按照你想要的方式使用,或者也许只是在用户选择不合适的选项时警告他们。
总结
上面的例子及其事件向你展示了 Kubernetes 的这个特性带来了什么,以及你可以使用这个特性的几个场景。重申一下,这有助于确保任务关键型 Pod 能够正常运行并可用以提供流量,并且在资源紧张的情况下决定集群的行为。
它赋予了你决定 Pod 调度顺序和抢占顺序的一些权力。因此,你需要明智地定义 PriorityClasses。例如,如果你有一个集群自动扩缩容器来按需添加节点,请确保它以 system-cluster-critical
PriorityClass 运行。你不想陷入自动扩缩容器被抢占且没有新节点上线的情况。
如果你有任何问题或反馈,请随时在 LinkedIn 上与我联系。