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

使用 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 服务)创建集群时,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

下图显示了它的工作原理,并举例说明,将在接下来的部分中详细介绍。

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 清单文件。

---
# 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 亿)之间。
  • 较大的数字由 system-cluster-critical(此 Pod 对集群至关重要)和 system-node-critical(节点严重依赖此 Pod)等 PriorityClasses 保留。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 清单或 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,依此类推。在任何集群中,您都可以通过验证准入 Webhook 使用外部项目(例如 KyvernoGatekeeper)强制执行类似的控制。

无论您如何执行此操作,Kubernetes 都会为您提供多种选择,以确保 PriorityClasses 按照您希望的方式使用,或者可能只是在用户选择不合适的选项时警告用户。

总结

上面的示例及其事件向您展示了 Kubernetes 的此功能带来了什么,以及您可以利用此功能的几种情况。重申一下,这有助于确保关键任务 Pod 正常运行并可用于提供流量,并且在资源短缺的情况下,确定集群行为。

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

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