本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用 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 來實現的,該 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 通常比啟動一個新節點快得多。

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 億)之間。
  • 更大的數字被 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。

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,依此類推。在*任何*叢集中,你都可以使用外部專案(如 KyvernoGatekeeper)透過驗證准入 Webhook 來實施類似的控制。

無論你如何做,Kubernetes 都為你提供了選項,以確保 PriorityClasses 按照你想要的方式使用,或者也許只是在使用者選擇不合適的選項時警告他們。

總結

上面的例子及其事件向你展示了 Kubernetes 的這個特性帶來了什麼,以及你可以使用這個特性的幾個場景。重申一下,這有助於確保任務關鍵型 Pod 能夠正常執行並可用以提供流量,並且在資源緊張的情況下決定叢集的行為。

它賦予了你決定 Pod 排程順序和搶佔順序的一些權力。因此,你需要明智地定義 PriorityClasses。例如,如果你有一個叢集自動擴縮容器來按需新增節點,請確保它以 system-cluster-critical PriorityClass 執行。你不想陷入自動擴縮容器被搶佔且沒有新節點上線的情況。

如果你有任何問題或反饋,請隨時在 LinkedIn 上與我聯絡。