第一千零一篇的 cgroups 介紹
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 的設定複雜度降低很多之外,還有支援非特權帳號也可以使用 cgroups,blkio 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 算是最弱的,所以不建議使用
📖 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
- https://en.wikipedia.org/wiki/Cgroups
- https://cizixs.com/2017/08/25/linux-cgroup/
- https://zhuanlan.zhihu.com/p/271808319
- https://zorrozou.github.io/docs/%E8%AF%A6%E8%A7%A3Cgroup%20V2.html
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/ch01
- https://medium.com/nttlabs/cgroup-v2-596d035be4d7
- https://wiki.archlinux.org/title/cgroups
- https://hustcat.github.io/cgroup-v2-and-writeback-support/
- https://facebookmicrosites.github.io/cgroup2/docs/pressure-metrics.html
- https://unit42.paloaltonetworks.com/rootless-containers-the-next-trend-in-container-security/