容器其實(shí)是一種沙盒技術(shù)令境。顧名思義,沙盒就是能夠像一個(gè)集裝箱一樣昨稼,把你的應(yīng)用“裝”起來的技術(shù)回还。這樣双戳,應(yīng)用與應(yīng)用之間抛人,因?yàn)橛辛诉吔缍恢劣谙嗷ジ蓴_弛姜;而被裝進(jìn)集裝箱的應(yīng)用,也可以被方便地搬來搬去函匕,這也是 PaaS 最理想的狀態(tài)娱据。
計(jì)算機(jī)的程序和進(jìn)程初解
在將Docker的實(shí)現(xiàn)原理之前我們先來聊一聊計(jì)算機(jī)的程序蚪黑。
計(jì)算機(jī)的程序歸根結(jié)底來說就是一組計(jì)算機(jī)能識(shí)別和執(zhí)行的指令盅惜,運(yùn)行于電子計(jì)算機(jī)上,滿足人們某種需求的信息化工具忌穿。
由于計(jì)算機(jī)只認(rèn)識(shí) 0 和 1抒寂,所以無論用哪種語言編寫這段代碼,最后都需要通過某種方式翻譯成二進(jìn)制文件掠剑,才能在計(jì)算機(jī)操作系統(tǒng)中運(yùn)行起來屈芜。而為了能夠讓這些代碼正常運(yùn)行,我們往往還要給它提供數(shù)據(jù)朴译,比如一個(gè)加法程序需要提供一個(gè)輸入或輸入文件井佑。這些數(shù)據(jù)加上代碼本身的二進(jìn)制文件,放在磁盤上眠寿,就是我們平常所說的一個(gè)“程序”躬翁,也叫代碼的可執(zhí)行鏡像(executable image)。
執(zhí)行程序時(shí)盯拱,首先盒发,操作系統(tǒng)從“程序”中發(fā)現(xiàn)輸入數(shù)據(jù)保存在一個(gè)文件中例嘱,所以這些數(shù)據(jù)就會(huì)被加載到內(nèi)存中待命。同時(shí)宁舰,操作系統(tǒng)又讀取到了計(jì)算加法的指令拼卵,這時(shí),它就需要指示 CPU 完成加法操作蛮艰。而 CPU 與內(nèi)存協(xié)作進(jìn)行加法計(jì)算腋腮,又會(huì)使用寄存器存放數(shù)值、內(nèi)存堆棧保存執(zhí)行的命令和變量壤蚜。同時(shí)低葫,計(jì)算機(jī)里還有被打開的文件,以及各種各樣的 I/O 設(shè)備在不斷地調(diào)用中修改自己的狀態(tài)仍律。
一旦“程序”被執(zhí)行起來嘿悬,它就從磁盤上的二進(jìn)制文件,變成了計(jì)算機(jī)內(nèi)存中的數(shù)據(jù)水泉、寄存器里的值善涨、堆棧中的指令、被打開的文件草则,以及各種設(shè)備的狀態(tài)信息的一個(gè)集合钢拧。這樣一個(gè)程序運(yùn)行起來后的計(jì)算機(jī)執(zhí)行環(huán)境的總和,我們稱之為:進(jìn)程
Docker的實(shí)現(xiàn)
回過頭來我們來看炕横,虛擬機(jī)和容器源内,無論是虛擬機(jī)還是容器都可以理解是在做虛擬化(也就是一臺(tái)機(jī)器當(dāng)做多臺(tái)來使用),虛擬化核心需要解決的問題:資源隔離與資源限制
虛擬機(jī)硬件虛擬化技術(shù)份殿, 通過一個(gè) hypervisor 層和獨(dú)立的Guest OS實(shí)現(xiàn)對(duì)資源的徹底隔離膜钓。
-
容器技術(shù)的核心功能,就是通過約束和修改進(jìn)程的動(dòng)態(tài)表現(xiàn)卿嘲,從而制造一個(gè)邊界颂斜。
- 邊界的實(shí)現(xiàn)方式是利用內(nèi)核的 Cgroup 和 Namespace 特性,此功能完全通過軟件實(shí)現(xiàn)拾枣。
這里雖然容器繼續(xù)也通過Namesapce和Cgroup實(shí)現(xiàn)了隔離沃疮,但它也有其弊端:隔離的不徹底
盡管可以在容器里通過 Mount Namespace 單獨(dú)掛載其他不同版本的操作系統(tǒng)文件,比如 CentOS 或者 Ubuntu梅肤,但這并不能改變共享宿主機(jī)內(nèi)核的事實(shí)司蔬。
隔離機(jī)制
首先,我這里使用centos7系統(tǒng)先嘗試啟動(dòng)一個(gè)容器姨蝴,去觀察它一下:
[root@cluster1 ~]$ docker run -it busybox /bin/sh
/ $ ps aux
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps aux
這里可以看到一個(gè)比較有趣的事情俊啼,我們?nèi)ゲ榭催M(jìn)程時(shí),我在容器中運(yùn)行的/bin/sh
進(jìn)程ID變?yōu)榱恕?”似扔,熟悉centos的人都知道進(jìn)程為“1”的應(yīng)該是systemd
程序吨些,而不是我運(yùn)行的/bin/sh
搓谆,這其實(shí)就是Docker對(duì)我們運(yùn)行的進(jìn)程使用了障眼法,對(duì)被隔離的應(yīng)用的進(jìn)程空間做了手腳豪墅,使得這個(gè)進(jìn)程只能看到重新計(jì)算過的進(jìn)程編號(hào)泉手,比如 PID=1。這其實(shí)就是Linux當(dāng)中的Namespace機(jī)制偶器,這里的PID namespace被隔離后就會(huì)呈現(xiàn)這樣的效果斩萌。
除此以外,Linux還提供了Mount屏轰、UTS颊郎、IPC、Network 和 User 這些 Namespace霎苗,用來對(duì)各種不同的進(jìn)程上下文進(jìn)行“障眼法”操作姆吭。
這就是Docker對(duì)于隔離機(jī)制的實(shí)現(xiàn)。下面我來深入的了解一下
namesapce 名稱 | 隔離的資源 |
---|---|
Mount | Linux 內(nèi)核實(shí)現(xiàn)的第一個(gè) Namespace唁盏,Mount points(文件系統(tǒng)掛載點(diǎn)) |
IPC | System V IPC(信號(hào)量内狸、消息隊(duì)列、共享內(nèi)存) 和POSIX MESSAGE QUEUES |
Network | Network devices厘擂、stacks昆淡、ports(網(wǎng)絡(luò)設(shè)備、網(wǎng)絡(luò)棧刽严、端口等) |
PID | Process IDs(進(jìn)程編號(hào)) |
User | User and Groups IDs(用戶和用戶組) |
UTS | Hostname and NIS domain name(主機(jī)名與NIS域名) |
同樣的道理昂灵,我們?nèi)ビ^察一個(gè)運(yùn)行的docker容器,然后手動(dòng)的去模擬實(shí)現(xiàn)它的隔離舞萄,為了方便理解眨补,先確認(rèn)宿主機(jī)存在的內(nèi)容,并且制造一些可以用來區(qū)別的數(shù)據(jù)
[root@cluster1 ~]$ touch /root/host.txt
# 宿主機(jī)上制作一個(gè)掛載點(diǎn)
[root@cluster1 ~]$ mkdir /tmp/tmpfs
[root@cluster1 ~]$ mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs
[root@cluster1 ~]$ df -h /tmp/tmpfs/
Filesystem Size Used Avail Use% Mounted on
tmpfs 20M 0 20M 0% /tmp/tmpfs
# 查看宿主機(jī)主機(jī)名
[root@cluster1 ~]$ hostname
cluster1
# 查看宿主機(jī)進(jìn)程
[root@cluster1 ~]$ ps aux | head
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 191056 3960 ? Ss 21:30 0:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root 2 0.0 0.0 0 0 ? S 21:30 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? S< 21:30 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? S 21:30 0:00 [ksoftirqd/0]
root 7 0.0 0.0 0 0 ? S 21:30 0:00 [migration/0]
....
# 為了驗(yàn)證IPC namespace鹏氧,需要?jiǎng)?chuàng)建一個(gè)系統(tǒng)間通信隊(duì)列
# ipcmk -Q 命令:用來創(chuàng)建系統(tǒng)間通信隊(duì)列渤涌。
# ipcs -q 命令:用來查看系統(tǒng)間通信隊(duì)列列表佩谣。
[root@cluster1 ~]$ ipcmk -Q
[root@cluster1 ~]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x48d07ddf 0 root 644 0 0
# 查看宿主機(jī)用戶數(shù)量個(gè)數(shù)
[root@cluster1 ~]$ cat /etc/passwd | wc -l
20
# 查看宿主機(jī)網(wǎng)絡(luò)
[root@cluster1 ~]$ 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: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:88:77:6c brd ff:ff:ff:ff:ff:ff
inet 10.10.1.100/24 brd 10.10.1.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::20c:29ff:fe88:776c/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:b7:30:58:e4 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:b7ff:fe30:58e4/64 scope link
valid_lft forever preferred_lft forever
# 觀察宿主機(jī)內(nèi)核
[root@cluster1 ~]$ uname -a
Linux cluster1 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
下面開始觀察docker容器
# 啟動(dòng)一個(gè)容器
[root@cluster1 ~]$ docker run -it busybox /bin/sh
# 完全獨(dú)立的目錄(非宿主機(jī)目錄)
/ $ ls /
bin dev etc home lib lib64 proc root sys tmp usr var
/ $ ls /root
/ $ df -h /tmp/tmpfs/
Filesystem Size Used Available Use% Mounted on
df: /tmp/tmpfs/: can't find mount point
# 獨(dú)立的主機(jī)名
/# hostname
d87b4acb91fc
# 獨(dú)立的進(jìn)程
/$ ps aux
PID USER TIME COMMAND
1 root 0:00 /bin/sh
7 root 0:00 ps aux
# 獨(dú)立的系統(tǒng)通信隊(duì)列
/$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
# 獨(dú)立的用戶
/$ cat /etc/passwd | wc -l
9
# 獨(dú)立的網(wǎng)絡(luò)
/$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 相同的宿主機(jī)內(nèi)核
/$ uname -a
Linux d87b4acb91fc 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 GNU/Linux
OK把还,現(xiàn)在驗(yàn)證了我們上面的觀點(diǎn),容器是被沙河隔離的茸俭,下面我們?cè)賮砜纯词侨绾螌?shí)現(xiàn)的吊履?
我們進(jìn)行一下,用namespace模擬這個(gè)過程调鬓,看看我們能否達(dá)到docker的效果
首先我們先來了解一個(gè)命令—unshare
作用:一個(gè)用來取消與父進(jìn)程共享指定的命名空間
[root@cluster1 ~]# unshare --help
Usage: unshare [options] <program> [<argument>...]
Run a program with some namespaces unshared from the parent.
Options: -m, --mount unshare mounts namespace -u, --uts unshare UTS namespace (hostname etc) -i, --ipc unshare System V IPC namespace -n, --net unshare network namespace -p, --pid unshare pid namespace -U, --user unshare user namespace -f, --fork fork before launching <program> --mount-proc[=<dir>] mount proc filesystem first (implies --mount) -r, --map-root-user map current user to root (implies --user) --propagation <slave|shared|private|unchanged> modify mount propagation in mount namespace
# 我們通過unshare模擬容器的創(chuàng)建
# CentOS7 默認(rèn)允許創(chuàng)建的 User Namespace 為 0
# 所以需要先echo 65535 > /proc/sys/user/max_user_namespaces 打開限制
[root@cluster1 ~]$ echo 65535 > /proc/sys/user/max_user_namespaces
[root@cluster1 ~]$ unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash
# 沒有獨(dú)立的目錄(獨(dú)立思考一下艇炎,后面會(huì)講)
[root@cluster1 ~]$ ls /root/host.txt
/root/host.txt
# 沒有獨(dú)立的mount掛載(獨(dú)立思考一下,后面會(huì)講)
[root@cluster1 ~]$ df -h /tmp/tmpfs/
Filesystem Size Used Avail Use% Mounted on
tmpfs 20M 0 20M 0% /tmp/tmpfs
# 看起來沒有獨(dú)立的主機(jī)名
[root@cluster1 ~]$ hostname
cluster1
# 獨(dú)立的進(jìn)程
[root@cluster1 ~]$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 116204 2912 pts/1 S 22:58 0:00 /bin/bash
root 25 0.0 0.0 155472 1852 pts/1 R+ 23:00 0:00 ps aux
# 獨(dú)立的系統(tǒng)通信隊(duì)列
[root@cluster1 ~]$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
# 獨(dú)立的網(wǎng)絡(luò)
[root@cluster1 ~]$ 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
# 看起來沒有獨(dú)立的用戶
[root@cluster1 ~]$ cat /etc/passwd | wc -l
20
從上邊論證腾窝,我們還缺乏幾個(gè)沒有隔離的掛載點(diǎn)缀踪、主機(jī)名居砖、目錄和用戶,我們接著分析
# 我們?cè)趫?zhí)行'unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash' 之后的終端繼續(xù)驗(yàn)證
# 嘗試修改主機(jī)名
$ hostname -b yijiuweishu
$ hostname
yijiuweishu
# 新增掛載點(diǎn)
$ mkdir /tmp/containerfs
$ mount -t tmpfs -o size=20m tmpfs /tmp/containerfs
$ df -h /tmp/containerfs/
Filesystem Size Used Avail Use% Mounted on
tmpfs 20M 0 20M 0% /tmp/containerfs
在新開啟一個(gè)終端,去查看宿主機(jī)的主機(jī)名和掛載點(diǎn)
[root@cluster1 ~]$ hostname
cluster1
[root@cluster1 ~]$ df -h /tmp/containerfs/
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 49G 2.4G 47G 5% /
可以看到實(shí)際上驴娃,主機(jī)名和掛載點(diǎn)也是獨(dú)立的奏候,只不過剛?cè)∠蚕砟且豢蹋覀儾蛔霾僮鞔匠ǎ鳈C(jī)名和掛載點(diǎn)是和宿主機(jī)一致的蔗草。
再去驗(yàn)證user namespace
User Namespace 主要是用來隔離用戶和用戶組的。
一個(gè)比較典型的應(yīng)用場(chǎng)景就是在主機(jī)上以非 root 用戶運(yùn)行的進(jìn)程可以在一個(gè)單獨(dú)的 User Namespace 中映射成 root 用戶疆柔。使用 User Namespace 可以實(shí)現(xiàn)進(jìn)程在容器內(nèi)擁有 root 權(quán)限咒精,而在主機(jī)上卻只是普通用戶。
而不是以root用戶映射成root用戶旷档,這樣映射后還是有root的權(quán)限
# 我們體驗(yàn)下用普通用戶去取消共享user namespace
[root@cluster1 ~]$ useradd test
[root@cluster1 ~]$ su - test
Last login: Fri Mar 17 23:08:56 CST 2023 on pts/2
[test@cluster1 ~]$ unshare --user -r --fork /bin/bash
# 可以看到模叙,雖然我們變成了root用戶,但是并不能調(diào)用一些root能執(zhí)行的命令鞋屈,由此可以推斷我們是進(jìn)行了用戶隔離
[root@cluster1 ~]$ reboot
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.
再來說一下我們看見的還是宿主機(jī)目錄向楼,這里會(huì)引申出一個(gè)新的概念
根文件系統(tǒng)rootfs
根文件系統(tǒng)首先是內(nèi)核啟動(dòng)時(shí)所mount(掛載)的第一個(gè)文件系統(tǒng),內(nèi)核代碼映像文件保存在根文件系統(tǒng)中谐区,而系統(tǒng)引導(dǎo)啟動(dòng)程序會(huì)在根文件系統(tǒng)掛載之后從中把一些基本的初始化腳本和服務(wù)等加載到內(nèi)存中去運(yùn)行湖蜕。
根文件系統(tǒng)一般叫做rootfs,這里所謂的文件系統(tǒng)并不是指FAT宋列、FAT32昭抒、NTFS、XFS這類的文件系統(tǒng)炼杖,熟悉Linux的人都知道灭返,Linux一切皆文件,且目錄結(jié)構(gòu)都在 /
下開始坤邪,這里的根文件系統(tǒng)指的就是/
熙含。
namespace雖然解決了共享問題,但沒有改變?nèi)萜鞯母夸浲Х模匀萜髦惺峭ㄟ^rootfs來實(shí)現(xiàn)的怎静。
切換新的根目錄,我們需要一個(gè)根文件系統(tǒng)(有自己的/bin
黔衡、/dev
蚓聘、/sys
、/proc
等)
# 獲取一個(gè)根文件系統(tǒng)
# 方式一
$ wget https://github.com/ericchiang/containers-from-scratch/releases/download/v0.1.0/rootfs.tar.gz
# 方式二(docker每個(gè)容器也有自己的根文件系統(tǒng)盟劫,我們拷貝一份)
$ docker run -d -i busybox /bin/sh
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6bd3747bc339 busybox "/bin/sh" 3 minutes ago Up 3 minutes heuristic_napier
$ docker export 6bd3747bc339 -o busybox.tar
$ tar xf busybox.tar
$ ls
bin busybox.tar dev etc home lib lib64 proc root sys tmp usr var
chroot 隔離
chroot是在 Unix 和 Linux 系統(tǒng)的一個(gè)操作夜牡,針對(duì)正在運(yùn)作的軟件行程和它的子進(jìn)程,改變的根目錄侣签。使它不能對(duì)改變后的目錄之外的訪問(讀寫塘装、查看)
- chroot 是通過指定 新的根目錄 和運(yùn)行的命令組成急迂,所以這意味著新的根目錄中也要有可執(zhí)行的命令和層次結(jié)構(gòu)
# chroot NEWROOT [COMMAND [ARG]...]
# 注意 此處的COMMAND需要是新的根目錄中存在的
[root@cluster1 ~]$ pwd
/root
[root@cluster1 ~]$ mkdir rootfs
[root@cluster1 ~]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6bd3747bc339 busybox "/bin/sh" 3 minutes ago Up 3 minutes heuristic_napier
[root@cluster1 ~]$ docker export 6bd3747bc339 -o busybox.tar
# 將busybox的根文件系統(tǒng) 放到/root/rootfs/下
[root@cluster1 ~]$ tar xf busybox.tar -C rootfs/
# 切換根目錄
[root@cluster1 ~]$ chroot rootfs/ /bin/bash
# 這表示busybox中,不存在bash終端
chroot: failed to run command ‘/bin/bash’: No such file or director
[root@cluster1 ~]$ chroot rootfs/ /bin/sh
/ # /bin/ls
bin dev etc home lib lib64 proc root sys tmp usr var
/ # /bin/ls /root
/ #
pivot_root隔離
pivot_root把當(dāng)前進(jìn)程的root文件系統(tǒng)放在put_old目錄蹦肴,而使new_root成為新的root文件系統(tǒng)
new_root 與 put_old 必須是文件夾
new_root文件夾必須是一個(gè)掛載點(diǎn) 袋毙,并且new_root文件夾里面有完整rootfs的各種文件
new_root 文件夾掛載應(yīng)該是一個(gè)與主機(jī)不同的namespace
put_old文件夾必須在new_root文件夾內(nèi)
這從某方面也解釋了,為什么有時(shí)候docker run一個(gè)容器后冗尤,宿主機(jī)會(huì)多一個(gè)掛載點(diǎn)(/var/lib/docker/overlay2/xxx/merged)
- pviot_root主要是把整個(gè)系統(tǒng)切換到一個(gè)新的root目錄听盖,然后去掉對(duì)之前root文件系統(tǒng)的依賴,以便于可以u(píng)mount 之前的文件系統(tǒng)(pivot_root需要root權(quán)限)
- chroot是只改變即將運(yùn)行的某進(jìn)程的根目錄裂七,而系統(tǒng)的其他部分依舊依賴于老的root文件系統(tǒng)
在Docker中皆看,會(huì)優(yōu)先調(diào)用pivot_root,如果系統(tǒng)不支持才會(huì)使用 chroot
# 宿主機(jī)/root 目錄
[root@cluster1 ~]$ ls
anaconda-ks.cfg busybox.tar host.txt rootfs
[root@cluster1 ~]$ unshare --mount --fork /bin/bash #需要有獨(dú)立的命名空間
[root@cluster1 ~]$ mkdir /new_root
[root@cluster1 ~]$ mount -t tmpfs mytmpfs /new_root #new_root是一個(gè)獨(dú)立的掛載點(diǎn)
[root@cluster1 ~]$ mkdir /new_root/old_root # 可以是任意名字背零,
[root@cluster1 ~]$ tar xf busybox.tar -C /new_root
# busybox文件系統(tǒng)的/root
[root@cluster1 ~]$ ls /new_root/root
[root@cluster1 ~]$ touch /new_root/root/container.txt
[root@cluster1 ~]$ ls /new_root/root/
container.txt
# pivot_root 切換根文件系統(tǒng)
[root@cluster1 ~]$ pivot_root /new_root /new_root/old_root
[root@cluster1 ~]$ ls
bash: /usr/bin/ls: No such file or directory # 沒有環(huán)境變量了
[root@cluster1 ~]$ /bin/ls /root/
container.txt
從另一個(gè)角度驗(yàn)證
Linux中每個(gè)進(jìn)程都會(huì)記錄其所使用的namespace(一串id)腰吟,下面我們來找一找
我們要知道一個(gè)環(huán)境變量, $$ 用于表示當(dāng)前的終端所在的進(jìn)程ID
# 首先查看當(dāng)前系統(tǒng)所用的namespace
[root@cluster1 ~]$ echo $$
5210
[root@cluster1 ~]$ ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 18 00:22 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 18 00:22 uts -> uts:[4026531838]
# 查看一個(gè)容器所用的namesapce
[root@cluster1 ~]$ docker run -it busybox /bin/sh
/ # echo $$
1
/ # ls -l /proc/1/ns/
total 0
lrwxrwxrwx 1 root root 0 Mar 17 16:23 ipc -> ipc:[4026532700]
lrwxrwxrwx 1 root root 0 Mar 17 16:23 mnt -> mnt:[4026532698]
lrwxrwxrwx 1 root root 0 Mar 17 16:23 net -> net:[4026532703]
lrwxrwxrwx 1 root root 0 Mar 17 16:23 pid -> pid:[4026532701]
lrwxrwxrwx 1 root root 0 Mar 17 16:23 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Mar 17 16:23 uts -> uts:[4026532699]
# 再來試試我們手動(dòng)實(shí)現(xiàn)的namesapce 隔離徙瓶,是否生效
[root@cluster1 ~]$ unshare --mount --pid --mount-proc --uts --ipc --user -r --net --fork /bin/bash
[root@cluster1 ~]$ ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Mar 18 00:25 ipc -> ipc:[4026532699]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 mnt -> mnt:[4026532697]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 net -> net:[4026532702]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 pid -> pid:[4026532700]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 user -> user:[4026532633]
lrwxrwxrwx 1 root root 0 Mar 18 00:25 uts -> uts:[4026532698]
從這個(gè)角度看毛雇,我們手動(dòng)
unshare
的也都是獨(dú)立的namespace,也論證了我們的觀點(diǎn)侦镇。當(dāng)然Docker可不僅僅只是創(chuàng)建了namesapce灵疮,實(shí)際上還做了很多細(xì)節(jié)上的處理
Cgroup資源限制
通過namespace可以保證容器之間的隔離,但是無法控制每個(gè)容器可以占用多少資源壳繁, 如果其中的某一個(gè)容器正在執(zhí)行 CPU 密集型的任務(wù)震捣,那么就會(huì)影響其他容器中任務(wù)的性能與執(zhí)行效率,導(dǎo)致多個(gè)容器相互影響并且搶占資源闹炉。如何對(duì)多個(gè)容器的資源使用進(jìn)行限制就成了解決進(jìn)程虛擬資源隔離之后的主要問題蒿赢。
什么是Cgroup?
Control Groups(簡(jiǎn)稱 CGroups)就是能夠隔離宿主機(jī)器上的物理資源渣触,例如 CPU羡棵、內(nèi)存、磁盤 I/O 和網(wǎng)絡(luò)帶寬嗅钻。每一個(gè) CGroup 都是一組被相同的標(biāo)準(zhǔn)和參數(shù)限制的進(jìn)程皂冰。而我們需要做的,其實(shí)就是把容器這個(gè)進(jìn)程加入到指定的Cgroup中啊犬。
類似如上的圖片表示灼擂,創(chuàng)建了 2 個(gè) cgroup(每個(gè) cgroup 有 4 個(gè)進(jìn)程),并且限制它們各自最多只能使用 2GB 的內(nèi)存觉至。如果使用超過 2GB 的內(nèi)存,那么將會(huì)觸發(fā) OOM(Out Of Memory) 睡腿。
cgroup原理和用法并不復(fù)雜语御,但其內(nèi)核數(shù)據(jù)結(jié)構(gòu)特別復(fù)雜峻贮,錯(cuò)綜復(fù)雜的數(shù)據(jù)結(jié)構(gòu)感覺才是cgroup真正的難點(diǎn),了解即可应闯。
感興趣可以看看:https://github.com/dongzhiyan-stack/kernel-code-comment/blob/master/linux-3.10.96/kernel/cgroup.c
Cgroup的特點(diǎn)
cgroups的API以一個(gè)偽文件系統(tǒng)的方式實(shí)現(xiàn)纤控,用戶態(tài)的程序可以通過文件操作實(shí)現(xiàn)cgroups 的組織
cgroups的組織管理操作單元可以細(xì)粒到線程級(jí)別,用戶可以創(chuàng)建銷毀cgroups碉纺,從而實(shí)現(xiàn)資源再分配管理
所有資源管理的功能都以子系統(tǒng)方式實(shí)現(xiàn)船万,接口統(tǒng)一
子任務(wù)創(chuàng)建之初與其父進(jìn)程處于同一個(gè)cgroups的控制組
Cgroup的作用
作用 | 說明 |
---|---|
資源限制 | cgroups可以對(duì)任務(wù)使用的資源總額進(jìn)行限制,如設(shè)定應(yīng)用運(yùn)行時(shí)使用的內(nèi)存上限骨田,一但超過這個(gè)配額就發(fā)出OOM提示 |
優(yōu)先級(jí)分配 | 通過分配的CPU時(shí)間片數(shù)量及磁盤IO帶寬大小耿导,實(shí)際就相當(dāng)于控制了任務(wù)運(yùn)行的優(yōu)先級(jí) |
資源統(tǒng)計(jì) | cgroups 可以統(tǒng)計(jì)系統(tǒng)的資源使用量,如CPU時(shí)長(zhǎng)态贤,內(nèi)存使用量舱呻,這個(gè)功能非常適用于計(jì)費(fèi) |
任務(wù)控制 | cgroups可以對(duì)任務(wù)執(zhí)行掛起 |
Cgroup的術(shù)語
術(shù)語 | 說明 |
---|---|
task(任務(wù)) | 在cgroups的術(shù)語中,任務(wù)表示系統(tǒng)的一個(gè)進(jìn)程或者線程 |
cgroups(控制組) | cgroups中的資源控制都以cgroups以單位實(shí)現(xiàn)悠汽,cgroups表示按某種資源控制標(biāo)準(zhǔn)劃分而成的任務(wù)組箱吕,包含一個(gè)或多個(gè)子系統(tǒng)茬高,一個(gè)任務(wù)加入某個(gè)cgroups雅采,也可以從某個(gè)cgroups簽到另一cgroups |
subsystem(子系統(tǒng)) | cgroups中的子系統(tǒng)就是一個(gè)資源調(diào)度控制器。CPU子系統(tǒng)可以控制從CPU時(shí)間分配,內(nèi)存子系統(tǒng)可以設(shè)置cgroups的內(nèi)存使用量 |
hierarchy(層級(jí)) | 層級(jí)由一系列cgroups以一個(gè)樹狀結(jié)構(gòu)排列而成胡陪,每個(gè)層級(jí)通過綁定對(duì)應(yīng)的子系統(tǒng)進(jìn)行資源控制。層級(jí)中cgroups的節(jié)點(diǎn)可以包含零個(gè)節(jié)點(diǎn)或多個(gè)子節(jié)點(diǎn),子節(jié)點(diǎn)繼承父節(jié)點(diǎn)掛載的子系統(tǒng)吹泡。整個(gè)操作系統(tǒng)可以有多個(gè)層級(jí) |
Cgroup子系統(tǒng)
一個(gè)子系統(tǒng)代表一類資源調(diào)度控制器爆哑。例如內(nèi)存子系統(tǒng)可以限制內(nèi)存的使用量揭朝,CPU 子系統(tǒng)可以限制 CPU 的使用時(shí)間潭袱。子系統(tǒng)是真正實(shí)現(xiàn)某類資源的限制的基礎(chǔ)敌卓。
Subsystem(子系統(tǒng)) cgroups 中的子系統(tǒng)就是一個(gè)資源調(diào)度控制器(又叫 controllers)
最終在Linux表現(xiàn)其實(shí)是一個(gè)文件系統(tǒng)
下面我們?cè)贑entos7上查看一下Cgroup的子系統(tǒng)(Centos7 默認(rèn)systemd也會(huì)使用cgroup)
# 控制cgroup可以使用命令工具行,lssubsys
[root@cluster1 ~]$ yum install -y libcgroup-tools
[root@cluster1 ~]$ lssubsys -m
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
blkio /sys/fs/cgroup/blkio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids
# 通過查看文件也是一樣
[root@cluster1 ~]$ ll /sys/fs/cgroup/
total 0
drwxr-xr-x 2 root root 0 Mar 18 11:57 blkio
lrwxrwxrwx 1 root root 11 Mar 18 11:57 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Mar 18 11:57 cpuacct -> cpu,cpuacct
drwxr-xr-x 2 root root 0 Mar 18 11:57 cpu,cpuacct
drwxr-xr-x 2 root root 0 Mar 18 11:57 cpuset
drwxr-xr-x 3 root root 0 Mar 18 12:01 devices
drwxr-xr-x 2 root root 0 Mar 18 11:57 freezer
drwxr-xr-x 2 root root 0 Mar 18 11:57 hugetlb
drwxr-xr-x 2 root root 0 Mar 18 11:57 memory
lrwxrwxrwx 1 root root 16 Mar 18 11:57 net_cls -> net_cls,net_prio
drwxr-xr-x 2 root root 0 Mar 18 11:57 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Mar 18 11:57 net_prio -> net_cls,net_prio
drwxr-xr-x 2 root root 0 Mar 18 11:57 perf_event
drwxr-xr-x 2 root root 0 Mar 18 11:57 pids
drwxr-xr-x 4 root root 0 Mar 18 11:57 systemd
# 通過掛載點(diǎn)查看關(guān)聯(lián)的cgroup子系統(tǒng)
[root@cluster1 ~]$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
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)
cpu:使用調(diào)度程序控制任務(wù)對(duì)cpu的使用
cpuacct:自動(dòng)生成cgroup中任務(wù)對(duì)cpu資源使用情況的報(bào)告
cpuset:主要用于設(shè)置CPU和內(nèi)存的親和性
blkio:塊設(shè)備 I/O 限制
devices:可以開啟或關(guān)閉cgroup中任務(wù)對(duì)設(shè)備的訪問
freezer: 可以掛起或恢復(fù)cgroup中的任務(wù)
pids:限制任務(wù)數(shù)量
memory:可以設(shè)定cgroup中任務(wù)對(duì)內(nèi)存使用量的限定,并且自動(dòng)生成這些任務(wù)對(duì)內(nèi)存資源使用情況的報(bào)告
perf_event:增加了對(duì)每 group 的監(jiān)測(cè)跟蹤的能力,可以監(jiān)測(cè)屬于某個(gè)特定的 group 的所有線程以及運(yùn)行在特定CPU上的線程
net_cls:docker沒有直接使用它鸥跟,它通過使用等級(jí)識(shí)別符標(biāo)記網(wǎng)絡(luò)數(shù)據(jù)包医咨,從而允許linux流量控制程序識(shí)別從具體cgroup中生成的數(shù)據(jù)包
Cgroup控制組
控制組 說白了就是一組進(jìn)程(進(jìn)程組),cgroup 就是用來限制 控制組 的資源使用拟淮。為了能夠方便地向一個(gè) 控制組 添加或者移除進(jìn)程(在命令行也能操作)谴忧,內(nèi)核使用了 虛擬文件系統(tǒng) 來進(jìn)行管理 控制組很泊。
控制組可以類比成Linux的目錄樹結(jié)構(gòu),由于目錄有層級(jí)關(guān)系撑蚌,所以 控制組 也有層級(jí)關(guān)系
每個(gè)控制組目錄中搏屑,都有一個(gè)名為 tasks 的文件辣恋,用于保存當(dāng)前 控制組 包含的進(jìn)程列表。如果我們想向某個(gè) 控制組 添加一個(gè)進(jìn)程時(shí)携狭,可以把進(jìn)程的 PID 寫入到 tasks 文件中即可稀并。
在 Linux 內(nèi)核中,可以存在多個(gè) 層級(jí)(控制組樹)单默,每個(gè)層級(jí)可以關(guān)聯(lián)一個(gè)或多個(gè) 資源控制子系統(tǒng)碘举,但同一個(gè) 資源控制子系統(tǒng) 不能關(guān)聯(lián)到多個(gè)層級(jí)中。如下圖所示:
直白來說搁廓,即/cgrp4關(guān)聯(lián)在/cgrp1下引颈,所以不能再直接關(guān)聯(lián)/sys/fs/cgroup/的某個(gè)子系統(tǒng)了
# 如果用戶想把資源控制子系統(tǒng)關(guān)聯(lián)到其他層級(jí),那么可以使用 mount 命令來進(jìn)行掛載
# 將內(nèi)存子系統(tǒng)重新關(guān)聯(lián)到 /sys/fs/cgroup/memory 這個(gè)層級(jí)
mount -t cgroup -o memory memory /sys/fs/cgroup/memory
動(dòng)手操作
為了不被冗余的篇幅影響閱讀體驗(yàn)境蜕,這里以cpuset設(shè)置獨(dú)占cpu舉例
其他的子系統(tǒng)請(qǐng)參考我的另一篇文章:
cpuset子系統(tǒng)
先來看下cpuset子系統(tǒng)下的文件作用
文件 | 說明 |
---|---|
cpuset.cpus | 允許cgroup中的進(jìn)程使用的CPU列表蝙场。如0-2,16代表 0,1,2,16這4個(gè)CPU |
cpuset.cpu_exclusive | cgroup是否獨(dú)占cpuset.cpus 中分配的cpu 。(默認(rèn)值0粱年,共享售滤;1,獨(dú)占)逼泣,如果設(shè)置為1趴泌,其他cgroup內(nèi)的cpuset.cpus值不能包含有該cpuset.cpus內(nèi)的值 |
cpuset.mems | 允許cgroup中的進(jìn)程使用的內(nèi)存節(jié)點(diǎn)列表。如0-2,16代表 0,1,2,16這4個(gè)可用節(jié)點(diǎn) |
cpuset.mem_exclusive | 是否獨(dú)占memory拉庶,(默認(rèn)值0嗜憔,共享;1氏仗,獨(dú)占) |
cpuset.mem_hardwall | cgroup中任務(wù)的內(nèi)存是否隔離吉捶,(默認(rèn)值0夺鲜,不隔離;1呐舔,隔離币励,每個(gè)用戶的任務(wù)將擁有獨(dú)立的空間) |
cpuset.memory_pressure | 衡量cpuset中內(nèi)存分頁(yè)壓力的大小 |
cpuset.memory_spread_page | 是否在允許的節(jié)點(diǎn)上均勻分布頁(yè)面緩存 |
cpuset.memory_spread_slab | 是否將 slab 緩存均勻分布在允許的節(jié)點(diǎn)上 |
cpuset.sched_load_balance | 是否在該 cpuset 上的 CPU 內(nèi)進(jìn)行負(fù)載平衡 |
cpuset.sched_relax_domain_level | 遷移任務(wù)時(shí)的搜索范圍 |
cpuset.memory_pressure_enabled | 僅在root cgroup中存在,表示是否計(jì)算內(nèi)存壓力 |
簡(jiǎn)單了解一下stress命令
stress --cpu 2 --timeout 600
表示使用stress命令模擬將2個(gè)CPU使用到百分之百珊拼,一會(huì)我們只綁定一個(gè)CPU食呻,用stress壓一下,看看會(huì)有幾個(gè)CPU被占滿
# 由于centos7已經(jīng)使用了cgroup澎现,所以不需要我們額外掛載
# mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
cd /sys/fs/cgroup/cpuset
mkdir wzp
cd wzp
echo 1 > cpuset.cpus
echo 0 > cpuset.mems
/bin/echo $$ > tasks
bash
# 這里執(zhí)行之后仅胞,我們新執(zhí)行的bash終端是在wzp這個(gè)cgroup的限制中的,也就是說剑辫,他僅可以使用1號(hào)CPU
cat /proc/self/cpuset
# 執(zhí)行命令干旧,模擬讓兩個(gè)CPU 100%
stress --cpu 2 --timeout 600
# 另開一個(gè)終端,top看看實(shí)際情況
PS:要?jiǎng)h除/sys/fs/cgroup/xxx/下 自己創(chuàng)建的目錄妹蔽,不能用 rm -rf 只能用rmdir
觀察Docker容器
docker的cgroup存放在/sys/fs/cgroups/某子系統(tǒng)/docker/<container-ID>
下面我們啟動(dòng)一個(gè)docker椎眯,并設(shè)置上cpuset、cpu和memory限制來驗(yàn)證我們的觀點(diǎn)
# 設(shè)置nginx容器使用CPU0-1胳岂,共享CPU512编整,內(nèi)存大小500m
[root@cluster1 ~]$ docker run -d --cpuset-cpus="0-1" --cpu-shares=512 --memory=500m nginx:alpine
4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6
# 查看cgroup配置
[root@cluster1 ~]$ cat /sys/fs/cgroup/cpuset/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/cpuset.cpus
0-1
[root@cluster1 ~]$ cat /sys/fs/cgroup/cpu/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/cpu.shares
512
[root@cluster1 ~]$ cat /sys/fs/cgroup/memory/docker/4431d5a9c9ff657dc826a947c8c556e96b2e66722bd0e65932f77ed8357769e6/memory.limit_in_bytes
524288000
下一篇,Docker的實(shí)現(xiàn)原理——網(wǎng)絡(luò)