第一千零一篇的 cgroups 介紹

smalltown
Starbugs Weekly 星巴哥技術專欄
30 min readSep 30, 2021

Background

最近自己經手的 K8s Cluster 開始準備從 cgroups v1 轉換到 v2,期間遇到了一些問題,也意識到自己其實對於 cgroups 真的很不熟,就只依稀知道它會負責控制 Container 所使用到的系統資源,但實際怎麼是運作的?v1 升級到 v2 的差異在哪裡?為什麼有人會說升級到 v2 就可以實現 Rootless Container?為什麼要使用 systemd 當作 kubelet 和 container runtime 的 cgroups drvier?每一個問題都讓我覺得更不敢對別人說自己了解 cgroups XD 所以在查詢資料的過程中,順便筆記下來,方便自己之後健忘可以再來看一下,也讓想要知道成為 Container, Kubernetes, systemd 最重要元件之一的 cgroups 究竟是在做什麼的人,可以透過此篇文章快速了解一下

What is cgroups?

cgroups 的全名叫做 Control Groups,他是 Linux Kernel 中的功能,可以用來限制,控制與隔離 Process 所使用到的系統資源,例如 CPU, Memory, Disk I/O, Network…等,這個專案於 2006 年由 Google 的一些工程師所發起 (主要由 Paul Menage 和 Rohit Seth 所主導),一開始其實是叫做 Process Containers,不過在 2007 年時覺得 Container 這個詞彙在 Linux Kernel 已經有許多不同的意義,為了避免混肴,所以才改成 Control Groups,並且被合併到 Linux Kernel 2.6.24 的版本中

這個初始版本就是目前我們大多數人所使用的版本,又被稱為 cgroups v1,後來 cgroups 由 Tejun Heo 接手,他重新設計並且重寫了 cgroups,被重寫的版本就是這兩年大家常聽到的 cgroups v2,它於 2016 年 3 月 14 發佈於 Linux Kernel 4.5 中,而 cgroups 主要提供有底下四個功能:

📖 Resource Limiting: Group 可以設定 Memory 的使用上限,其中也包含 File System 的 Cache

📖 Prioritization: 不同的 Group 可以擁有不同的 CPU 跟 Disk I/O 使用優先順序

📖 Accounting: 計算 Group 內的資源使用狀況,例如可以拿來當作計費的依據

📖 Control:凍結或是重啟一整個 Group 的 Process

cgroups Concept

首先來稍微了解 cgroups 的一些基本核心概念,cgroups 主要由四個元素構成:

📖 Task: 運行於作業系統內的 Process,在 cgroups 內被稱為 Task

📖 Subsystem: 一種資源控制器,一個 Subsystem 負責管理一種資源,例如 CPU 或是 Memory,不同的作業系統所提供的 Subsystem 種類可能不盡相同,以下列出一些常見的類型

🖥️ blkio:限制 Task 對於儲存裝置的讀取,例如 Disk, SSD 和 USB…等

🖥️ cpu: 透過 Scheduler 安排 cgroup 內 Task 存取 CPU 資源

🖥️ cpuacct: 自動產生 cgroup 內的 Task 使用 CPU 資源的報告

🖥️ cpuset: 為 cgroup 內的 Task 分配一組單獨的 CPU 核心 (多核心 的 CPU 系統) 跟 Memory

🖥️ devices: 控制 cgroup 中 Task 可以存取的裝置有哪些

🖥️ freezer: 暫停跟恢復位於 cgroup 內的 Task

🖥️ memory: 限制 Task 可以使用的 Memory,並且自動產生 cgroup 內 Task 使用 Memory 資源的報告

🖥 ️net_cls: 對網路封包標記上 Class ID,進而讓 Linux Traffic Controller 根據這些 Class ID 得知網路封包是來自於哪一個 cgroup 中的 Task

🖥 ️net_prio: 為每一個 Network Interface 提供動態設定網路流量優先權的方法

🖥 ️ns: 有關於 Namespace 的 Subsystem

🖥 ️perf_event: 用來辨別 Task 屬於哪一個 cgroup,而且可以拿來做效能分析用途

📖 cgroup: cgroups 的資源控制單位,也就是 Task 與 Subsystem 的關係連結,用來定義 Task 的資源管理策略,例如把一個 Task 加入到某個 cgroup 內,那麼這個 Task 使用資源時就必須遵守該 cgroup 中所定義的規範

📖 hierarchy: 一群 cgroup 所組成的樹狀結構,每個節點都是一個 cgroup,一個 cgroup 可以有多個子節點,子節點預設繼承父節點的屬性;系統中可以有多個 hierarchy,換言之,就像一座森林一樣

cgroup v1 Hands On

接著我們透過實際操作 cgroups v1 來了解上面提到的這些東西如何各司其職地管理 Task 使用到的資源,cgroups 本身並沒有提供類似 API 或是 Library 的操作方式,他其實是透過 Linux Virtual File System (VFS) 所實作出來,所以操作他就類似於操作一般的檔案系統沒什麼兩樣,底下列出幾個使用他的方式:

📖 當檔案系統使用:透過新增,修改,刪除資料夾和檔案來設定 cgroups

📖 CLI 工具:例如使用 libcgroup 內所提供的 cgcreate, cgexec 和 cgclassify 命令

📖 rules engine daemon:透過 rules engine daemon 來設定 cgroups

📖 cgroups Driver:最主流的方式就是透過 systemd 和 cgroupfs 這種 cgroups Driver 來使用它,

而因為這邊想要讓大家詳細地了解 cgroups 是如何運作的,所以使用最原始的方式,也就是第一種來做演示,底下使用的作業系統為 Ubuntu 20.04,因為 cgroups 的 VFS 在一開始就已經被作業系統自動掛載了,所以可以透過 mount 指令來做查詢:

~$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
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_cls,net_prio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

接著我們試著在具有 Memory Subsystem 的 cgroup /sys/fs/cgroup/memory 底下新增資料夾 democgv1,其實也就等同於建立了一個叫做 democgv1 的 cgroup

~$ mkdir /sys/fs/cgroup/memory/democgv1

查看該資料夾 (cgroup) 會發現已經自動產生好控制資源 (Memory) 時所需要的組態檔案,透過修改相關檔案內容來限制位於這個 cgroups 內的 Task 如何使用該種資源 (Memory)

~$ ls -l /sys/fs/cgroup/memory/democgv1
total 0
-rw-r--r-- 1 root root 0 Sep 29 13:39 cgroup.clone_children
--w--w--w- 1 root root 0 Sep 29 13:39 cgroup.event_control
-rw-r--r-- 1 root root 0 Sep 29 13:39 cgroup.procs
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.failcnt
--w------- 1 root root 0 Sep 29 13:39 memory.force_empty
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.memsw.failcnt
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.memsw.limit_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.memsw.max_usage_in_bytes
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.memsw.usage_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.numa_stat
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.oom_control
---------- 1 root root 0 Sep 29 13:39 memory.pressure_level
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.stat
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.swappiness
-r--r--r-- 1 root root 0 Sep 29 13:39 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Sep 29 13:39 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Sep 29 13:39 notify_on_release
-rw-r--r-- 1 root root 0 Sep 29 13:39 tasks

大部分的檔案都是與該類型的 Subsystem 如何控制資源相關,可以看到上面列出來的東西都與 Memory 的設定相關,但有幾個檔案是無關 Subsystem,在任何類型 Subsystem 的 cgroup 中也一定都會存在的:

📖 tasks: 紀錄此 cgroup 所包含 Task 的 PID 列表,把某個 Process 的 PID 加到這個檔案中的話,就等同於把該 Process 移到此 cgroup 底下

📖 cgroup.procs: 此 cgroup 所包含的子 cgroup 列表,操作方式跟上面的 tasks 檔案一樣

📖 notify_on_relesae: 其值為 0 或是 1,用來決定是否在 cgroup 被刪除時發出通知,如果其值為 1,那當 cgroup 內最後一個 Task 離開時,並且最後一個子 cgroup 也被刪除的話,系統就會執行 release_agent 中所定義的指令

📖 release_agent: 定義需要被執行的指令

所以想要刪除 cgroup 的話,就是刪除對應的資料夾;要是刪除的當下在 tasks 檔案中還存在有 Process PID 的話,他們就會自動移動到上一層的父 cgroup 中

~$ rmdir /sys/fs/cgroup/memory/democgv1/

cgroups v1 v.s. v2

透過上一節簡單的操作後,大概瞭解了 cgroup 是如何控制 Task 使用的資源,接下來就是把系統內的 cgroup 從 v1 改成 v2,並且做類似的操作,來比較看看他們的差異在哪裡

~$ echo 'GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} systemd.unified_cgroup_hierarchy=1"' | sudo tee /etc/default/grub.d/70-cgroup-unified.cfg
~$ sudo update-grub
~$ sudo reboot -h now
# 重新開機登入機器後,透過下面指令檢查該檔案是否存在來判斷 cgroup v2 啟用與否
~$ ls /sys/fs/cgroup/cgroup.controllers

切換到 cgroup v2 之後,先來看看資料夾結構變成什麼樣子

~$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
~$ ls -l /sys/fs/cgroup
total 0
-r--r--r-- 1 root root 0 Sep 29 14:13 cgroup.controllers
-rw-r--r-- 1 root root 0 Sep 29 14:20 cgroup.max.depth
-rw-r--r-- 1 root root 0 Sep 29 14:20 cgroup.max.descendants
-rw-r--r-- 1 root root 0 Sep 29 14:13 cgroup.procs
-r--r--r-- 1 root root 0 Sep 29 14:20 cgroup.stat
-rw-r--r-- 1 root root 0 Sep 29 14:13 cgroup.subtree_control
-rw-r--r-- 1 root root 0 Sep 29 14:20 cgroup.threads
-rw-r--r-- 1 root root 0 Sep 29 14:20 cpu.pressure
-r--r--r-- 1 root root 0 Sep 29 14:20 cpu.stat
-r--r--r-- 1 root root 0 Sep 29 14:20 cpuset.cpus.effective
-r--r--r-- 1 root root 0 Sep 29 14:20 cpuset.mems.effective
drwxr-xr-x 2 root root 0 Sep 29 14:13 init.scope
-rw-r--r-- 1 root root 0 Sep 29 14:20 io.cost.model
-rw-r--r-- 1 root root 0 Sep 29 14:20 io.cost.qos
-rw-r--r-- 1 root root 0 Sep 29 14:20 io.pressure
-r--r--r-- 1 root root 0 Sep 29 14:20 io.stat
-r--r--r-- 1 root root 0 Sep 29 14:20 memory.numa_stat
-rw-r--r-- 1 root root 0 Sep 29 14:20 memory.pressure
-r--r--r-- 1 root root 0 Sep 29 14:20 memory.stat
drwxr-xr-x 39 root root 0 Sep 29 14:13 system.slice
drwxr-xr-x 3 root root 0 Sep 29 14:13 user.slice

回想一下剛剛 v1 是使用不同的 Subsystem 分成不同的 cgroup (/sys/fs/cgroup/${subsystem}),然後子 cgroup 再繼續往下產生,因此子 cgroup 也只能使用相同的 Subsystem 來控制位於其內的 Task,但是 v2 整個看起來都不太一樣了,讓我們新增一個 cgroup 瞧瞧看

~$ mkdir /sys/fs/cgroup/democgv2~$ ls -l /sys/fs/cgroup/democgv2
total 0
-r--r--
r-- 1 root root 0 Sep 29 14:25 cgroup.controllers
-r--r--r-- 1 root root 0 Sep 29 14:25 cgroup.events
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.freeze
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.max.depth
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.max.descendants
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.procs
-r--r--r-- 1 root root 0 Sep 29 14:25 cgroup.stat
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.subtree_control
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.threads
-rw-r--r-- 1 root root 0 Sep 29 14:25 cgroup.type
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.max
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.pressure
-r--r--r-- 1 root root 0 Sep 29 14:25 cpu.stat
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.uclamp.max
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.uclamp.min
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.weight
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpu.weight.nice
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpuset.cpus
-r--r--r-- 1 root root 0 Sep 29 14:25 cpuset.cpus.effective
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpuset.cpus.partition
-rw-r--r-- 1 root root 0 Sep 29 14:25 cpuset.mems
-r--r--r-- 1 root root 0 Sep 29 14:25 cpuset.mems.effective
-rw-r--r-- 1 root root 0 Sep 29 14:25 io.max
-rw-r--r-- 1 root root 0 Sep 29 14:25 io.pressure
-r--r--r-- 1 root root 0 Sep 29 14:25 io.stat
-rw-r--r-- 1 root root 0 Sep 29 14:25 io.weight
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.current
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.events
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.events.local
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.high
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.low
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.max
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.min
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.numa_stat
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.oom.group
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.pressure
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.stat
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.swap.current
-r--r--r-- 1 root root 0 Sep 29 14:25 memory.swap.events
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.swap.high
-rw-r--r-- 1 root root 0 Sep 29 14:25 memory.swap.max
-r--r--r-- 1 root root 0 Sep 29 14:25 pids.current
-r--r--r-- 1 root root 0 Sep 29 14:25 pids.events
-rw-r--r-- 1 root root 0 Sep 29 14:25 pids.max

差別在於在這個新增的 cgroup 內已經有了多種 Subsystem 可以用來控制位於其內的 Task,因為可以看到 CPU, Memory …等的 Subsystem 組態檔案都已經自動產生,而不像 v1 想要使用不同的 Subsystem 必須要切換到不同的資料夾內去建立相關的 cgroup,像上面 v1 的範例中,想要管理 Memory 的話,就必須要到 Memory 的 cgroup 資料夾下面去新增子 cgroup

而在 v2 要如何去設定 cgroup 可以使用哪一些 Subsystem 呢?透過查看該 cgroup 內的 cgroup.controllers 檔案就可以得知,例如下面顯示出有 cpuset, cpu, io …等 Subsystem;而 v1 用來記錄 Process PID 的 tasks 檔案就等同於 v2 的 cgroup.procs,用來記錄子 cgroup 的 cgroup.procs 就等同於 v2 的 cgroup.threads

~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma

可以發現整個架構變得清爽很多,而其實 cgroups 本身的設定操作要細談的話,應該用好幾篇也寫不完XD 因為不同的 subsystem 要控制的資源都有很多東西可以討論,這邊只是把基本概念呈現出來,有興趣的人可以參考詳細文件;除此之外,cgroups v2 所新增的功能也都滿厲害的,除了透過上面實際演示看到整個 cgroups 的 hierarchy 結構大改,讓 cgroup 的設定複雜度降低很多之外,還有支援非特權帳號也可以使用 cgroupsblkio subsystem 加入對於 Buffer IO 限制新增 PSI (Pressure Stall Information) 來監控 CPU, Memory 與 IO 健康狀況的功能…等

cgroups Driver Solutions

有自己架設過 Kubernetes 來使用的人,可能會對於一個參數設定有點印象,也就是這裡要談到的 cgroups Driver,在 Container Runtime 也都有類似的設定,其實上面提到如何操縱 cgroups 時就有提過可以透過 cgroups Driver 來達成,例如 cgroupfs 或是 systemd,因為這樣就不用重造輪子,也就是說 Kubernetes 跟 Container Runtime 可以直接使用 cgroups,而不需要去實作新增,修改,刪除 cgroups VFS 的行為,而為什麼很多文章都會推薦直接使用 systemd 呢?(在 Kubernetes v1.22 中假如沒有特別指定的話,預設就會直接使用 systemd 來當作 cgroups Driver)

原因在於 systemd 已經被各種 Linux Distribution 選定為預設的系統管理服務,他同時也負責產生跟使用著 Root cgroup 並且擔任 cgroup 的管理者,systemd 跟 cgruops 的整合相當地深入,並且使用 systemd unit 來管理 cgroup,換句話說,就是大家都習慣使用,而且又穩定;假如把 kubelet 的 cgroups Drvier 修改成 cgroupfs 的話,那在 Linux 系統中將同時會有兩個角色一起管理 cgroups,只使用單一個系統來管理 cgroups 的話,在資源控制上當然更為簡潔,也來得更一致化,不會有打架的情況發生,而且在真實使用案例上,也有一些人回報設定 kubelet 和 Container Runtime 使用 cgroupfs,然後 Linux 系統本身也使用 systemd 來管理 cgroups 的情況下,會有不穩定的現象發生

所以下次再看到這個參數時,要記得兩個原則:

📖 不管是什麼服務需要 cgroups 功能,都一律使用 systemd 當作 cgroups Drvier

📖 要注意不同服務的 cgroups Driver 要一致,例如 kubelet 和 container runtime 要使用同樣的 cgroups Driver,避免有奇怪的問題發生

Rootless Container

❌ 用了 cgroups v2 後就可以達成 Rootless Container

⭕ 要達成 Rootless Container 必須要有 cgroups v2

這兩年常常聽到 cgroups v2 跟 Rootless Container 一起被提及,這邊想針對這個議題做深入一點的討論,其實要達成 Rootless Container 的話,有很多的條件必須要滿足才行,cgroups v2 只是其中一塊拼圖,首先來看看為什麼會需要 Rootless Container:

📖 要是 Container Engine, Runtime 或是 Orchestrator 被入侵的話,可以避免入侵者獲得整體系統的 Root 權限

📖 允許多個一般使用者 (使用非特權帳號情況下) 在同一台機器運行 Container

📖 可以在巢狀 Container 內增加隔離性

透過 User Namespaces 讓 Rootless Container 的達成有其可能性,當一個使用者建立並且進入到一個新的 User Namespace 時,他會變成這個 Namespace 底下的 Root 使用者,獲得可以開啟一個 Container 所需要的權限,但是 Namespace 中的 Root 使用者權限,比整個作業系統的 Root 權限來得小,例如他沒有辦法讀取或是刪除 Kernel 模組,很不幸的當 Container Engine 要建立一個 Container 時就是需要XD 具體來說,會有 Networking,Storage 和 cgroups 的操作需求,因此要讓 Rootless Container 達成的話,就要讓非特權帳號有辦法去操縱 Networking,Storage 和 cgroups,底下讓我以雖然之後要被換掉,但是目前最多人用的 Docker 來舉例說明 (官方文件):

📖 Networking: Docker 建議在 Rootless 模式下使用 slirp4netns 來讓非特權帳號使用者可以建立 Network Namespace,假如作業系統內沒有安裝 slirp4netns v0.4.0 之後的版本,就會使用 VPNKit,從 RootlessKit 的文件中可以發現各種讓非特權使用者帳號建立 Network Namespace 解決方案的效能表現,其中 VPNKit 算是最弱的,所以不建議使用

https://github.com/rootless-containers/rootlesskit/tree/v0.13.0#network-drivers

📖 Storage: 在 Storage Driver 的方面,假如要使用 Rootless 模式的話,必須要選擇 overlay2, fuse-overlayfs, btrfs 或是 vfs

📖 cgroups: 上面已經有提到 cgroups v2 才支援讓非特權使用者帳號可以使用, 再加上 cgroups Driver 建議使用 systemd 比較穩定,因此毫無懸念,在 Docker Rootless 模式下,必須要使用 cgroups v2 搭配 systemd 來當作 cgroups Driver

所以要達成 Rootless Container ,就是要讓自己所使用的 Container Engine 可以透過對應的解決方案,去使用非特權使用者帳號操縱 Networking, Storage 和 cgroups,例如 slirp4netns + vfs + cgroups v2;所以 podman 與 lxc 想要達成 Rootless Container 的話,也是一樣的思維邏輯,這邊也附上 podman Rootless Container 設定文件;再更近一步要讓整個 Container Orchestrator (Kubernetes) 也運行在 Rootless 模式下要怎麼做呢?這在目前 K8s v1.22 為 Alpha 階段而已,有興趣的人可以參考此 K8s 官方文件

cgroups v2 Adoption Status

上面點扯得太遠,把話題再度拉回來到 cgroups 身上,既然 cgroups v2 這麼好,多了很多好用的功能,並且能夠進一步協助達成 Rootless Container,從他在 2016 合併到 Linux Kernel 之後,目前在各種 Linux Distribution 和 Container 生態系整合的進度如何呢?

🕒 首先 Fedroa 31 在 2019/10/29 開了第一槍,成為搶先將 cgroups v2 預設開啟的主要 Linux Distribution

🕒 Frdora CoreOS 則是在 2021/06 左右 (GitHub Issue Reference)

🕒 FlatCar CoreOS 在版本 2969.0.0 之後將 cgroups v2 變成預設,目前要測試的話可以使用 Beta Release Channel (官方說明)

🕒 至於其他的 Linux Distribution ,其實只要其中的 systemd 版本在 v226 且 Kernel 版本再 v4.2 之後那麼就可以透過設定 systemd.unified_cgroup_hierarchy=1 cgroups v1 切成 v2;各個 Linux Distribution 正在逐步將 cgroups v2 變成預測,例如 Ubuntu 預計從版本 21.10 之後開始要把 cgroups v2 變成預設

🕒 在 Container OCI 這層面上,runc 自版本 v1.0.0-rc93 後開始支援,由 RedHat 所主導的 crun 則在 Fedora 31 推出後就有支援

🕒 在 Container Runtime 以上層面,containerd 從版本 v1.4 後開始支援Docker 從版本 v20.10 後開始支援,Podman 跟 crun 的情況一樣,從 Fedora 31 後就跟著一起支援

🕒 Kubernets 從 v1.19 開始初步支援,後續有關 cgroups v2 的整合進度則可以查看這個 GitHub PR

而為什麼在 2016 年就已經推出的 cgroups v2,一直到 2020~2021 大家才開始陸續整合開發呢?因為他在 2016 年剛推出時對於 Container 的使用上有一些問題,一直到了 Linux Kernel 4.15 (2018/01/28) 支援 Device Controller,用以避免 Container 內 Root 使用者繞過 Container 直接存取系統裝置檔案,例如 /dev/sda1;然後 Linux Kernel 5.2 (2019/07/07) 支援 Freezer Controller,用以避免 TOCTOU 攻擊,至此大家才開始覺得 cgroups v2 可以被 Container 整合進來使用,所以整個 Container 生態系也才從這個時間點後開始動了起來

Conclusion

cgroups v2 很強大,整個 Container 生態系也都開始支援,自己在架設 Kubernetes 時可以使用 cgroups v2 成功運行,除了遇到一個小問題還沒解決之外;所以在達成 Rootless Container 之後,大家應該就可以開始往 Rootless Kubernetes 前進了XD

Reference

--

--

smalltown
Starbugs Weekly 星巴哥技術專欄

原來只是一介草 QA,但開始研究自動化維運雲端服務後,便一頭栽進 DevOps 的世界裏,熱愛鑽研各種可以提升雲端服務品質及增進團隊開發效率的開源技術