生產環境中的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-Preempting


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM