1、Namespace
Linux內核中的namespace技術實現了各種資源的隔離。
最新的 Linux 5.6 內核中提供了 8 種類型的 Namespace:
Namespace 名稱 | 作用 | 內核版本 |
---|---|---|
Mount(mnt) | 隔離掛載點 | 2.4.19 |
Process ID (pid) | 隔離進程 ID | 2.6.24 |
Network (net) | 隔離網絡設備,端口號等 | 2.6.29 |
Interprocess Communication (ipc) | 隔離 System V IPC 和 POSIX message queues | 2.6.19 |
UTS Namespace(uts) | 隔離主機名和域名 | 2.6.19 |
User Namespace (user) | 隔離用戶和用戶組 | 3.8 |
Control group (cgroup) Namespace | 隔離 Cgroups 根目錄 | 4.6 |
Time Namespace | 隔離系統時間 | 5.6 |
1.1、隔離掛載點
Mount Namespace 實現了不同進程可以看到不同的掛載信息。
通俗點說,容器內的掛載操作不會影響到主機。
使用unshare命令新建一個mount namespace
$ sudo unshare --mount --fork /bin/bash
創建臨時掛載目錄 [root@centos7 centos]# mkdir /tmp/tmpfs
使用tmpfs掛載一個目錄 [root@centos7 centos]# mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
當前窗口查看掛載信息 [root@centos7 centos]# df -h Filesystem Size Used Avail Use% Mounted on /dev/vda1 500G 1.4G 499G 1% / devtmpfs 16G 0 16G 0% /dev tmpfs 16G 0 16G 0% /dev/shm tmpfs 16G 0 16G 0% /sys/fs/cgroup tmpfs 16G 57M 16G 1% /run tmpfs 3.2G 0 3.2G 0% /run/user/1000 tmpfs 20M 0 20M 0% /tmp/tmpfs
新開一個窗口,查看主機掛載信息
可看到掛載信息不同 [centos@centos7 ~]$ df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 16G 0 16G 0% /dev tmpfs 16G 0 16G 0% /dev/shm tmpfs 16G 57M 16G 1% /run tmpfs 16G 0 16G 0% /sys/fs/cgroup /dev/vda1 500G 1.4G 499G 1% / tmpfs 3.2G 0 3.2G 0% /run/user/1000
1.2、隔離進程ID
此功能可實現不同的PID namespace內的進程擁有相同的ID。
創建一個pid namespace
$ sudo unshare --pid --fork --mount-proc /bin/bash 查看進程信息,可以發現1號進程是bash [root@centos7 centos]# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 115544 2004 pts/0 S 10:57 0:00 bash root 10 0.0 0.0 155444 1764 pts/0 R+ 10:59 0:00 ps aux
1.3、隔離網絡設備、端口號
net namespace實現網絡設備的隔離。
查看主機網絡信息
$ ip a 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 02:11:b0:14:01:0c brd ff:ff:ff:ff:ff:ff inet 172.20.1.11/24 brd 172.20.1.255 scope global dynamic eth0 valid_lft 86063337sec preferred_lft 86063337sec inet6 fe80::11:b0ff:fe14:10c/64 scope link valid_lft forever preferred_lft forever 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:82:8d:a0:df brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:82ff:fe8d:a0df/64 scope link valid_lft forever preferred_lft forever 創建一個net namespace $ sudo unshare --net --fork /bin/bash [root@centos7 centos]# 查看此namespace的網絡信息 [root@centos7 centos]# ip a 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
1.4、隔離主機名
UTS Namespace 主要是用來隔離主機名的,它允許每個 UTS Namespace 擁有一個獨立的主機名。
-
-
[root@centos7 centos]#
創建好 UTS Namespace 后,當前命令行窗口已經處於一個獨立的 UTS Namespace 中,下面我們使用 hostname 命令(hostname 可以用來查看主機名稱)設置一下主機名:
-
root@centos7 centos]# hostname -b lagoudocker
然后再查看一下主機名:
-
[root@centos7 centos]# hostname
-
lagoudocker
通過上面命令的輸出,我們可以看到當前UTS Namespace 內的主機名已經被修改為 lagoudocker。然后我們新打開一個命令行窗口,使用相同的命令查看一下主機的 hostname:
-
[centos
-
centos7
可以看到主機的名稱仍然為 centos7,並沒有被修改。由此,可以驗證 UTS Namespace 可以用來隔離主機名。
1.5、IPC Namespace
IPC Namespace 主要是用來隔離進程間通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以實現同一 IPC Namespace 內的進程彼此可以通信,不同 IPC Namespace 的進程卻不能通信。
同樣我們通過一個實例來驗證下IPC Namespace的作用,首先我們使用 unshare 命令來創建一個 IPC Namespace:
-
-
[root@centos7 centos]#
下面我們需要借助兩個命令來實現對 IPC Namespace 的驗證。
-
ipcs -q 命令:用來查看系統間通信隊列列表。
-
ipcmk -Q 命令:用來創建系統間通信隊列。
我們首先使用 ipcs -q 命令查看一下當前 IPC Namespace 下的系統通信隊列列表:
-
[centos
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
由上可以看到當前無任何系統通信隊列,然后我們使用 ipcmk -Q 命令創建一個系統通信隊列:
-
[root
-
Message queue id: 0
再次使用 ipcs -q 命令查看當前 IPC Namespace 下的系統通信隊列列表:
-
[root
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
-
0x73682a32 0 root 644 0 0
可以看到我們已經成功創建了一個系統通信隊列。然后我們新打開一個命令行窗口,使用ipcs -q 命令查看一下主機的系統通信隊列:
-
[centos
-
-
------ Message Queues --------
-
key msqid owner perms used-bytes messages
通過上面的實驗,可以發現,在單獨的 IPC Namespace 內創建的系統通信隊列在主機上無法看到。即 IPC Namespace 實現了系統通信隊列的隔離。
1.6、User Namespace
User Namespace 主要是用來隔離用戶和用戶組的。一個比較典型的應用場景就是在主機上以非 root 用戶運行的進程可以在一個單獨的 User Namespace 中映射成 root 用戶。使用 User Namespace 可以實現進程在容器內擁有 root 權限,而在主機上卻只是普通用戶。
User Namesapce 的創建是可以不使用 root 權限的。下面我們以普通用戶的身份創建一個 User Namespace,命令如下:
-
[centos
-
[root
CentOS7 默認允許創建的 User Namespace 為 0,如果執行上述命令失敗( unshare 命令返回的錯誤為 unshare: unshare failed: Invalid argument ),需要使用以下命令修改系統允許創建的 User Namespace 數量,命令為:echo 65535 > /proc/sys/user/max_user_namespaces,然后再次嘗試創建 User Namespace。
然后執行 id 命令查看一下當前的用戶信息:
-
[root
-
uid= 0(root) gid=0(root) groups=0(root),65534(nfsnobody) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
通過上面的輸出可以看到我們在新的 User Namespace 內已經是 root 用戶了。下面我們使用只有主機 root 用戶才可以執行的 reboot 命令來驗證一下,在當前命令行窗口執行 reboot 命令:
-
[root
-
Failed to open /dev/initctl: Permission denied
-
Failed to talk to init daemon.
可以看到,我們在新創建的 User Namespace 內雖然是 root 用戶,但是並沒有權限執行 reboot 命令。這說明在隔離的 User Namespace 中,並不能獲取到主機的 root 權限,也就是說 User Namespace 實現了用戶和用戶組的隔離。
2、Cgroups
Cgroups技術用來限制容器內進程使用CPU、內存的資源的使用量。
2.1、CPU子系統
我首先以 cpu 子系統為例,演示一下cgroups如何限制進程的 cpu 使用時間。由於cgroups的操作很多需要用到 root 權限,我們在執行命令前要確保已經切換到了 root 用戶,以下命令的執行默認都是使用 root 用戶。
第一步:在 cpu 子系統下創建 cgroup
cgroups的創建很簡單,只需要在相應的子系統下創建目錄即可。下面我們到 cpu 子系統下創建測試文件夾:
執行完上述命令后,我們查看一下我們新創建的目錄下發生了什么?
-
-
total 0
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.clone_children
-
--w--w--w-. 1 root root 0 Sep 5 09:19 cgroup.event_control
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.procs
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_period_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_quota_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_period_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_runtime_us
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.shares
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpu.stat
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.stat
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage
-
-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage_percpu
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 notify_on_release
-
-rw-r--r--. 1 root root 0 Sep 5 09:19 tasks
由上可以看到我們新建的目錄下被自動創建了很多文件,其中 cpu.cfs_quota_us 文件代表在某一個階段限制的 CPU 時間總量,單位為微秒。例如,我們想限制某個進程最多使用 1 核 CPU,就在這個文件里寫入 100000(100000 代表限制 1 個核) ,tasks 文件中寫入進程的 ID 即可(如果要限制多個進程 ID,在 tasks 文件中用換行符分隔即可)。
此時,我們所需要的 cgroup 就創建好了。對,就是這么簡單。
第二步:創建進程,加入 cgroup
這里為了方便演示,我先把當前運行的 shell 進程加入 cgroup,然后在當前 shell 運行 cpu 耗時任務(這里利用到了繼承,子進程會繼承父進程的 cgroup)。
使用以下命令將 shell 進程加入 cgroup 中:
查看一下 tasks 文件內容:
-
-
3485
-
3543
其中第一個進程 ID 為當前 shell 的主進程,也就是說,當前 shell 主進程為 3485。
第三步:執行 CPU 耗時任務,驗證 cgroup 是否可以限制 cpu 使用時間
下面,我們使用以下命令制造一個死循環,來提升 cpu 使用率:
執行完上述命令后,我們新打開一個 shell 窗口,使用 top -p 命令查看當前 cpu 使用率,-p 參數后面跟進程 ID,我這里是 3485。
-
-
top - 09:51:35 up 3 days, 22:00, 4 users, load average: 1.59, 0.58, 0.27
-
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
-
-
KiB Mem : 32779616 total, 31009780 free, 495988 used, 1273848 buff/cache
-
KiB Swap: 0 total, 0 free, 0 used. 31852336 avail Mem
-
-
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
-
3485 root 20 0 116336 2852 1688 S 99.7 0.0 2:10.71 bash
通過上面輸出可以看到 3485 這個進程被限制到了只能使用 100 % 的 cpu,也就是 1 個核。說明我們使用 cgroup 來限制 cpu 使用時間已經生效。此時,執行 while 循環的命令行窗口可以使用 Ctrl+c 退出循環。
為了進一步證實 cgroup 限制 cpu 的准確性,我們修改 cpu 限制時間為 0.5 核,命令如下:
同樣使用上面的命令來制造死循環:
保持當前窗口,新打開一個 shell 窗口,使用 top -p 參數查看 cpu 使用率:
-
-
top - 10:05:25 up 3 days, 22:14, 3 users, load average: 1.02, 0.43, 0.40
-
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
-
-
KiB Mem : 32779616 total, 31055676 free, 450224 used, 1273716 buff/cache
-
KiB Swap: 0 total, 0 free, 0 used. 31898216 avail Mem
-
-
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
-
3485 root 20 0 115544 2116 1664 R 50.0 0.0 0:23.39 bash
通過上面輸出可以看到,此時 cpu 使用率已經被限制到了 50%,即 0.5 個核。
驗證完 cgroup 限制 cpu,我們使用相似的方法來驗證 cgroup 對內存的限制。
2.2、memory子系統
第一步:在 memory 子系統下創建 cgroup
同樣,我們查看一下新創建的目錄下發生了什么?
-
total 0
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.clone_children
-
--w--w--w-. 1 root root 0 Sep 5 10:18 cgroup.event_control
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.procs
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.failcnt
-
--w-------. 1 root root 0 Sep 5 10:18 memory.force_empty
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.slabinfo
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.max_usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.failcnt
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.limit_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.max_usage_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.move_charge_at_immigrate
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.numa_stat
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.oom_control
-
----------. 1 root root 0 Sep 5 10:18 memory.pressure_level
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.soft_limit_in_bytes
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.stat
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.swappiness
-
-r--r--r--. 1 root root 0 Sep 5 10:18 memory.usage_in_bytes
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.use_hierarchy
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 notify_on_release
-
-rw-r--r--. 1 root root 0 Sep 5 10:18 tasks
其中 memory.limit_in_bytes 文件代表內存使用總量,單位為 byte。
例如,這里我希望對內存使用限制為 1G,則向 memory.limit_in_bytes 文件寫入 1073741824,命令如下:
第二步:創建進程,加入 cgroup
同樣把當前 shell 進程 ID 寫入 tasks 文件內:
第三步,執行內存測試工具,申請內存
這里我們需要借助一下工具 memtester,memtester 的安裝這里不再詳細介紹了。具體安裝方式可以參考這里。
安裝好 memtester 后,我們執行以下命令:
-
-
memtester version 4.2.2 (64-bit)
-
Copyright (C) 2010 Charles Cazabon.
-
Licensed under the GNU General Public License version 2 (only).
-
-
pagesize is 4096
-
pagesizemask is 0xfffffffffffff000
-
want 1500MB (1572864000 bytes)
-
got 1500MB (1572864000 bytes), trying mlock ...Killed
該命令會申請 1500 M 內存,並且做內存測試。由於上面我們對當前 shell 進程內存限制為 1 G,當 memtester 使用的內存達到 1G 時,cgroup 便將 memtester 殺死。
上面最后一行的輸出結果表示 memtester 想要 1500 M 內存,但是由於 cgroup 限制,達到了內存使用上限,被殺死了,與我們的預期一致。
我們可以使用以下命令,降低一下內存申請,將內存申請調整為 500M:
-
-
memtester version 4.2.2 (64-bit)
-
Copyright (C) 2010 Charles Cazabon.
-
Licensed under the GNU General Public License version 2 (only).
-
-
pagesize is 4096
-
pagesizemask is 0xfffffffffffff000
-
want 500MB (524288000 bytes)
-
got 500MB (524288000 bytes), trying mlock ...locked.
-
Loop 1/1:
-
Stuck Address : ok
-
Random Value : ok
-
Compare XOR : ok
-
Compare SUB : ok
-
Compare MUL : ok
-
Compare DIV : ok
-
Compare OR : ok
-
Compare AND : ok
-
Sequential Increment: ok
-
Solid Bits : ok
-
Block Sequential : ok
-
Checkerboard : ok
-
Bit Spread : ok
-
Bit Flip : ok
-
Walking Ones : ok
-
Walking Zeroes : ok
-
8-bit Writes : ok
-
16-bit Writes : ok
-
-
Done.
這里可以看到,此時 memtester 已經成功申請到 500M 內存並且正常完成了內存測試。