一、開頭
接觸過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配額。
參考:
小作文有不足的地方歡迎指出。
歡迎收藏、點贊、提問。關注頂級飲水機管理員,除了管燒熱水,有時還做點別的。