天天看點

docker容器技術基礎之linux cgroup、namespace

接觸過docker的同學多多少少聽過這樣一句話“docker容器通過linux namespace、cgroup特性實作資源的隔離與限制”。今天我們來嘗試學習一下這兩個東西。

一、開頭

二、關于namesapce

命名空間将全局系統資源包裝在一個抽象中,使命名空間内的程序看起來它們擁有自己獨立的全局資源執行個體。命名空間内對全局資源的改變對其他程序可見,命名空間的成員對其他程序不可見。

目前linux 核心已實作的7種命名空間如下:
Namespace   Flag(API操作類型别名)    Isolates(隔離内容)

Cgroup      CLONE_NEWCGROUP   Cgroup root directory (since Linux 4.6)
IPC         CLONE_NEWIPC      System V IPC, POSIX message queues (since Linux 2.6.19)
Network     CLONE_NEWNET      Network devices, stacks, ports, etc. (since Linux 2.6.24)
Mount       CLONE_NEWNS       Mount points (since Linux 2.4.19)
PID         CLONE_NEWPID      Process IDs (since Linux 2.6.24)
User        CLONE_NEWUSER     User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTS         CLONE_NEWUTS      Hostname and NIS domain name (since Linux 2.6.19)
           
檢視程序的namespace
[root@i-k9pwet2d ~]# pidof bash
14208 11123 2053

[root@i-k9pwet2d ~]# ls -l  /proc/14208/ns
total 0
lrwxrwxrwx 1 root root 0 Jul 20 09:36 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jul 20 09:36 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Jul 20 09:36 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Jul 20 09:36 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Jul 20 09:36 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 20 09:36 uts -> uts:[4026531838]

           

每一個程序在

/proc/[pid]/ns

都可以看到其所屬的namespace資訊,這些連結檔案指向所屬的namespace及inode ID,我們可以通過readlink 來檢視兩個程序的是否屬于同一個命名空間,inode相同則他們所屬相同命名空間

[root@i-k9pwet2d ~]# readlink /proc/11123/ns/uts
uts:[4026531838]
[root@i-k9pwet2d ~]# readlink /proc/14208/ns/uts
uts:[4026531838]
           
如何将你的程序注冊到命名空間(API操作)?

clone():建立一個新的命名空間,子程序同屬新的命名空間,flags即我們建立的namespace類型,形如CLONE_NEW*

int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
                 /* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
           

setns(): 加入一個命名空間,fd為

/proc/[pid]/ns

下的連結檔案,nstype即我們的Flag

int setns(int fd, int nstype);
           

unshare() :退出某個namespace并加入建立的新空間。

int unshare(int flags);
           

ioctl() : ioctl系統調用可用于查詢命名空間的資訊

int ioctl(int fd , unsigned long request , ...);
           

下面我們通過shell 指令 unshare 來看看命名空間7大隔離實作

1.PID Namespace

PID Namespace 的作用是用來隔離程序,利用 PID Namespace 可以實作每個容器的主程序為 1 号程序,而容器内的程序在主機上卻擁有不同的PID。

[root@i-k9pwet2d ~]# unshare --fork --pid --mount-proc /bin/bash
[root@i-k9pwet2d ~]# ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1 115680  2036 pts/0    S    10:46   0:00 /bin/bash
root        12  0.0  0.1 115684  2048 pts/0    S    10:47   0:00 -bash
root        30  0.0  0.0 155468  1804 pts/0    R+   10:57   0:00 ps -aux

ls -l /proc/1/ns
total 0
lrwxrwxrwx 1 root root 0 Jul 20 11:05 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Jul 20 11:05 mnt -> mnt:[4026532545]
lrwxrwxrwx 1 root root 0 Jul 20 11:05 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Jul 20 11:05 pid -> pid:[4026532546]
lrwxrwxrwx 1 root root 0 Jul 20 11:05 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 20 11:05 uts -> uts:[4026531838]
           

在新的PID Namespace中我們隻能看到自身命名空間的程序。并且目前的bash處于起來的mnt、pid命名空間。

2.Mount Namespace

它可以用來隔離不同的程序或程序組看到的挂載點。在容器内的挂載操作不會影響主機的挂載目錄。

我們建立一個命名空間

unshare --mount --fork /bin/bash
           

挂在一個目錄

[root@i-k9pwet2d ~]# mkdir /tmp/mnt
[root@i-k9pwet2d ~]# mount -t tmpfs -o size=1m tmpfs /tmp/mnt

[root@i-k9pwet2d ~]# df -h |grep mnt
tmpfs            1M     0   1M   0% /tmp/mnt
           

在命名空間内的挂載并不影響我們的主機目錄,我們在主機上檢視不到挂載資訊

df -h |grep mnt 
           

3.User Namespace

User Namespace用來隔離使用者和使用者組。我們來建立一個使用者命名空間并修改提示符

[root@i-k9pwet2d ~]# PS1='\u@container#' unshare --user -r /bin/bash

root@container#

           

再檢視ns,使用者連結是不同的,已處于不同空間。

[root@i-k9pwet2d ~]# readlink /proc/1835/ns/user
user:[4026532192]
[root@i-k9pwet2d ~]# readlink /proc/$$/ns/user
user:[4026531837]
           

使用者命名空間的最大優勢是無需 root 權限即可運作容器,避免應用使用root對主機的影響。

4.UTS Namespace

UTS Namespace 用于隔離主機名的,它允許每個 UTS Namespace 擁有一個獨立的主機名。

[root@i-k9pwet2d ~]# unshare --fork --uts /bin/bash
           

在命名空間中修改主機名,在主機中不受影響

[root@i-k9pwet2d ~]# hostname -b container
[root@i-k9pwet2d ~]# hostname
container
           

主機中

[root@i-k9pwet2d ~]# hostname
i-k9pwet2d
           

5.IPC Namespace

IPC 命名空間隔離某些 IPC 資源,即 System V IPC 對象(參見sysvipc(7))和(自 Linux 2.6.30 起)POSIX 消息隊列(請參閱mq_overview(7))。容器通過IPC Namespace、PID Namespace實作同一 IPC Namespace 内的程序彼此可以通信,不同 IPC Namespace 的程序卻不能通信。

我們使用linux中ipc相關指令來測試

ipcs -q 指令:用來檢視系統間通信隊列清單。

ipcmk -Q 指令:用來建立系統間通信隊列。

我們先建立一個IPC Namespace

[root@i-k9pwet2d ~]# unshare --fork --ipc /bin/bash
           

建立一個通信隊列後查詢一下

[root@i-k9pwet2d ~]# ipcmk -Q
Message queue id: 0

[root@i-k9pwet2d ~]# ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x1de4aef6 0          root       644        0            0 
           

在主機上查詢,可以看到通信已經被隔離了

[root@i-k9pwet2d ~]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
           

6.Net Namespace

Net Namespace 可用于隔離網絡裝置、IP 位址和端口等資訊。Net Namespace 可以讓每個程序擁有自己獨立的 IP 位址,端口和網卡資訊。

我們繼續建立一個Net Namespace

[root@i-k9pwet2d ~]# unshare --net --fork /bin/bash
           

檢視網絡和端口資訊

[root@i-k9pwet2d ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    
[root@i-k9pwet2d ~]# netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     
           

上面看到了一個回環接口lo,狀态處于DOWN,我們将它啟動,這樣我們的Namespace有了自己的網絡位址。

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
           
[root@i-k9pwet2d ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:96:e1:36:04 brd ff:ff:ff:ff:ff:ff
    inet 10.150.25.9/24 brd 10.150.25.255 scope global noprefixroute dynamic eth0
       valid_lft 80720sec preferred_lft 80720sec
    inet6 fe80::5054:96ff:fee1:3604/64 scope link 
       valid_lft forever preferred_lft forever
       
[root@i-k9pwet2d ~]# netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      757/sshd            
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1112/master 
...
           

7.Cgroup Namespace

Cgroup是對程序的cgroup視圖虛拟化。 每個 cgroup 命名空間都有自己的一組 cgroup 根目錄。Linux 4.6開始支援。

cgroup 命名空間提供的虛拟化有多種用途:

  • 防止資訊洩漏。否則容器外的cgroup 目錄路徑對容器中的程序可見。
  • 簡化了容器遷移等任務。
  • 允許更好地限制容器化程序。可以挂載容器的 cgroup 檔案系統,這樣容器無需通路主機 cgroup 目錄。

8.Time Namespace

虛拟化兩個系統時鐘,用于隔離時間。 linux 5.7核心開始支援 參考位址:TIME_NAMESPACES(7)

三、關于Cgroup

從上面我們了解到當我們要運作一個容器時,docker等應用會為該容器建立一組 namespace,對作業系統而言可以了解為一組程序。這下我們完成了“權利”的集中,但是“權利越大,責任也大”,我們不能放任這組“大權“不管,是以又有了Cgroup(Linux Control Group)這個東西。

Cgroup最主要的作用,就是限制一個程序組能夠使用的資源上限,包括 CPU、記憶體、磁盤、網絡帶寬等等。

cgroups 架構提供了以下内容:

  • 資源限制: 可以為我們的程序組配置記憶體限制或cpu個數限制又或者僅限于某個特定外圍裝置。
  • 優先級: 一個或多個組可以配置為優先占用 CPU 或磁盤 I/O 吞吐量。
  • 資源記錄: 監視和測量組的資源使用情況。
  • 控制: 可以當機或停止和重新啟動程序組。

一個 cgroup 可以由一個或多個程序組成,這些程序都綁定到同一組限制。這些組也可以是分層的,即子組可以繼承父組管理的限制。

Linux 核心為 cgroup 技術提供了對一系列控制器或子系統的通路。控制器負責将特定類型的系統資源配置設定給一組一個或多個程序。例如,

memory

控制器限制記憶體使用,而

cpuacct

控制器監控 CPU 使用。

我們通過Mount檢視系統中cgroup的子系統

[root@i-k9pwet2d ~]# mount -t cgroup 
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/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
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/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
           

可以看到cgroup已認證檔案系統方式挂載到/sys/fs/cgroup/

[root@i-k9pwet2d ~]# ls -l /sys/fs/cgroup/
total 0
drwxr-xr-x 2 root root  0 Jul 20 12:23 blkio
lrwxrwxrwx 1 root root 11 Jul 20 12:23 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Jul 20 12:23 cpuacct -> cpu,cpuacct
drwxr-xr-x 2 root root  0 Jul 20 12:23 cpu,cpuacct
drwxr-xr-x 2 root root  0 Jul 20 12:23 cpuset
drwxr-xr-x 4 root root  0 Jul 20 12:23 devices
drwxr-xr-x 2 root root  0 Jul 20 12:23 freezer
drwxr-xr-x 2 root root  0 Jul 20 12:23 hugetlb
drwxr-xr-x 2 root root  0 Jul 20 12:23 memory
lrwxrwxrwx 1 root root 16 Jul 20 12:23 net_cls -> net_cls,net_prio
drwxr-xr-x 2 root root  0 Jul 20 12:23 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Jul 20 12:23 net_prio -> net_cls,net_prio
drwxr-xr-x 2 root root  0 Jul 20 12:23 perf_event
drwxr-xr-x 2 root root  0 Jul 20 12:23 pids
drwxr-xr-x 4 root root  0 Jul 20 12:23 systemd

           
接下來我們通過一個執行個體看看cgroup是如何限制CPU使用的

我們啟動一個循環腳本,這個循環腳本将占用近100%的CPU,我們通過cgroup限制到50%

$ cat loop.sh
#!/bash/sh

while [ 1 ]; do
:
done
           

将我們的腳本放到背景,擷取它的PID為21497

nohup bash loop.sh &   
           

我們需要建立一個cgroup控制組loop

[root@i-k9pwet2d ~]# mkdir /sys/fs/cgroup/cpu/loop
           

loop組是CPU的子組,上面提到子組可以繼承父組管理的限制是以loop将繼承對系統整個cpu的通路權限

[root@i-k9pwet2d shell]# ls -l /sys/fs/cgroup/cpu/loop
total 0
-rw-r--r-- 1 root root 0 Jul 20 17:15 cgroup.clone_children
--w--w--w- 1 root root 0 Jul 20 17:15 cgroup.event_control
-rw-r--r-- 1 root root 0 Jul 20 17:15 cgroup.procs
-r--r--r-- 1 root root 0 Jul 20 17:15 cpuacct.stat
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpuacct.usage
-r--r--r-- 1 root root 0 Jul 20 17:15 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Jul 20 17:15 cpu.shares
-r--r--r-- 1 root root 0 Jul 20 17:15 cpu.stat
-rw-r--r-- 1 root root 0 Jul 20 17:15 notify_on_release
-rw-r--r-- 1 root root 0 Jul 20 17:15 tasks

           

檢視繼承後的loop組cpu限制,計算周期為100000us,采樣時間無限制(-1)

[root@i-k9pwet2d shell]# cat /sys/fs/cgroup/cpu/loop/cpu.cfs_period_us
100000
[root@i-k9pwet2d shell]# cat /sys/fs/cgroup/cpu/loop/cpu.cfs_quota_us
-1
           

為了限制程序的的cpu使用率為50%,我們需要更新

cpu.cfs_quota_us

的值為50000

echo 50000 >/sys/fs/cgroup/cpu/loop/cpu.cfs_quota_us
           

将腳本PID更新到loop控制組下的

tasks

[root@i-k9pwet2d shell]# echo 21497 >/sys/fs/cgroup/cpu/loop/tasks
           

此時我們的腳本CPU使用率已被限制到50%

PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                        
21497 root      20   0  113284   1176    996 R 50.0  0.1  12:17.48 bash  
           

在docker啟動容器時做的cpu限制參數

--cpu-period

--cpu-quota

實際上就是調整對應容器控制組的cpu配額。

參考:

  • 《深入剖析Kubernetes》張磊
  • Everything You Need to Know about Linux Containers, Part I: Linux Control Groups and Process Isolation
  • namespaces(7) — Linux manual page

小作文有不足的地方歡迎指出。

歡迎收藏、點贊、提問。關注頂級飲水機管理者,除了管燒熱水,有時還做點别的。