Kubernetes-23:詳解如何將CPU Manager做到遊刃有餘

k8s中為什麼要用CPU Manager?

默認情況下,kubelet 使用CFS配額來執行 Pod 的 CPU 約束。Kubernetes的Node節點會運行多個Pod,其中會有部分的Pod屬於CPU密集型的工作負載。在這種情況下,Pod之間會爭搶節點的CPU資源。當爭搶劇烈的時候,Pod會在不同的CPU Core之間進行頻繁的切換,更糟糕的是在NUMA Node之間的切換。這種大量的上下文切換,會影響程式運行的性能。

什麼是cpu密集型?
通俗來講就是對cpu依賴很高,操作cpu的頻率非常高,充分的使用cpu資源來實現本地計算任務。
另外還有io密集型:
io密集型就是講磁碟/記憶體的io操作會非常頻繁,文件讀寫、網路請求等,這種一般cpu利用率會非常低

CPU Manager有什麼缺點?

CPU Manager特性是節點級別的CPU調度選擇,所以無法在集群維度中選擇最優的CPU Core組合。同時CPU Manager特性要求Pod是Guaranteed時(Pod中的每個容器必須指定CPU Request和CPU Limit,並且兩者要相等)才能生效,且無法適用於所有類型的Pod。

如何開啟CPU Manager

cpu Manager 在 Kubernetes v1.12 引用為 [beta],故想要更好的使用它,版本需>=v1.12。
CPU 管理策略通過 kubelet 參數 –cpu-manager-policy 或 KubeletConfiguration 中的 cpuManagerPolicy 欄位來指定。支援兩種策略:

  • none: 默認策略,表示現有的調度行為。可以理解為不開啟cpu manager。
  • static: 允許為節點上具有某些資源特徵的 Pod 賦予增強的 CPU 親和性和獨佔性。

none 策略

none 策略顯式地啟用現有的默認 CPU 親和方案,不提供作業系統調度器默認行為之外的親和性策略。通過 CFS 配額來實現 Guaranteed Pods和 Burstable Pods的 CPU 使用限制。

static 策略

static 策略針對具有整數型 CPU requests 的 Guaranteed Pod ,它允許該類 Pod中的容器訪問節點上的獨佔 CPU 資源。這種獨佔性是使用cpuset cgroup 控制器來實現的。

CPU 管理器定期通過 CRI 寫入資源更新,以保證記憶體中 CPU 分配與 cgroupfs 一致。同步頻率通過新增的 Kubelet 配置參數 –cpu-manager-reconcile-period 來設置。如果不指定,默認與 –node-status-update-frequency 的周期(默認10s)相同。
Static 策略的行為可以使用 –cpu-manager-policy-options 參數來微調。該參數採用一個逗號分隔的 key=value 策略選項列表。此特性可以通過 CPUManagerPolicyOptions 特性門控來完全禁用。

更改CPU Manager策略

由於 CPU 管理器策略只能在 kubelet 生成新 Pod 時應用,所以簡單地從 “none” 更改為 “static”將不會對現有的 Pod 起作用。因此,為了正確更改節點上的 CPU 管理器策略,請執行以下步驟:

  1. 騰空節點。就是將pod都在此節點驅逐,或者索性stop container。
  2. 停止 kubelet。
  3. 刪除舊的 CPU 管理器狀態文件。該文件的路徑默認為 /var/lib/kubelet/cpu_manager_state。這將清除CPUManager 維護的狀態,以便新策略設置的 cpu-sets 不會與之衝突。
  4. 編輯 kubelet 配置以將 CPU 管理器策略更改為所需的值。
  5. 啟動 kubelet。
    對需要更改其 CPU 管理器策略的每個節點重複此過程。

說明: CPU 管理器不支援運行時下線和上線 CPUs。此外,如果節點上的 CPUs 集合發生變化,則必須驅逐節點上的 Pod,並通過刪除 kubelet 根目錄中的狀態文件cpu_manager_state來手動重置 CPU Manager。

CPU Manager使用注意事項

此策略管理一個 CPU 共享池,該共享池最初包含節點上所有的 CPU 資源。可獨佔性 CPU 資源數量等於節點的 CPU 總量減去通過 kubelet –kube-reserved 或 –system-reserved參數保留的 CPU 資源。從 1.17 版本開始,可以通過 kubelet –reserved-cpus 參數顯式地指定 CPU 預留列表。由 –reserved-cpus 指定的顯式 CPU 列表優先於由 –kube-reserved 和 –system-reserved指定的 CPU 預留。通過這些參數預留的 CPU 是以整數方式,按物理核心 ID 升序從初始共享池獲取的。共享池是 BestEffort 和 Burstable Pod 運行的 CPU 集合。Guaranteed Pod 中的容器,如果聲明了非整數值的 CPU requests,也將運行在共享池的 CPU 上。只有 Guaranteed Pod 中,指定了整數型 CPU requests 的容器,才會被分配獨佔 CPU 資源。

說明: 當啟用 static 策略時,要求使用 –kube-reserved 和/或 –system-reserved 或–reserved-cpus 來保證預留的 CPU 值大於零。這是因為零預留 CPU 值可能使得共享池變空。
例如:–kube-reserved=cpu=1,memory=0

當 Guaranteed Pod 調度到節點上時,如果其容器符合靜態分配要求,相應的 CPU 會被從共享池中移除,並放置到容器的 cpuset 中。因為這些容器所使用的 CPU 受到調度域本身的限制,所以不需要使用 CFS 配額來進行 CPU 的綁定。換言之,容器 cpuset 中的 CPU 數量與 Pod 規約中指定的整數型 CPU limit 相等。這種靜態分配增強了 CPU 親和性,減少了 CPU 密集的工作負載在節流時引起的上下文切換。

CPU Manager yaml模板

正確模板:

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "200Mi"
        cpu: "2"

該 Pod 屬於 Guaranteed QoS 類型,因為其 requests 值與 limits相等。同時,容器對 CPU 資源的限制值是一個大於或等於 1 的整數值。所以,該 nginx 容器被賦予 2 個獨佔 CPU。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"

該 Pod 屬於 Guaranteed QoS 類型,因其指定了 limits 值,同未指定requests,requests 值被設置為與 limits 值相等。同時,容器對 CPU 資源的限制值是一個大於或等於 1 的整數值。所以,該 nginx 容器被賦予 2 個獨佔 CPU。

錯誤模板:

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "1.5"
      requests:
        memory: "200Mi"
        cpu: "1.5"

該 Pod 屬於 Guaranteed QoS 類型,因為其 requests 值與 limits相等。但是容器對 CPU 資源的限制值是一個小數。所以該容器運行在共享 CPU 池中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "100Mi"
        cpu: "1"

該 Pod 屬於 Burstable QoS 類型,因為其資源 requests 不等於 limits。所以該容器運行在共享 CPU 池中。

Static 策略選項

你可以使用以下特性門控根據成熟度級別打開或關閉選項組:

  • CPUManagerPolicyBetaOptions 默認啟用。禁用以隱藏 beta 級選項。
  • CPUManagerPolicyAlphaOptions 默認禁用。啟用以顯示 alpha 級選項。

必須使用CPUManagerPolicyOptions kubelet 選項啟用某個選項。

靜態 CPUManager 策略存在以下策略選項:

  • full-pcpus-only(beta,默認可見)
  • distribute-cpus-across-numa(alpha,默認隱藏)

如果使用 full-pcpus-only 策略選項,static 策略總是會分配完整的物理核心。默認情況下,如果不使用該選項,static 策略會使用拓撲感知最適合的分配方法來分配 CPU。在啟用了 SMT 的系統上,此策略所分配是與硬體執行緒對應的、獨立的虛擬核。這會導致不同的容器共享相同的物理核心,該行為進而會導致吵鬧的鄰居問題。
啟用該選項之後,只有當一個 Pod 里所有容器的 CPU 請求都能夠分配到完整的物理核心時,kubelet 才會接受該 Pod。如果 Pod 沒有被准入,它會被置於 Failed 狀態,錯誤消息是SMTAlignmentError。

如果使用 distribute-cpus-across-numa 策略選項,在需要多個 NUMA 節點來滿足分配的情況下,static 策略會在 NUMA 節點上平均分配 CPU。默認情況下,CPUManager 會將 CPU 分配到一個 NUMA 節點上,直到它被填滿,剩餘的 CPU 會簡單地溢出到下一個 NUMA 節點。這會導致依賴於同步屏障(以及類似的同步原語)的並行程式碼出現不期望的瓶頸,因為此類程式碼的運行速度往往取決於最慢的工作執行緒(由於至少一個 NUMA 節點存在可用 CPU 較少的情況,因此速度變慢)。通過在 NUMA 節點上平均分配 CPU,應用程式開發人員可以更輕鬆地確保沒有某個工作執行緒單獨受到 NUMA 影響,從而提高這些類型應用程式的整體性能。

可以通過將 full-pcups-only=true 添加到 CPUManager 策略選項來啟用 full-pcpus-only 選項。同樣地,可以通過將 distribute-cpus-across-numa=true添加到 CPUManager 策略選項來啟用 distribute-cpus-across-numa 選項。當兩者都設置時,它們是「累加的」,因為 CPU 將分布在 NUMA 節點的 full-pcpus 塊中,而不是單個核心。