Kubernetes CPU 配置 -> Linux CFS
在使用 Kubernetes 時,可以透過 resources.requests
和 resources.limits
配置資源的請求和限額,例如:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "250m"
limits:
cpu: "500m"
對容器的資源配置會透過 CRI
元件(如 containerd
、cri-o
交由更底層的 runc
或 kata-container
)去設定 Linux 的 cgroup。
在 cgroup v1 中(目前仍然是主流版本,v2 則正在發展):
requests.cpu
對應為 cgroup 的cpu.shares
。cpu.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 中,透過 GPM
(Goroutine-Processor-Machine
)模式排程 goroutine
,而 processor 的數量取自 GOMAXPROCS
,GOMAXPROCS
的預設值則是 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