天天看點

Kubernetes 的資源管理藝術

作者:閃念基因

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           

如下圖:

Kubernetes 的資源管理藝術

圖4.1 節點上 Pod 所能使用的資源總量

4.1.2 Node 級别的 Cgroups 層級

Cgroups層級目錄如下(以 CPU 子系統為例)

Kubernetes 的資源管理藝術

圖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 子系統為例)

Kubernetes 的資源管理藝術

圖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 子系統為例)

Kubernetes 的資源管理藝術

圖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 子系統為例)

Kubernetes 的資源管理藝術

圖4.5 Cgroups 層級目錄

最後,讓我們一起看下CPU子系統的Cgroup目錄結構。

Kubernetes 的資源管理藝術

圖4.6 CPU子系統的Cgroups 層級目錄

參考資料

[1] Kubernetes 官方文檔(https://kubernetes.io/docs/home/)

作者:流月

來源-微信公衆号:信也科技拍黑米

出處:https://mp.weixin.qq.com/s/BiMFUkZ47wo5znBhgAnuPA

繼續閱讀