本文发表已超过一年。较早的文章可能包含过时内容。请检查页面中的信息自发布以来是否已不正确。

使用 PriorityClass 保护关键业务 Pod 免遭驱逐

Pod 优先级和抢占有助于确保在资源紧张时,通过决定调度和驱逐顺序来保证关键任务 Pod 的运行。

Kubernetes 已被广泛采用,许多组织将其作为事实上的编排引擎,用于运行需要频繁创建和删除的工作负载。

因此,Pod 的适当调度是确保应用程序 Pod 在 Kubernetes 集群中正常运行而没有任何问题的关键。本文深入探讨了利用 PriorityClass 对象进行资源管理的用例,以保护关键任务或高优先级 Pod 免遭驱逐,并确保应用程序 Pod 正常运行并提供服务。

Kubernetes 中的资源管理

控制平面由多个组件组成,其中调度器(通常是内置的 kube-scheduler)是负责为 Pod 分配节点的组件之一。

每当创建 Pod 时,它都会进入“Pending”状态,之后调度器会确定哪个节点最适合放置新的 Pod。

在后台,调度器作为一个无限循环运行,查找没有设置 nodeName 但已准备好调度的 Pod。对于每个需要调度的 Pod,调度器都会尝试决定哪个节点应该运行该 Pod。

如果调度器找不到任何节点,该 Pod 将保持 Pending 状态,这是不理想的。

下图从第 1 点到第 4 点解释了请求流程

A diagram showing the scheduling of three Pods that a client has directly created.

Kubernetes 中的调度

典型用例

下面是一些现实生活中的场景,可能需要控制 Pod 的调度和驱逐。

  1. 假设您计划部署的 Pod 非常关键,并且您有一些资源限制。一个例子是像 Grafana Loki 这样的基础设施组件的 DaemonSet。Loki Pod 必须在其他 Pod 可以在每个节点上运行之前运行。在这种情况下,您可以通过手动识别和删除不需要的 Pod 或向集群添加新节点来确保资源可用性。但这两种方法都不合适,因为前者执行起来很繁琐,后者可能涉及时间和金钱的支出。

  2. 另一个用例是单个集群包含以下具有相关优先级的环境的 Pod

    • 生产环境 (prod):最高优先级
    • 预生产环境 (preprod):中等优先级
    • 开发环境 (dev):最低优先级

    在集群资源消耗较高的情况下,节点上的 CPU 和内存资源会发生竞争。虽然集群级别的自动伸缩可以添加更多节点,但这需要时间。在此期间,如果无法进一步扩展集群节点,一些 Pod 可能会保持 Pending 状态,或者由于资源竞争导致服务降级。如果 kubelet 从节点驱逐了 Pod,该驱逐将是随机的,因为 kubelet 没有关于要驱逐哪个 Pod 和要保留哪个 Pod 的特殊信息。

  3. 第三个例子可能是一个由队列应用程序或数据库支持的微服务遇到资源紧张,并且队列或数据库被驱逐。在这种情况下,所有其他服务将变得无法使用,直到数据库再次可以处理流量。

也可能有其他场景,您希望控制 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 实现的,它确保 Pod 正常运行并且整个集群不受影响。

$ kubectl get priorityclass
NAME                      VALUE        GLOBAL-DEFAULT   AGE
system-cluster-critical   2000000000   false            82m
system-node-critical      2000001000   false            82m

下图借助一个示例准确展示了它是如何工作的,该示例将在接下来的部分详细介绍。

A flow chart that illustrates how the kube-scheduler prioritizes new Pods and potentially preempts existing Pods

Pod 调度和抢占

Pod 优先级和抢占

Pod 抢占是 Kubernetes 的一个功能,允许集群基于优先级抢占 Pod(移除现有 Pod 以利于新 Pod)。Pod 优先级表示一个 Pod 在调度时相对于其他 Pod 的重要性。如果资源不足以运行所有当前 Pod,调度器会尝试驱逐优先级较低的 Pod,而不是优先级较高的 Pod。

此外,当健康的集群发生节点故障时,通常会抢占较低优先级的 Pod,以便在可用节点上为较高优先级的 Pod 腾出空间。即使集群可以自动启动新节点,也会发生这种情况,因为 Pod 创建通常比启动新节点快得多。

PriorityClass 要求

在设置 PriorityClasses 之前,需要考虑几件事。

  1. 决定需要哪些 PriorityClasses。例如,根据环境、Pod 类型、应用程序类型等。
  2. 您集群的默认 PriorityClass 资源。没有指定 priorityClassName 的 Pod 将被视为优先级 0。
  3. 对所有 PriorityClasses 使用一致的命名约定。
  4. 确保您的工作负载的 Pod 运行在正确的 PriorityClass 下。

PriorityClass 实践示例

假设有 3 个应用程序 Pod:一个用于生产环境,一个用于预生产环境,一个用于开发环境。下面是每种环境的三个示例 YAML Manifest 文件。

---
# 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 的基本 Manifest 文件是什么样子,并概述一些先决条件

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 亿) 之间。
  • 较大的数值保留给某些 PriorityClass 使用,例如 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。

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 Manifest 文件或 Pod 模板(在 ReplicaSet 或 Deployment 中)中做一些小改动。换句话说,您需要在 .spec.priorityClassName(它是一个字符串值)处指定优先级类名称。

首先更新之前的生产环境 Pod Manifest 文件,为其分配 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 Namespace 的 Pod 必须使用 prod-pc PriorityClass。通过另一个 ValidatingAdmissionPolicyBinding,您可以确保 preprod Namespace 使用 preprod-pc PriorityClass,依此类推。在任何集群中,您都可以通过验证性准入 Webhook 使用外部项目(例如 KyvernoGatekeeper)来强制执行类似的控制。

无论您如何操作,Kubernetes 都提供了多种选项来确保 PriorityClasses 按照您希望的方式使用,或者只是在用户选择不合适的选项时 发出警告

总结

上述示例及其事件展示了 Kubernetes 的这项功能带来的益处,以及可以使用此功能的多种场景。重申一下,这有助于确保关键任务 Pod 正常运行并可用以处理流量,并在资源紧张的情况下决定集群的行为。

它赋予您一些权力来决定 Pod 的调度顺序和抢占顺序。因此,您需要合理地定义 PriorityClasses。例如,如果您有集群自动伸缩器可以按需添加节点,请确保使用 system-cluster-critical PriorityClass 运行它。您不希望出现自动伸缩器被抢占而没有新节点上线的情况。

如果您有任何疑问或反馈,请随时通过 LinkedIn 与我联系。