1. 前言
在目前雲原生技術的發展浪潮中,Kubernetes已經無可争議地成為上司者,其卓越的容器編排能力極大地友善了開發人員和運維團隊。
然而,支撐Kubernetes的技術遠比看上去的更加複雜。Cgroups作為 Linux核心的一個重要功能,被Kubernetes廣泛應用于各級資源的管理。通過Cgroups,Kubernetes能夠對CPU、記憶體等資源進行精細的控制和管理,進而顯著提升了系統的可用性、穩定性和性能。
本文将深入讨論Kubernetes如何利用Cgroups管理資源,并提供一些推薦的配置和最佳實踐。
2. Cgroups簡介
Cgroups,全稱為Control Groups,即控制組,允許對指定程序進行各種計算資源的限制,包括但不限于CPU使用量、記憶體使用量、IO裝置的流量等。
Cgroups的優勢在哪裡?在Cgroups出現之前,任何程序都能建立數百甚至數千個線程,這可能會輕易耗盡一台計算機的所有CPU和記憶體資源。
然而,引入Cgroups技術後,我們便能對單個程序或一組程序的資源消耗進行限制。
Cgroups通過不同的子系統來限制不同的資源,其中每個子系統負責限制一種特定資源。這些子系統以類似的方式工作:将相關程序配置設定到一個控制組中,并通過樹狀結構進行管理,每個控制組都有其自定義的資源控制參數。
子系統 | 功能 |
cpu | 管理 cgroups 中程序的 CPU 使用率 |
cpuacct | 統計 cgroups 中程序的 CPU 使用情況 |
cpuset | 為 cgroups 中的任務配置設定獨立的 CPU 和記憶體節點 |
memory | 管理 cgroups 中程序的記憶體使用量 |
blkio | 管理 cgroups 中程序的塊裝置 IO |
devices | 控制 cgroups 中程序對某些裝置的通路權限 |
... | ... |
表1 常用 cgroups 子系統
在作業系統中,我們可以使用以下指令檢視Cgroups挂載目錄
$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
在 Cgroups根目錄下,我們可以看到各子系統的檔案
$ ll /sys/fs/cgroup
total 0
drwxr-xr-x 6 root root 0 Apr 16 05:13 blkio
lrwxrwxrwx 1 root root 11 Apr 16 05:13 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Apr 16 05:13 cpuacct -> cpu,cpuacct
drwxr-xr-x 6 root root 0 Apr 16 05:13 cpu,cpuacct
drwxr-xr-x 5 root root 0 Apr 16 05:13 cpuset
drwxr-xr-x 5 root root 0 Apr 16 05:13 devices
drwxr-xr-x 4 root root 0 Apr 16 05:13 freezer
drwxr-xr-x 5 root root 0 Apr 16 05:13 hugetlb
drwxr-xr-x 6 root root 0 Apr 16 05:13 memory
lrwxrwxrwx 1 root root 16 Apr 16 05:13 net_cls -> net_cls,net_prio
drwxr-xr-x 4 root root 0 Apr 16 05:13 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Apr 16 05:13 net_prio -> net_cls,net_prio
drwxr-xr-x 4 root root 0 Apr 16 05:13 perf_event
drwxr-xr-x 6 root root 0 Apr 16 05:13 pids
drwxr-xr-x 6 root root 0 Apr 16 05:13 systemd
3. Cgroups驅動介紹及選型
3.1 Cgroups 驅動介紹
Cgroups驅動主要有兩種類型:
- systemd
- cgroupfs
systemd:
- systemd是一個系統和服務管理器,廣泛用作許多現代Linux發行版的 init系統。
- systemd直接通過其控制接口管理cgroups,并通過工具(如 systemctl、systemd-cgls、systemd-cgtop等)提供cgroups的管理和監控功能。
cgroupfs:
- cgroupfs通過檔案系統接口管理cgroups。
- cgroupfs将cgroups以檔案系統的形式挂載至/sys/fs/cgroup目錄,允許使用者通過檔案和目錄操作來管理cgroups,這類似于對普通檔案系統的操作。
systemd驅動與cgroupfs驅動的主要差別在于它們的管理接口。systemd 提供了一整套專用工具和接口來管理cgroups,而cgroupfs則通過檔案系統接口進行管理。在實際應用中,盡管大多數現代Linux發行版采用 systemd來管理cgroups,cgroupfs仍然作為Linux核心支援的一個選項存在。
3.2 Kubernetes 中 Cgroups 驅動選擇
Kubelet預設使用cgroupfs作為Cgroups驅動。然而,如果您通過 kubeadm部署Kubernetes 叢集(版本 1.22 及以上),系統會預設采用 systemd作為Cgroups驅動。
我們不能讓cgroupfs和systemd都作為我們的翅膀,必須二選一,那麼我們應該如何選擇呢?
其實也很簡單,作業系統使用的是哪個Cgroups驅動,我們就選哪個。
因為如果一個系統中同時存在兩種Cgroups管理器,假設kubelet和容器運作時(例如containerd)使用cgroupfs,而作業系統則使用systemd,那麼在資源壓力增大時,節點可能會變得不穩定。(修羅場)
對于大多數現代Linux發行版,如RHEL、CentOS、Fedora、Ubuntu、Debian等,它們都采用systemd來管理cgroups。是以,在這些Linux發行版上,kubelet和容器運作時應選擇systemd作為Cgroups驅動。
4. Kubernetes 是如何使用 Cgroups 來管理資源的
在Kubernetes中,Cgroups用于管理不同級别的資源,這些級别從大到小依次為:
- Node
- Pod
- Container
接下來,我們将按照Node、Pod 和Container這三個級别,逐級描述 Kubernetes中的Cgroups管理方式。同時,我們也會讨論Kubernetes如何利用QoS (Quality of Service) 政策來優化資源配置設定和管理。
4.1 Node 級别
在Kubernetes叢集的一個節點中,資源可分為三部分:
- 系統元件使用的資源:作業系統本身的程序所使用的資源,如 sshd, systemd等
- Kubernetes元件使用的資源:如 kubelet, 容器運作時(如 Docker, containerd)等
- Pod使用的資源:叢集内Pod所使用的資源
如果我們不對這三個部分的資源使用進行管理,節點資源緊張時,可能會導緻叢集雪崩。
想象以下場景:
- 叢集中的A節點上的Pod glutton吃光了節點上所有的資源
- 由于資源競争,系統元件和Kubernetes元件因為搶不到資源而崩潰
- 節點A狀态變為Not Ready
- Kubernetes嘗試将節點A上的Pod glutton遷移到其他處于Ready狀态的節點,例如節點B
- 節點B上的資源随後也被Pod glutton完全占用
- ...
本來叢集和人都能跑,現在隻有人能跑了。
那麼,為了叢集的健康,更為了人的健康(飯碗),聰明的你,或許已經想到了一個智慧的方法:提前為系統元件和Kubernetes元件預留資源,防止資源被惡意或異常的Pod完全占用。
巧了,Kubernetes的設計者也是這麼想的。
4.1.1 Node 級别資源管理
在Node層面,Kubernetes允許我們修改以下配置來預留并限制資源:
- systemReserved
- kubeReserved
- evictionHard
- enforceNodeAllocatable
systemReserved
為系統元件預留的資源,如sshd等。
如果配置了此項,則需要同時配置systemReservedCgroup來指定系統元件的Cgroups路徑。
同時,需要在kubelet啟動前預先檢查/建立systemReservedCgroup的路徑,否則kubelet會啟動失敗。
示例:
systemReserved:
cpu: 100m
memory: 2048Mi
ephemeral-storage: 1Gi
pid: "1000"
systemReservedCgroup: /system.slice
kubeReserved
為Kubernetes元件預留的資源,如 kubelet,容器運作時等。
如果配置了此項,則需要同時配kubeReservedCgroup來指定Kubernetes 元件的Cgroups路徑。
同時,需要在kubelet啟動前預先檢查/建立kubeReservedCgroup的路徑,否則kubelet會啟動失敗。
示例:
kubeReserved:
cpu: 100m
memory: 2048Mi
ephemeral-storage: 1Gi
pid: "1000"
kubeReservedCgroup: /kube.slice
evictionHard
節點壓力驅逐的條件,kubelet會監視叢集節點上的記憶體、磁盤空間和檔案系統inodes等資源。當這些資源中的一項或多項達到指定條件時,kubelet 會主動終止 pod以回收節點資源。
示例:
evictionHard:
imagefs.available: 10%
memory.available: 500Mi
nodefs.available: 5%
nodefs.inodesFree: 5%
enforceNodeAllocatable
指定需要限制的資源類型,預設為pods,即隻會限制節點上pod使用的資源。
另外,也可以限制系統元件的資源(system-reserved)和Kubernetes 元件的資源(kube-reserved)。
為了確定核心元件的穩定運作,通常建議不要限制系統元件和 Kubernetes元件的資源,以避免這些核心元件出現資源不足的情況。
示例:
enforceNodeAllocatable:
-pods
經過上述配置後,節點上Pod所能使用的資源總量(Allocatable)為:
節點資源總量(Capacity) - evictionHard - kubeReserved - systemReserved
如下圖:
圖4.1 節點上 Pod 所能使用的資源總量
4.1.2 Node 級别的 Cgroups 層級
Cgroups層級目錄如下(以 CPU 子系統為例)
圖4.2 Cgroups 層級目錄
在上圖中:
- /system.slice:管理系統元件的資源使用
- /kube.slice:管理Kubernetes元件(如kubelet、容器運作時等)的資源使用
- /kubepods.slice:管理該節點上Kubernetes叢集中Pod的資源使用
4.2 QoS 級别
QoS,全稱Quality of Service,即服務品質。
Kubernetes對所有Pod進行分類,并根據它們的資源請求(Requests)和限制(Limits)的不同,将它們配置設定到特定的QoS級别。
在節點資源壓力情況下,Kubernetes會根據QoS級别來決定哪些Pod應該被驅逐。
4.2.1 QoS 等級
Qo 等級共有 3 個:
- Guaranteed
- Burstable
- BestEffort
Guaranteed
Pod中所有的容器(不包含臨時容器)都設定了CPU和記憶體資源的請求(Requests)和限制(Limits),且請求和限制的值相等。
Burstable
不滿足Guaranteed等級的條件,但至少有一個容器設定了CPU和記憶體資源的請求(Requests)或限制(Limits)。
BestEffort
既不滿足Guaranteed等級的條件,也不滿足Burstable等級的條件,即 Pod中所有的容器都沒有設定CPU和記憶體資源的請求(Requests)和限制(Limits)。
三種QoS等級的Pod重要性等級如下:
Guaranteed > Burstable > BestEffort
即當節點資源耗盡時,會首先驅逐BestEffort等級的Pod,然後是 Burstable等級的Pod,最後是Guaranteed等級的Pod。當此驅逐是由于資源壓力而導緻時,隻有超出資源請求的Pod才是驅逐的候選者。
4.2.2 QoS 級别的 Cgroups 層級
Cgroups層級目錄如下(以 CPU 子系統為例)
圖4.3 Cgroups 層級目錄
如圖所示,Guaranteed等級的Pod的Cgroups目錄在 最外層,Burstable 和BestEffort等級的Cgroups目錄則是在子目錄。
4.3 Pod 級别
4.3.1 Pod 級别的資源管理
一個Pod可以包含一個或多個容器。然而,Pod的總資源消耗并不僅限于其内部所有容器資源消耗的總和。實際上,Pod的資源使用還包括額外的開銷,具體包括:
- Pod基礎資源開銷:每個Pod都會産生一些基礎的資源開銷,這主要包括網絡配置和Pod内部運作的一些基礎服務的資源消耗。
- Kubernetes系統容器:Kubernetes在每個Pod中運作一個特殊的容器,通常稱為pause容器。這個容器作為所有其他容器的父容器,負責管理網絡、卷挂載等任務。雖然pause容器消耗的資源相對較少,但在計算Pod的總資源消耗時,這部分資源也需要被考慮。
為了有效管理Pod的資源消耗,Kubernetes在Pod層面建立了一個 Cgroups層級,通過這種方式可以更精确地控制和監控Pod及其容器的資源使用情況。
4.3.2 Pod 級别的 Cgroups 層級
Cgroups層級目錄如下(以 CPU 子系統為例)
圖4.4 Cgroups 層級目錄
4.4 Container級别
4.4.1 Container級别的資源管理
容器級别的資源隔離是由底層容器運作時(如 containerd)實作的。在 Kubernetes環境中,為容器配置資源請求(requests)和限制(limits)後,這些配置資訊通過 kubelet 傳遞給容器運作時。這一過程依賴于 Kubernetes的容器運作時接口(Container Runtime Interface, CRI),CRI為kubelet與容器運作時之間的互動提供了标準化的協定和工具集。
容器運作時接收到資源配置資訊後,利用Cgroups對每個容器的資源使用進行限制和隔離。通過配置Cgroups,容器運作時可以對CPU、記憶體等資源進行精确的控制。具體來說,它會配置cpu.shares、cpu.cfs_period_us、cpu.cfs_quota_us、memory.limit_in_bytes等參數,確定容器的資源消耗不會超出預設的限制。
總體而言,kubelet負責傳遞資源配置資訊,而具體的資源隔離和限制則是由容器運作時通過配置cgroups實作的。這種設計使得Kubernetes能夠高效且安全地管理容器資源。
4.4.2 CPU資源配置
- CPU 請求(request):通過Cgroups的cpu.shares 實作。當容器的 CPU請求設定為x millicores時,其cpu.shares 值計算為x * 1024 / 1000。例如,如果容器的CPU請求為1000 millicores(即相當于 1 個CPU核心),那麼cpu.shares的值将為1024。這樣做確定了即使在 CPU資源緊張的情況下,該容器也能獲得至少相當于1個CPU核心的計算資源。
- CPU 限制(limit):通過Cgroups的cpu.cfs_period_us和 cpu.cfs_quota_us實作。在Kubernetes中,cpu.cfs_period_us被設定為100000微秒(即 100 毫秒),而cpu.cfs_quota_us根據CPU限制來計算。這兩個參數共同作用,嚴格限制容器的CPU使用量,確定不會超過設定的限制。如果隻指定了request而沒有指定limit,那麼cpu.cfs_quota_us将被設定為-1(表示沒有限制)。如果既沒有指定 request也沒有指定limit,那麼cpu.shares将被設定為2,意味着配置設定給該容器的CPU資源将是最低限度的。
4.4.3 記憶體資源配置
- 記憶體請求(request):Kubernetes 在進行排程決策時會考慮記憶體請求,這一參數雖然不會直接反映在容器級别的Cgroups配置中,但它確定了在節點上為容器配置設定了足夠的記憶體資源。記憶體請求主要用于 Kubernetes 的排程過程,以保證節點具有為容器配置設定所需的記憶體資源。
- 記憶體限制(limit):記憶體限制是通過Cgroups的 memory.limit_in_bytes 參數來實作的,它定義了容器程序可以使用的最大記憶體量。若容器的記憶體使用超過了這一限制,将會觸發OOM Killer,可能會導緻容器被終止并重新啟動。如果沒有指定記憶體限制,則 memory.limit_in_bytes會被設定為一個極大的值,這通常意味着容器可以無限制地使用記憶體。
簡而言之,Kubernetes通過Cgroups配置實作容器的資源隔離,確定每個容器根據其請求(request)和限制(limit)獲得合理的資源配置設定。這一機制既提高了資源的利用效率,又防止了任何單一容器的過度資源消耗,進而保障了系統的整體穩定性。
4.4.4 Container 級别的 Cgroups 層級
Cgroups 層級目錄如下(以 CPU 子系統為例)
圖4.5 Cgroups 層級目錄
最後,讓我們一起看下CPU子系統的Cgroup目錄結構。
圖4.6 CPU子系統的Cgroups 層級目錄
參考資料
[1] Kubernetes 官方文檔(https://kubernetes.io/docs/home/)
作者:流月
來源-微信公衆号:信也科技拍黑米
出處:https://mp.weixin.qq.com/s/BiMFUkZ47wo5znBhgAnuPA