Kubernetes:CPU 配置、Linux CFS、程式語言的效能問題

凌虚發表於2024-12-11

Kubernetes CPU 配置 -> Linux CFS

在使用 Kubernetes 時,可以透過 resources.requestsresources.limits 配置資源的請求和限額,例如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        cpu: "250m"
      limits:
        cpu: "500m"

對容器的資源配置會透過 CRI 元件(如 containerdcri-o 交由更底層的 runckata-container)去設定 Linux 的 cgroup。

在 cgroup v1 中(目前仍然是主流版本,v2 則正在發展):

  • requests.cpu 對應為 cgroup 的 cpu.sharescpu.shares = 1024 表示一個核 CPU,requests.cpu = 250m 表示 0.25 核,對應的 cpu.shares = 1024 * 0.25 = 256。此項配置只作用在 CPU 繁忙時,決定如何給多個容器按比例分配 CPU 時間。
  • limits.cpu 對應為 cgroup 的:

    • cpu.cfs_period_us:表示 CPU 排程週期,一般是 100000 us,即 100 ms。
    • cpu.cfs_quota_us:表示在一個週期內,容器最多可以使用的 CPU 時間。limits.cpu = 500m 表示 0.5 核,對應的 cpu.cfs_quota_us = 100000 * 0.5 = 50000

以上配置可以進入該容器中的 /sys/fs/cgroup/cpu/ 目錄檢視,也可以直接檢視宿主機上的 /sys/fs/cgroup/cpu/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod<uid>.slice/cri-containerd-<uid>.scope/ 目錄。

此時可以看到,在容器環境下,對 CPU 的限額是透過 Linux 的 CFS 機制來完成的。由此很自然地引出了一個問題:容器裡的應用程式/程式語言拿到的 CPU 核數是什麼?

容器環境中的程式語言

如果透過 CPU 核數去設定協程/執行緒/程序數時,可能會發生意料之外的效能問題。

在 Golang 中,透過 GPMGoroutine-Processor-Machine)模式排程 goroutine,而 processor 的數量取自 GOMAXPROCSGOMAXPROCS 的預設值則是 runtime.NumCPU() 拿到的宿主機 CPU 核數。這可能導致應用程式出現更大的延遲,容器配額越小、宿主機資源越大時影響越糟糕。解決方式就是設定 GOMAXPROCS=max(1, floor(cpu_quota)),或者直接使用 uber-go/automaxprocs 這個庫。更多資訊可參考:https://github.com/golang/go/issues/33803

在 Java 中,JVM 的垃圾回收機制與 Linux CFS 排程器的相互作用,也可能導致更長的 STW(stop-the-word)。LinkedIn 工程師在博文 Application Pauses When Running JVM Inside Linux Control Groups 中則建議提供足夠的 CPU 配額,並且應該根據場景調低 GC 執行緒。

更好的方式顯然是消除模糊,根據容器的資源配置,明確地設定相關影響值。

總結

Kubernetes 工作負載的 CPU 配額決定了 Linux CFS 的行為,進而有可能導致程式語言意料之外的效能問題。


(我是凌虛,關注我,無廣告,專注技術,不煽動情緒,歡迎與我交流)


參考資料:

  • https://github.com/golang/go/issues/33803
  • https://www.linkedin.com/blog/engineering/archive/application...
  • https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt

相關文章