容器的隔離(namespace)與資源限制(cgroups)


1. 什么是容器

簡單來說,容器其實是一種沙盒技術。顧名思義,沙盒就是能夠像一個集裝箱一樣,把你的應用“裝”起來的技術。這樣,應用與應用之間,就因為有了邊界而不至於相互干擾;而被裝進集裝箱的應用,也可以被方便地搬來搬去。

想要理解容器,有必要先回顧一下進程相關的基礎知識。

對於進程來說,它的靜態表現就是程序,平常都安安靜靜地待在磁盤上;而一旦運行起來,它就變成了計算機里的數據和狀態的總和,這就是它的動態表現。

而容器技術的核心功能就是通過約束和修改進程的動態表現,從而為其創造出一個“邊界”。其中,Cgroups 技術是用來制造約束的主要手段,而 Namespace 技術則是用來修改進程視圖的主要方法

2. Namespace 隔離

Linux Namespace是Linux提供的一種內核級別環境隔離的方法。Linux內核中提供了6種namespace隔離的系統調用,如下所示:(官方文檔在這里Namespace in Operation

namespace 系統調用參數 隔離內容
UTS CLONE_NEWUTS 主機名與域名
IPC CLONE_NEWIPC 信號量、消息隊列和共享內存
PID CLONE_NEWPID 進程編號
Network CLONE_NEWNET 網絡設備、網絡棧、端口等
Mount CLONE_NEWNS 掛載點(文件系統)
User CLONE_NEWUSER 用戶和用戶組

該部分理解起來比較抽象,最好的方法就是寫代碼感受一下。

推薦耗子叔的這兩篇博文,跟着走一遍就會有比較直觀的感受。不再贅述。

3. Cgroups 資源限制

通過linux namespace,我們已經能夠創建出一個資源隔離的進程了,也就是所謂的“容器”,但這個“容器”尚不完整,還需要對其進行資源限制。為什么呢?這里通過PID namespace為例進行說明。

雖然容器內的第 1 號進程在“障眼法”的干擾下只能看到容器里的情況,但是從宿主機的角度來看,它作為第 100 號進程與其他所有進程之間依然是平等的競爭關系這就意味着,雖然第 100 號進程表面上被隔離了起來,但是它所能夠使用到的資源(比如 CPU、內存),卻是可以隨時被宿主機上的其他進程(或者其他容器)占用的。當然,這個 100 號進程自己也可能把所有資源吃光。這些情況,顯然都不是一個“沙盒”應該表現出來的合理行為。

Linux Cgroups 就是 Linux內核中用來為進程設置資源限制的一個重要功能,其全稱是Linux Control Groups的簡稱,通過man cgroups命令,可以看到其標准定義:

Control cgroups, usually referred to as cgroups, are a Linux kernel feature which allow processes to be organized into hierarchical(分層的) groups whose usage of various types of resources can then be limited and monitored. The kernel's cgroup interface is provided through a pseudo-filesystem called cgroupfs.

Grouping is implemented in the core cgroup kernel code, while resource tracking and limits are implemented in a set of per-resource-type subsystems (memory, CPU, and so on).

簡單來說,其主要作用就是:

限制一個進程組能夠使用的資源上限,包括 CPU、內存、磁盤、網絡帶寬等等。

此外,Cgroups 還能夠對進程進行優先級設置、審計,以及將進程掛起和恢復等操作。本文重點探討它與容器關系最緊密的“限制”能力,並通過一組實踐來認識一下 Cgroups。

在實踐之前,再了解兩個概念,即控制組(group)子系統(subsystem),其定義同樣可以通過man cgroups進行查看,這里摘錄如下:

  • group:A cgroup is a collection of processes that are bound to a set of limits or parameters defined via the cgroup filesystem.
  • subsystem:A subsystem is a kernel component that modifies the behavior of the processes in a cgroup. Various subsystems have been implemented, making it possible to do things such as limiting the amount of CPU time and memory available to a cgroup, accounting for the CPU time used by a cgroup, and freezing and resuming execution of the processes in a cgroup. Subsystems are sometimes also known as resource controllers (or simply, controllers).

一個“控制組”就是一組綁定在一起的進程的集合,通過cgroup文件系統限定資源或參數;而“子系統”就是所謂的“cgroup文件系統”,用來限定“控制組”內進程的資源。這兩者是彼此協作的概念。

在Linux中,Cgroups實際就是以文件和目錄的方式組織在操作系統的 /sys/fs/cgroup 路徑下,可以通過 mount 命令把它們展示出來,如下:

root@ubuntu:~# mount -t cgroup
...
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
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)
...

它的輸出結果,是一系列文件系統目錄。可以看到,在 /sys/fs/cgroup 下面有很多諸如 cpuset、cpu、 memory 這樣的子目錄,這也就是上面提到的子系統(subsystem)。這些都是我這台機器當前可以被 Cgroups 進行限制的資源種類。而在子系統對應的資源種類下,就可以看到該類資源具體可以被限制的方法。比如:

root@ubuntu:~# ls /sys/fs/cgroup/cpu
cgroup.clone_children  cpuacct.usage_percpu       cpu.cfs_quota_us   system.slice
cgroup.procs           cpuacct.usage_percpu_sys   cpu.shares         tasks
cgroup.sane_behavior   cpuacct.usage_percpu_user  cpu.stat           user.slice
cpuacct.stat           cpuacct.usage_sys          docker
cpuacct.usage          cpuacct.usage_user         notify_on_release
cpuacct.usage_all      cpu.cfs_period_us          release_agent

比如這兩個參數cfs_quota_uscfs_period_us,它表示的意思是:限制進程在長度為cfs_period_us的一段時間內,只能被分配到總量為cfs_quota_us的CPU時間

默認情況下,這兩個參數的值如下,-1表示沒有任何限制,cfs_quota_uscfs_period_us的單位是us。

root@ubuntu:~# cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 
-1     // 沒有限制
root@ubuntu:~# cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 
100000 // 即100ms

也就是說,默認情況下,任何一個線程在 100ms 的時間周期內,「最多」能被分配到的CPU時間就是 100ms,不會被限制。

那么這些配置文件該如何使用呢?怎樣才能真正實現“限制”的效果呢?接下來真正的進入實驗。

首先,在對應的子系統下面創建一個目錄,比如,我們現在進入 /sys/fs/cgroup/cpu 目錄下:

root@ubuntu:/sys/fs/cgroup/cpu# mkdir fake-container
root@ubuntu:/sys/fs/cgroup/cpu# ls fake-container/
cgroup.clone_children  cpuacct.usage_percpu_sys   cpu.shares
cgroup.procs           cpuacct.usage_percpu_user  cpu.stat
cpuacct.stat           cpuacct.usage_sys          notify_on_release
cpuacct.usage          cpuacct.usage_user         tasks
cpuacct.usage_all      cpu.cfs_period_us
cpuacct.usage_percpu   cpu.cfs_quota_us

可以看到,操作系統會在新創建的 fake-container目錄下,自動生成該子系統對應的資源限制文件。還是以上面說的那兩個參數為例,現在該目錄下,該參數的值還是默認的:

root@ubuntu:/sys/fs/cgroup/cpu/fake-container# cat cpu.cfs_quota_us 
-1
root@ubuntu:/sys/fs/cgroup/cpu/fake-container# cat cpu.cfs_period_us
100000

現在在后台執行這樣一條腳本:

root@ubuntu:~# while : ; do : ; done &
[1] 9195

顯然,它執行了一個死循環,可以把計算機的 CPU 吃到 100%,根據它的輸出,我們可以看到這個腳本在后台運行的進程號(PID)是 9195。通過 top 指令確認:

在輸出里可以看到,CPU 的使用率已經 100% 了(%Cpu0 :100.0 us),並且主要就是被 PID=9195 這個進程吃掉的。

接下來,我們修改 fake-container 組里的 cfs_quota 文件,操作如下:

root@ubuntu:/sys/fs/cgroup/cpu# echo 20000 > fake-container/cpu.cfs_quota_us 

這個操作的意義表示:在每 100 ms 的時間里,被該控制組限制的進程只能使用 20 ms 的 CPU 時間,也就是說這個進程「最多」只能使用到 20% 的 CPU 帶寬

接下來,把被限制的進程 PID 寫入 fake-container 這個控制組的tasks文件里,即:

root@ubuntu:/sys/fs/cgroup/cpu# cat fake-container/tasks   // 默認該文件是空的
root@ubuntu:/sys/fs/cgroup/cpu# echo 9195 > fake-container/tasks  // 把被限制的進程PID寫進去

這樣一來,對該進程的限制就會生效了,通過 top 命令查看,發現CPU的使用率立刻下降到了 20%,如下圖所示:

以上就是通過cgroup進行資源限制的簡單實踐。

當然,除了 CPU 子系統用於限制 CPU 的使用之外,還有許多其他子系統對各種資源進行限制,這些子系統均在 /sys/fs/cgroup 目錄下:

root@ubuntu:/sys/fs/cgroup# ls
blkio  cpuacct      cpuset   freezer  memory   net_cls,net_prio  perf_event  rdma     unified
cpu    cpu,cpuacct  devices  hugetlb  net_cls  net_prio          pids        systemd

其中:

  • blkio,為塊設備設定I/O 限制,一般用於磁盤等設備;
  • cpuset,在多核機器上設置cgroup進程可以使用的CPU和內存;
  • memory,為進程設定內存使用的限制。
  • ...

通過實驗我們基本了解了使用 Linux Cgroups 進行資源限制的原理,對於 Docker 項目而言,只需要在每個子系統下面,為每個容器創建一個控制組(即創建一個新目錄),然后在啟動容器進程之后,把這個進程的 PID 填寫到對應控制組的 tasks 文件中就可以了

而至於在這些控制組下面的資源文件里填上什么值,就靠用戶執行 docker run 時的參數指定了,比如這樣一條命令:

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

在啟動容器之后,可以通過查看 Cgroups 文件系統下,CPU 子系統中,“docker”這個控制組里的資源限制文件的內容來確認:

$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us 
100000
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us 
20000

(全文完)


參考&推薦閱讀:

  1. 極客時間專欄:https://time.geekbang.org/column/article/14653
  2. DOCKER基礎技術:LINUX NAMESPACE(上)
  3. DOCKER基礎技術:LINUX NAMESPACE(下)
  4. DOCKER基礎技術:LINUX CGROUP
  5. 《容器與容器雲》第2版


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM