K8S CPU 請求和限制,是否有很好的選擇?
作者:JASON UMIKER
原文:https://sysdig.com/blog/kubernetes-cpu-requests-limits-autoscaling/
在這篇博文中,您將瞭解到:
-
CPU requests 如何工作
-
CPU limits 如何工作
-
按編程語言分類的現狀
-
限制不是最佳選擇的情況
-
您可以使用哪些替代方法來限制
Kubernetes CPU Requests
Requests 在 Kubernetes 中有兩個目的:
調度可用性
首先,它們告訴 Kubernetes 需要多少 CPU 可用。然後它會過濾掉任何沒有足夠的 Requests 資源來完成調度的節點。
如果沒有足夠的 Requests CPU / 內存,則 Pod 將處於未調度狀態,直到有滿足 Requests 的資源。
Guaranteed CPU 分配
一旦確定了在哪個節點上運行 Pod(在預選階段倖存下來,然後得分最高的節點),它就會設置 Linux CPU shares 以大致與 mCPU 指標保持一致。CPU Shares 是 Linux 控制組 cgroups 功能 [3]。
-
它應該是在每個節點上配置 Linux CPU shares 的唯一工具,並且它總是將這些 shares 與 mCPU 指標對齊。
-
它只允許在機器上可用的實際邏輯 CPU 內核中請求最大的 mCPU(如果沒有足夠的未請求 CPU 可用,它將被調度器過濾掉)。
-
因此,它將 Linux cgroups CPU shares 幾乎變成了系統上容器最少 CPU 內核的 “保證”,而在 Linux 上不一定這樣使用。
-
如果您通過 LimitRange 在命名空間上設置了一個 Limit,那麼這個 Limit 將會應用。
-
如果你沒有設置一個 request,但設置了 limit(直接或通過命名空間 LimitRange),那麼它也會設置一個 request 等於 limit。
-
如果命名空間上沒有 LimitRange,也沒有 limit,那麼如果不指定,就不會有 request。
-
沒有節點會在調度中被過濾掉,所以它可以在任何節點上調度。這意味着從 CPU 的角度來看,您可以過度配置集羣。
-
結果得到的 Pods / 容器將是節點上優先級最低的,因爲它們沒有 CPU shares (而那些設置了 request 的 Pod 是有的)。
還要注意,如果您沒有在 Pod 上設置 Request,則會將其作爲 BestEffort 服務質量 (QoS),並使其最有可能從節點中被逐出。微信搜索公衆號:信安黑客技術,回覆:黑客 領取資料 。
此外,假設您的應用程序是無狀態的,從可用性的角度來看,向外擴展 (將 Pod 分佈在更多的節點上) 而不是向上擴展通常是有意義的。這也意味着在調度時,更小的 pod 更容易安裝到您的節點上,從而更有效地利用我們的資源。
Kubernetes CPU 限制
除了請求,Kubernetes 也有 CPU 限制。限制是您的應用程序可以使用的最大資源量,但不能保證,因爲它們取決於可用資源。
雖然您可以將請求視爲確保容器至少具有該數量 / 比例的 CPU 時間,但限制卻確保容器的 CPU 時間不能超過該數量。
雖然這可以保護系統上的其他工作負載免於與受限容器競爭,但它也會嚴重影響受限容器的性能。限制工作的方式也不直觀,用戶經常錯誤配置它們。
requests 使用 Linux cgroup CPU shares,而 limits 則使用 cgroup CPU quotas。
這些有兩個主要組成部分:
-
Accounting Period:重置配額之前的時間量(以微秒爲單位)。Kubernetes 默認將此設置爲 100,000us(100 毫秒)。
-
配額週期:cgroup(本例中爲容器)在該週期內可以擁有的 CPU 時間量(以微秒爲單位)。Kubernetes 設置了這個 CPU == 1000m CPU == 100,000us == 100ms。
如果這是一個單線程應用程序 (一次只能在一個 CPU 核心上運行),那麼這就很直觀了。如果您將限制設置爲 1 個 CPU,那麼每 100 毫秒就會獲得 100 毫秒的 CPU 時間,或者全部時間。問題是當我們有一個多線程應用程序可以同時在多個 cpu 上運行時。
如果您有兩個線程,那麼您可以在最短的 50ms 內消耗一個 CPU 週期 (100ms)。10 個線程可以在 10 毫秒內消耗 1 個 CPU 週期,這樣我們每 100 毫秒就會節流 90 毫秒,也就是 90% 的時間! 這通常會導致比沒有節流的單個線程更差的性能。
另一個重要的考慮是,一些應用程序或語言 / 運行時將看到 Node 中的核心數量,並假設它可以使用所有的核心,而不管它的請求或限制。假設我們的 Kubernetes 節點 / 集羣 / 環境在我們擁有的內核數量上不一致。在這種情況下,相同的 Limit 可能導致節點之間的不同行爲,因爲它們將運行不同數量的線程來對應不同數量的 core。
所以,你需要:
-
設置 Limit 以容納所有線程。
-
降低線程數以便與 Limit 對齊。
除非語言 / 運行時知道查看我們的 cgroup 並自動適應你的 Limit。
編程語言的現狀
node.js
Node 是單線程的 (除非你使用 worker_threads),所以如果沒有它們,你的代碼就不能使用一個以上的 CPU 內核。這使得它成爲 Kubernetes 上擴展到更多 pod 的良好候選,而不是擴展單個 pod。
-
例外的是,Node.js 在默認情況下運行四個線程來執行各種操作 (文件系統 IO、DNS、crypto 和 zlib),而 UV_THREADPOOL_SIZE 環境變量可以控制這一點。
-
如果您確實使用 worker_threads,那麼如果您想防止節流,那麼您需要確保您運行的併發線程不會超過您的 CPU 限制。如果您通過 piscina 這樣的線程池包使用它們,這將更容易做到,在這種情況下,您需要確保 maxThreads 被設置爲與您的 CPU 限制相匹配,而不是與節點中的物理 CPU 相匹配 (piscina 目前默認爲物理 CPU 的 1.5 倍 - 並且不會從 cgroup/container limit 自動計算出限制)。
Python
像 Node.js 一樣,解釋性 Python 通常是單線程的,除了一些例外 (使用多處理庫,C/c++ 擴展等),不應該使用多個 CPU。這使得它成爲一個很好的候選,就像 Node.js 一樣,可以擴展到 Kubernetes 上的更多 pod,而不是擴展單個 pod。
注意,如果使用它,多處理庫假定 Node 的物理內核數是默認情況下應該運行的線程數。目前似乎沒有辦法影響這種行爲,除了在代碼中設置它,而不是採用默認池大小。目前 GitHub 上有一個未解決的問題 [4]。
Java
Java 虛擬機 (JVM) 現在提供自動容器 / cgroup 檢測支持,這允許它確定在 Docker 容器中運行的 Java 進程可用的內存量和處理器數量。它使用此信息來分配系統資源。此支持僅適用於 Linux x64 平臺。如果支持,則默認啓用容器支持。如果不支持,您可以使用 -XX:ActiveProcessorCount=X 手動指定 CPU 限制的核心數 [5]。
NET/C
與 Java 一樣,提供了自動容器 / cgroup 檢測支持 [6]。
Golang
爲容器設置 GOMAXPROCS 環境變量 [7] 以匹配任何 CPU 限制。或者使用 automaxprocs 包 [8] 根據 Uber 的容器限制自動設置。
真的需要限制嗎?
也許您可以避免所有這些,只使用請求,因爲它們討論的是總 CPU 的比例,而不是像限制那樣的 CPU 時間,因此在概念上更有意義。
限制的主要目的是確保特定的容器只能獲得節點的特定份額。如果您的節點數量是相當固定的,可能是因爲這是一個內部部署的數據中心,在接下來的幾個月 / 幾年裏使用的裸金屬節點數量是固定的,那麼它們可能是維護整個多租戶系統 / 環境的穩定性和公平性不得不接受的事。
然而,在雲計算中,我們擁有的節點數量實際上是無限的,除了它們的成本。他們可以快速地提供和取消供應。這種彈性和靈活性是許多客戶首先在雲中運行這些工作負載的重要原因!
同樣,需要多少節點直接取決於需要多少 pod 和它們的大小。需要的 pod 數量可以自動 / 動態地縮放,以響應在任何給定時間內要完成的工作量 (請求的數量、隊列中的項目數量等)。
我們可以做很多事情來節省成本,但是以一種損害其性能甚至可用性的方式限制工作負載應該是最後的手段。
Limit 的替代品
讓我們假設工作量增加(這會因工作負載而異,可能是請求 / 秒、隊列中的工作量、響應的延遲等)。首先,Horizontal Pod Autoscaler (HPA) 會自動添加另一個 Pod,但沒有足夠的節點容量來調度它。Kubernetes Cluster Autoscaler 看到這個 Pending Pod,然後添加一個節點。
隨後,工作日結束,由於要做的工作較少,HPA 縮減了幾個 Pods。集羣自動縮放器有足夠的空閒容量來縮小一些節點,並且仍然適合所有運行的節點。因此,它排空並終止一些節點,以便爲現在正在運行的較少的 Pods 調整環境的大小。
在這種情況下,我們不需要限制,只需要設置準確的請求 (這樣可以保證每個系統通過 CPU 共享運行所需的最小值) 和集羣自動定標器。這將確保不會對工作負載進行不必要的節流,並確保我們在任何給定時間都具有適當的容量。
結論
現在您更好地瞭解了 Kubernetes CPU 限制,是時候檢查您的工作負載並適當地配置它們了。
這通常意味着確保運行的併發線程數符合限制(儘管某些語言 / 運行時,如 Java 和 C# 現在可以爲您做到這一點)。
而且,有時,這意味着根本不使用它們,而是依靠 Requests 和 Horizontal Pod Autoscaler (HPA) 的組合來確保您始終有足夠的容量以更動態(和更少的節流)方式運行。
參考資料
[1]
Kubernetes 限制和請求的基礎知識: https://sysdig.com/blog/kubernetes-limits-requests/
[2]
內存不足終止和 CPU 節流: https://sysdig.com/blog/troubleshoot-kubernetes-oom/
[3]
CPU Shares 是 Linux 控制組 cgroups 功能: https://www.batey.info/cgroup-cpu-shares-for-kubernetes.html
[4]
目前 GitHub 上有一個未解決的問題: https://github.com/python/cpython/issues/77167
[5]
手動指定 CPU 限制的核心數: https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html#advanced-runtime-options-for-java
[6]
提供了自動容器 / cgroup 檢測支持: https://github.com/dotnet/designs/blob/main/accepted/2019/support-for-memory-limits.md.
[7]
爲容器設置 GOMAXPROCS 環境變量: https://pkg.go.dev/runtime
[8]
automaxprocs 包: https://github.com/uber-go/automaxprocs
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/shGWCeAOb1dCKK44-FB99Q