1. 程式人生 > >生產環境中的kubernetes 優先順序與搶佔

生產環境中的kubernetes 優先順序與搶佔

kubernetes 中的搶佔功能是排程器比較重要的feature,但是真正使用起來還是比較危險,否則很容易把低優先順序的pod給無辜kill。為了提高GPU叢集的資源利用率,決定勇於嘗試一番該featrue。當然使用之前還是得閱讀一下相關的程式碼做到心裡有數,出了問題也方便定位修復。

基本原理

優先順序與搶佔是為了確保一個高優先順序的pod在排程失敗後,可以通過"擠走" 低優先順序的pod,騰出空間後保證它可以排程成功。 我們首先需要在叢集中宣告PriorityClass來定義優先等級數值和搶佔策略,

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high
value: 10000
preemptionPolicy: Never
globalDefault: false
description: "This priority class should be used for high priority service pods."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low
value: -999
globalDefault: false
description: "This priority class should be used for log priority service pods."

如上所示定義了兩個PriorityClass物件。然後就可以在pod中宣告使用它了:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: high-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx
  template:
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources:
          limits:
            cpu: "500m"
      priorityClassName: high

這個 Pod 通過 priorityClassName 欄位。聲明瞭要使用名叫 high-priority 的 PriorityClass。當這個 Pod 被提交給 Kubernetes 之後,Kubernetes 的 Priority AdmissionController 就會自動將這個 Pod 的spec.priority 欄位設定為10000。
如下:

  preemptionPolicy: Never
  priority: 10000
  priorityClassName: high

Pod建立好之後,排程器就會根據建立的priority進行排程的決策,首先會在等待佇列中優先排程,如果排程失敗就會進行搶佔: 依次遍歷所有的node找出最適合的node,將該nodename填充在spec.nominatedNodeName欄位上,然後等待被搶佔的pod全都退出後再次嘗試排程到該node之上。具體的邏輯請自行閱讀相關程式碼,此處不在贅述。

生產環境使用方式

  • v1.14版本的kubernetes該feature已經GA,預設開啟,但此時我們往往沒有做好準備,如果直接給pod設定優先順序會導致很多意料之外的搶佔,造成故障。 (參見How a Production Outage Was Caused Using Kubernetes Pod Priorities)。所以建議在初次使用的時候還是先顯式關閉搶佔,只設置優先順序,等叢集中所有的pod都有了各自的優先順序之後再開啟,此時所有的搶佔都是可預期的。可以通過kube-scheduler 配置檔案中的disablePreemption: true進行關閉
  • 排程器是根據優先順序pod.spec.priority數值來決定優先順序的,而使用者是通過指定pod.sepc.priorityclass的名字來為pod選擇優先順序的,此時就需要Priority AdmissionController根據priorityclass name為pod自動轉換並設定對應的priority數值。我們需要確保該admissionController開啟,如果你的kube-apiserver中還是通過--admission-control flag來指定admissionoController的話需要手動新增Priority admissonController,如果是通過--enable-admission-plugins來指定的話,無需操作,該admissionController預設開啟。
  • 按照叢集規劃建立對應的PriorityClass及其對應的搶佔策略,目前支援兩種策略: Never, PreemptLowerPriority。 Never可以指定不搶佔其他pod, 即使該pod優先順序特別高,這對於一些離線任務較為友好。 非搶佔排程在v1.15中為alpha, 需要通過--feature-gates=NonPreemptingPriority=true 進行開啟。
  • 在建立好了PriorityClass之後,需要防止高優先順序的pod過分佔用太多資源,使用resourceQuota機制來限制其使用量,避免低優先順序的pod總是被高優先順序的pod壓制,造成資源飢餓。resoueceQuote可以通過指定scope為PriorityClass來限定某個優先順序能使用的資源量:
apiVersion: v1
kind: ResourceQuota
metadata:
  name: high-priority
spec:
  hard:
    pods: "10"
  scopeSelector:
    matchExpressions:
    - operator : In
      scopeName: PriorityClass
      values: ["high"]

如上即為限制高優先順序的pod最多能建立10個。operator指定了作用的物件,operator: In可以顯式指定作用於的哪些priorityClass,operator: Exists則指定作用於該namespace下的所有priorityClass。

  • 有時候我們想要只有priorityClass對應的resourceQuota存在之後才能建立pod,確保所有的priorityClass的pod資源都是受控的。 如果那個namespacew沒有該resourceQuota則拒絕該pod的建立,該操作可以通過指定--admission-control-config-file檔案來設定,內容如下:
apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
  configuration:
    apiVersion: resourcequota.admission.k8s.io/v1beta1
    kind: Configuration
    limitedResources:
    - resource: pods
      matchScopes:
      - scopeName: PriorityClass 
        operator: In
        values: ["high"]

該配置定義了: "high"優先順序的pod只能在指定的namespaces下建立,該namespaces有作用於"high"優先順序的resouceQuota,上面第四步中的resouceQuota即可滿足要求。
scopeName定義了作用的物件為priorityClass, operator指定了作用的範圍,可以是In操作符,指定某幾個value, 也可以是Exits操作符,指定所有的PriorityClass必須有對應的quota存在, 否則該namespace就無法建立該優先順序的pod,這些操作符與上面resouceQuota中定義的一一對應。通過這樣就限制了一些優先順序只能在有資源約束的namespace下建立。

  • 如果沒有顯式指定優先順序,則預設的優先順序值為0,需要結合業務規劃決定是否有必要調整預設優先順序。
  • 對於一些daemonset需要顯式設定較高的優先順序來防止被搶佔,在部署一個新的daemonset的時候需要考慮是否會造成大規模pod的搶佔。
  • 等到所有的優先順序設定完畢之後就可以開啟搶佔功能了,此時叢集中所有pending 的高優先順序pod就會瞬間搶佔,還是需要額外小心,確保叢集中高優先順序的pod不會導致低優先順序的pod大規模被kill,如果我們提前設定了對應的resource quota值,則會有一定的資源約束。
  • 優先順序和搶佔對於資源的精細化運營考驗很大,對於resource quota的設定需要十分精細,需要考慮兩個維度來設定: namespace層面和priority層面,我們既希望限制namespace使用的資源,有希望某個priority使用的資源,防止低優先順序的pod資源飢餓。 可以在初期只考慮namespace層面的限制,priority層面通過上層業務來保證,例如建立任務的時候保證叢集中高優先順序的資源使用量不超過50%等。

其他思考

筆者的線上環境中, 有些再跑的模型訓練任務業務對於自動failove實現不是很好,如果中途被搶佔了只能從頭開始計算,需要佔用額外的GPU資源,這種工作型別不允許被搶佔,但是如果把他設定為高優先順序又不太合適,因為它確實不是最高的優先順序,優先順序最高的還是線上業務,不能讓它搶佔線上業務。 它屬於中間優先順序,可以搶佔低優先順序的pod。 經過探索發現目前kubernetes並不支援該中型別,當前支援的搶佔策略為: Never, PreemptLowerPriority都無法滿足需求。所以在此基礎上開發了NonPreemptible型別的搶佔策略,該優先順序的pod是不允許被其他人搶佔的,排程還是按照優先順序在佇列裡排隊,但是一旦排程上去就無法被搶佔。
這種搶佔策略略顯"霸道",所以需要謹慎使用,設定resouceQuota,並且只能由特定的任務使用。 並且為了不影響deamonset等優先順序最高的任務,允許被某個指定Priority數值之上的pod搶佔。並且隨著業務的發展,這部分邏輯需要逐步去掉,之所有存在這部分邏輯是因為pod不能被中斷,不能被搶佔,所以還是需要使這些任務支援重啟與掛起,具體來說就是: pod掛載遠端磁碟並自動checkpoint,重啟之後從以前的恢復點繼續執行。等這些業務改造完成之後,逐步去掉這種工作搶佔策略

reference

Pod Priority and Preemption
Priority in ResourceQuota
Allow PriorityClasses To Be Non-Preempt