使用 PriorityClass 确保你的关键任务 Pod 免遭驱逐

Pod 优先级和抢占有助于通过决定调度和驱逐的顺序来确保关键任务 Pod 在资源紧缩的情况下正常运行。

作者:Sunny Bhambhani (InfraCloud Technologies)

译者:Wilson Wu (DaoCloud)

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

因此,是否能对 Pod 进行合适的调度是确保应用 Pod 在 Kubernetes 集群中正常启动并运行的关键。 本文深入探讨围绕资源管理的使用场景,利用 PriorityClass 对象来保护关键或高优先级 Pod 免遭驱逐并确保应用 Pod 正常启动、运行以及提供流量服务。

Kubernetes 中的资源管理

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

当 Pod 被创建时,它就会进入“Pending”状态,之后调度程序会确定哪个节点最适合放置这个新 Pod。

在后台,调度程序以无限循环的方式运行,并寻找没有设置 nodeName准备好进行调度的 Pod。 对于每个需要调度的 Pod,调度程序会尝试决定哪个节点应该运行该 Pod。

如果调度程序找不到任何节点,Pod 就会保持在这个不理想的挂起状态下。

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

由客户端直接创建的三个 Pod 的调度示意图。

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 中的 PriorityClass

PriorityClass 是 Kubernetes 中集群范围的 API 对象,也是 scheduling.k8s.io/v1 API 组的一部分。 它包含 PriorityClass 名称(在 .metadata.name 中定义)和一个整数值(在 .value 中定义)之间的映射。 整数值表示调度程序用来确定 Pod 相对优先级的值。

此外,当你使用 kubeadm 或托管 Kubernetes 服务(例如 Azure Kubernetes Service)创建集群时, Kubernetes 使用 PriorityClass 来保护控制平面节点上托管的 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

下图通过一个示例展示其确切工作原理,下一节详细介绍这一原理。

此流程图说明了 kube-scheduler 如何对新 Pod 进行优先级排序并可能对现有 Pod 进行抢占

Pod 调度和抢占

Pod 优先级和抢占

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

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

PriorityClass 的前提条件

在配置 PriorityClass 之前,需要考虑一些事项。

  1. 决定哪些 PriorityClass 是需要的。例如,基于环境、Pod 类型、应用类型等。
  2. 集群中默认的 PriorityClass 资源。当 Pod 没有设置 priorityClassName 时,优先级将被视为 0。
  3. 对所有 PriorityClass 使用一致的命名约定。
  4. 确保工作负载的 Pod 正在使用正确的 PriorityClass。

PriorityClass 的动手示例

假设有 3 个应用 Pod:一个用于生产(prod),一个用于预生产(prepord),一个用于开发(development)。 下面是这三个示例的 YAML 清单文件。

---
# 开发环境(dev)
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"
---
# 预生产环境(prepord)
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"
---
# 生产环境(prod)
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

在根据这些需求创建 PriorityClass 之前,让我们看看 PriorityClass 的基本清单是什么样的, 并给出一些先决条件:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: PRIORITYCLASS_NAME
value: 0 # -1000000000 到 1000000000 之间的任何整数值 
description: >-
  (可选)描述内容!  
globalDefault: false # 或 true。只有一个 PriorityClass 可以作为全局默认值。

以下是 PriorityClass 的一些先决条件:

  • PriorityClass 的名称必须是有效的 DNS 子域名。
  • 当你创建自己的 PriorityClass 时,名称不应以 system- 开头,因为这类名称是被 Kubernetes 本身保留的(例如,它们被用于两个内置的 PriorityClass)。
  • 其绝对值应在 -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。 如果没有 PriorityClass 的 globalDefault 设置为 true,则所有未定义 priorityClassName 的 Pod 都将被视为 0 优先级(即最低优先级)。
    • description:具备有意义值的字符串,以便人们知道何时使用此 PriorityClass。

PriorityClass 的实际应用

这里有一个例子。接下来,创建一些针对环境的 PriorityClass:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: dev-pc
value: 1000000
globalDefault: false
description: >-
  (可选)此 PriorityClass 只能用于所有开发环境(dev)Pod。  
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: preprod-pc
value: 2000000
globalDefault: false
description: >-
  (可选)此 PriorityClass 只能用于所有预生产环境(preprod)Pod。  
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: prod-pc
value: 4000000
globalDefault: false
description: >-
  (可选)此 PriorityClass 只能用于所有生产环境(prod)Pod。  

使用 kubectl create -f <FILE.YAML> 命令创建 PriorityClass 并使用 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

新的 PriorityClass 现已就位。需要对 Pod 清单或 Pod 模板(在 ReplicaSet 或 Deployment 中)进行一些小的修改。 换句话说,你需要在 .spec.priorityClassName(这是一个字符串值)中指定 PriorityClass 名称。

首先更新之前的生产环境 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 即将被终止。一旦成功终止并且有足够的资源用于 prod Pod,控制平面就可以对 prod 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

执行

配置 PriorityClass 时,它们会按照你所定义的方式存在。 但是,对集群进行变更的人员(和工具)可以自由设置任意 PriorityClass, 或者根本不设置任何 PriorityClass。然而,你可以使用其他 Kubernetes 功能来确保你想要的优先级被实际应用起来。

作为一项 Alpha 级别功能,你可以定义一个 ValidatingAdmissionPolicy 和一个 ValidatingAdmissionPolicyBinding,例如,进入 prod 命名空间的 Pod 必须使用 prod-pc PriorityClass。 通过另一个 ValidatingAdmissionPolicyBinding,你可以确保 preprod 命名空间使用 preprod-pc PriorityClass,依此类推。 在任何集群中,你可以使用外部项目,例如 KyvernoGatekeeper 通过验证准入 Webhook 实施类似的控制。

无论你如何操作,Kubernetes 都会为你提供选项,确保 PriorityClass 的用法如你所愿, 或者只是当用户选择不合适的选项时做出警告

总结

上面的示例及其事件向你展示了 Kubernetes 此功能带来的好处,以及可以使用此功能的几种场景。 重申一下,这一机制有助于确保关键任务 Pod 启动并可用于提供流量,并在资源紧张的情况下确定集群行为。

它赋予你一定的权力来决定 Pod 的调度顺序和抢占顺序。 因此,你需要明智地定义 PriorityClass。例如,如果你有一个集群自动缩放程序来按需添加节点, 请确保使用 system-cluster-critical PriorityClass 运行它。你不希望遇到自动缩放器 Pod 被抢占导致没有新节点上线的情况。

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