摘要:底層技術(shù)用于環(huán)境隔離,支持的包括以及新加入的等,用于隔離主機名和域名,使用標(biāo)識,用于隔離進(jìn)程間通信資源如消息隊列等,使用標(biāo)識,隔離進(jìn)程,用于隔離網(wǎng)絡(luò),用于隔離掛載點,用于隔離用戶組。
本文已獲得原作者_(dá)_七把刀__授權(quán)。
Docker 容器技術(shù)已經(jīng)發(fā)展了好些年,在很多項目都有應(yīng)用,線上運行也很穩(wěn)定。整理了部分 Docker 的學(xué)習(xí)筆記以及新版本特性,對Docker感興趣的同學(xué)可以看看,之前整理過的 Linux namespace 可以見之前的博文。1容器 & Docker & 虛擬機
Container (容器)是一種輕量級的虛擬化技術(shù),它不需要模擬硬件創(chuàng)建虛擬機。在 Linux 系統(tǒng)里面,使用到 Linux kernel 的 cgroups,namespace(ipc,network, user,pid,mount),capability 等用于隔離運行環(huán)境和資源限制的技術(shù),我們稱之為容器。容器技術(shù)早就出現(xiàn)。例如 Solaris Zones 和 BSD jails 就是非 Linux 操作系統(tǒng)上的容器,而用于 Linux 的容器技術(shù)也有很多如 Linux-Vserver、OpenVZ 和 FreeVPS。雖然這些技術(shù)都已經(jīng)成熟,但是這些解決方案還沒有將它們的容器支持集成到主流 Linux 內(nèi)核。總的來說,容器不等同于 Docker,容器更不是虛擬機。
LXC 項目由一個 Linux 內(nèi)核補丁和一些 userspace 工具組成,它提供一套簡化的工具來維護(hù)容器,用于虛擬環(huán)境的環(huán)境隔離、資源限制以及權(quán)限控制。LXC 有點類似 chroot,但是它比 chroot 提供了更多的隔離性。
Docker 最初目標(biāo)是做一個特殊的 LXC 的開源系統(tǒng),最后慢慢演變?yōu)樗约旱囊惶兹萜鬟\行時環(huán)境。Docker 基于 Linux kernel 的 CGroups,Namespace,UnionFileSystem 等技術(shù)封裝成一種自定義的容器格式,用于提供一整套虛擬運行環(huán)境。毫無疑問,近些年來 Docker 已經(jīng)成為了容器技術(shù)的代名詞,如其官網(wǎng)介紹的Docker is world"s leading software containerization platform。本文會先簡單介紹 Docker 基礎(chǔ)概念,然后會分析下 Docker 背后用到的技術(shù)。Debian 上安裝 Docker 方法參見docker-ce-installation-in-debian。
Docker 提供了一個打包和運行應(yīng)用的隔離環(huán)境,稱之為容器,Docker 的隔離和安全特性允許你在一個主機同時運行多個容器,而且它并不像虛擬機那樣重量級,容器都是基于宿主機的內(nèi)核運行的,它是輕量的,不管你運行的是ubuntu, debian 還是其他 Linux 系統(tǒng),用的內(nèi)核都是宿主機內(nèi)核。Docker 提供了工具和平臺來管理容器,而 Docker Engine 則是一個提供了大部分功能組件的CS架構(gòu)的應(yīng)用,如架構(gòu)圖所示,Docker Engine 負(fù)責(zé)管理鏡像,容器,網(wǎng)絡(luò)以及數(shù)據(jù)卷等。
2.2 Docker 架構(gòu)Docker 更詳細(xì)的架構(gòu)如圖所示,采用CS架構(gòu),client 通過 RESTFUL API 發(fā)送 docker 命令到 docker daemon 進(jìn)程,docker daemon 進(jìn)程執(zhí)行鏡像編譯,容器啟停以及分發(fā),數(shù)據(jù)卷管理等,一個 client 可以與多個 docker daemon 通信。
Docker Daemon:Docker 后臺進(jìn)程,用于管理鏡像,容器以及數(shù)據(jù)卷。
Docker Client:用于與 Docker Daemon 交互。
Docker Registry:用于存儲 Docker 鏡像,類似 github,公共的 Registry 有 Docker Hub 和 Docker Cloud。
Images:鏡像是用于創(chuàng)建容器的一種只讀模板。鏡像通常基于一個基礎(chǔ)鏡像,在此基礎(chǔ)上安裝額外的軟件。比如你的 nginx 鏡像可能基于 debian 然后安裝 nginx 并添加配置,你可以從 Docker Hub 上拉取已有的鏡像或者自己通過 Dockerfile 來編譯一個鏡像。
Containers:容器是鏡像的一個可運行示例,我們可通過 Docker client 或者 API 來創(chuàng)建,啟停或者刪除容器。默認(rèn)情況下,容器與宿主機以及其他容器已經(jīng)隔離,當(dāng)然你可以控制隔離容器的網(wǎng)絡(luò)或者存儲的方式。
Services:服務(wù)是 docker swarm 引入的概念,可以用于在多宿主機之間伸縮容器數(shù)目,支持負(fù)載均衡已經(jīng)服務(wù)路由功能。
2.3 Docker底層技術(shù)概覽通過下面命令運行一個 debian 容器,attach 到一個本機的命令行并運行/bin/bash。
docker run -i -t debian /bin/bash
這個命令背后都做了什么?
1.如果本機沒有 debian 鏡像,則會從你配置的 Registry 里面拉取一個 debian 的 latest 版本的鏡像,跟你運行了docker pull debian效果一樣。
2.創(chuàng)建容器。跟運行docker create一樣。
3.給容器分配一個讀寫文件系統(tǒng)作為該容器的 final layer,容器可以在它的文件系統(tǒng)創(chuàng)建和修改文件。
4.Docker 為容器創(chuàng)建了一套網(wǎng)絡(luò)接口,給容器分配一個 ip。默認(rèn)情況下,容器可以通過默認(rèn)網(wǎng)絡(luò)連通到外部網(wǎng)絡(luò)。
5.Docker啟動容器并執(zhí)行 /bin/bash。因為啟動時指定了 -i -t 參數(shù),容器是以交互模式運行且 attach 到本地終端,我們可以在終端上輸入命令并看到輸出。
6.運行 exit 可以退出容器,但是此時容器并沒有被刪除,我們可以再次運行它或者刪除它。
可以發(fā)現(xiàn),容器的內(nèi)核版本是跟宿主機一樣的,不同的是容器的主機名是獨立的,它默認(rèn)用容器 ID 做主機名。我們運行ps -ef可以發(fā)現(xiàn)容器進(jìn)程是隔離的,容器里面看不到宿主機的進(jìn)程,而且它自己有 PID 為1的進(jìn)程。此外,網(wǎng)絡(luò)也是隔離的,它有獨立于宿主機的 IP。文件系統(tǒng)也是隔離的,容器有自己的系統(tǒng)和軟件目錄,修改容器內(nèi)的文件并不影響宿主機對應(yīng)目錄的文件。
root@stretch:/home/vagrant# uname -r 4.9.0-6-amd64 root@stretch:/home/vagrant# docker run -it --name demo alpine /bin/ash / # uname -r ## 容器內(nèi) 4.9.0-6-amd64 / # ps -ef PID USER TIME COMMAND 1 root 0:00 /bin/ash 7 root 0:00 ps -ef / # ip a 1: lo:mtu 65536 qdisc noqueue state UNKNOWN qlen 1 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 6: eth0@if7: mtu 1500 qdisc noqueue state UP 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
這些隔離機制并不是 Docker 新開發(fā)的技術(shù),而是依托 Linux kernel 以及一些已有的技術(shù)實現(xiàn)的,主要包括:
Linux Namespaces(Linux2.6.24后引入):命名空間用于進(jìn)程(PID)、網(wǎng)絡(luò)(NET)、掛載點(MNT)、UTS、IPC 等隔離。
Linux Control Groups(CGroups):用于限制容器使用的資源,包括內(nèi)存,CPU等。
Union File Systems:UnionFS 把多個目錄結(jié)合成一個目錄,對外使用,最上層目錄為讀寫層(通常只有1個),下面可以有一個或多個只讀層,見容器和鏡像分層圖。Docker 支持 OverlayFS,AUFS、DeviceMapper、btrfs 等聯(lián)合文件系統(tǒng)。
Container Format: Docker Engine 組合 Namespaces,CGroup 以及 UnionFS 包裝為一個容器格式,默認(rèn)格式為 libcontainer,后續(xù)可能會加入 BSD Jails 或 Solaris Zones 容器格式的支持。
3 Docker底層技術(shù) 3.1 NamespacesNamespaces 用于環(huán)境隔離,Linux kernel 支持的 Namespace 包括UTS, IPC, PID, NET, NS, USER 以及新加入的 CGROUP 等,UTS 用于隔離主機名和域名,使用標(biāo)識 CLONE_NEWUTS,IPC 用于隔離進(jìn)程間通信資源如消息隊列等,使用標(biāo)識 CLONE_NEWIPC,PID 隔離進(jìn)程,NET 用于隔離網(wǎng)絡(luò),NS 用于隔離掛載點,USER 用于隔離用戶組。默認(rèn)情況下,通過 clone 系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程的 namespace 與父進(jìn)程是一致的,而你可以在 clone 系統(tǒng)調(diào)用中通過flag參數(shù)設(shè)置隔離的名字空間來隔離,當(dāng)然也可以更加方便的直接用 unshare 命令來創(chuàng)建新的 namespace。查看一個進(jìn)程的各 Namespace 命令如下:
root@stretch:/home/vagrant# ls -ls /proc/self/ns/ 0 lrwxrwxrwx 1 root root 0 May 17 22:04 cgroup -> cgroup:[4026531835] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 ipc -> ipc:[4026531839] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 mnt -> mnt:[4026531840] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 net -> net:[4026531957] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 pid -> pid:[4026531836] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 user -> user:[4026531837] 0 lrwxrwxrwx 1 root root 0 May 17 22:04 uts -> uts:[4026531838]PID Namespace
在容器中,有自己的 Pid namespace,因此我們看到的只有 PID 為1的初始進(jìn)程以及它的子進(jìn)程,而宿主機的其他進(jìn)程容器內(nèi)是看不到的。通常來說, Linux 啟動后它會先啟動一個 PID 為1的進(jìn)程,這是系統(tǒng)進(jìn)程樹的根進(jìn)程,根進(jìn)程會接著創(chuàng)建子進(jìn)程來初始化系統(tǒng)服務(wù)。PID namespace 允許在新的namespace 創(chuàng)建一棵新的進(jìn)程樹,它可以有自己的PID為1的進(jìn)程。在 PID namespace 的隔離下,子進(jìn)程名字空間無法知道父進(jìn)程名字空間的進(jìn)程,如在Docker容器中無法看到宿主機的進(jìn)程,而父進(jìn)程名字空間可以看到子進(jìn)程名字空間的所有進(jìn)程。如圖所示:
Linux 內(nèi)核加入 PID Namespace 后,對 pid 結(jié)構(gòu)進(jìn)行了修改,新增的 upid 結(jié)構(gòu)用于跟蹤 namespace 和 pid。
## 加入PID Namespace之前的pid結(jié)構(gòu) struct pid { atomic_t count; /* reference counter */ int nr; /* the pid value */ struct hlist_node pid_chain; /* hash chain */ ... }; ## 加入PID Namespace之后的pid結(jié)構(gòu) struct upid { int nr; /* moved from struct pid */ struct pid_namespace *ns; struct hlist_node pid_chain; /* moved from struct pid */ }; struct pid { ... int level; /* the number of upids */ struct upid numbers[0]; };
可以通過 unshare 測試下 PID namespace,可以看到新的 bash 進(jìn)程它的 pid namespace 與父進(jìn)程的不同了,而且它的 pid 是1。
root@stretch:/home/vagrant# unshare --fork --pid bash root@stretch:/home/vagrant# echo $$ 1 root@stretch:/home/vagrant# ls -ls /proc/self/ns/ 0 lrwxrwxrwx 1 root root 0 May 19 15:24 cgroup -> cgroup:[4026531835] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 ipc -> ipc:[4026531839] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 mnt -> mnt:[4026531840] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 net -> net:[4026531957] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 pid -> pid:[4026532232] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 user -> user:[4026531837] 0 lrwxrwxrwx 1 root root 0 May 19 15:24 uts -> uts:[4026531838]NS Namespace
NS Namespace 用于隔離掛載點,不同 NS Namespace 的掛載點互不影響。創(chuàng)建一個新的 Mount Namespace 效果有點類似 chroot,不過它隔離的比 chroot 更加完全。這是歷史上的第一個 Linux Namespace,由此得到了 NS 這個名字而不是用的 Mount。
在最初的 NS Namespace 版本中,掛載點是完全隔離的。初始狀態(tài)下,子進(jìn)程看到的掛載點與父進(jìn)程是一樣的。在新的 Namespace 中,子進(jìn)程可以隨意 mount/umount 任何目錄,而不會影響到父 Namespace。使用 NS Namespace完全隔離掛載點初衷很好,但是也帶來了某些情況下不方便,比如我們新加了一塊磁盤,如果完全隔離則需要在所有的 Namespace 中都掛載一遍。為此,Linux 在2.6.15版本中加入了一個 shared subtree 特性,通過指定 Propagation 來確定掛載事件如何傳播。比如通過指定 MS_SHARED 來允許在一個 peer group (子 namespace 和父 namespace 就屬于同一個組)共享掛載點,mount/umount 事件會傳播到 peer group 成員中。使用 MS_PRIVATE 不共享掛載點和傳播掛載事件。其他還有 MS_SLAVE 和 NS_UNBINDABLE 等選項。可以通過查看 cat /proc/self/mountinfo 來看掛載點信息,若沒有傳播參數(shù)則為 MS_PRIVATE 的選項。
例如你在初始 namespace 有兩個掛載點,通過 mount --make-shared /dev/sda1 /mntS 設(shè)置 /mntS 為shared類型,mount --make-private /dev/sda1 /mntP 設(shè)置 /mntP 為 private 類型。當(dāng)你使用 unshare -m bash 新建一個 namespace 并在它們下面掛載子目錄時,可以發(fā)現(xiàn) /mntS 下面的子目錄 mount/umount 事件會傳播到父 namespace,而 /mntP 則不會。關(guān)于 mount 各種模式詳解可以參考這篇文章。
在前面例子 Pid namespace 隔離后,我們在新的名字空間執(zhí)行 ps -ef 可以看到宿主機進(jìn)程,這是因為 ps 命令是從 /proc 文件系統(tǒng)讀取的數(shù)據(jù),而文件系統(tǒng)我們還沒有隔離,為此,我們需要在新的 NS Namespace 重新掛載 proc 文件系統(tǒng)來模擬類似 Docker 容器的功能。
root@stretch:/home/vagrant# unshare --pid --fork --mount-proc bash root@stretch:/home/vagrant# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 15:36 pts/1 00:00:00 bash root 2 1 0 15:36 pts/1 00:00:00 ps -ef
可以看到,隔離了 NS namespace 并重新掛載了 proc 后,ps 命令只能看到2個進(jìn)程了,跟我們在 Docker 容器中看到的一致。
NET NamespaceDocker 容器中另一個重要特性是網(wǎng)絡(luò)獨立(之所以不用隔離一詞是因為容器的網(wǎng)絡(luò)還是要借助宿主機的網(wǎng)絡(luò)來通信的),使用到 Linux 的 NET Namespace 以及 vet。veth 主要的目的是為了跨 NET namespace 之間提供一種類似于 Linux 進(jìn)程間通信的技術(shù),所以 veth 總是成對出現(xiàn),如下面的 veth0 和 veth1。它們位于不同的 NET namespace 中,在 veth 設(shè)備任意一端接收到的數(shù)據(jù),都會從另一端發(fā)送出去。veth 實現(xiàn)了不同namespace的網(wǎng)絡(luò)數(shù)據(jù)傳輸。
在 Docker 中,宿主機的 veth 端會橋接到網(wǎng)橋中,接收到容器中的 veth 端發(fā)過來的數(shù)據(jù)后會經(jīng)由網(wǎng)橋 docker0 再轉(zhuǎn)發(fā)到宿主機網(wǎng)卡 eth0,最終通過 eth0 發(fā)送數(shù)據(jù)。當(dāng)然在發(fā)送數(shù)據(jù)前,需要經(jīng)過iptables MASQUERADE 規(guī)則將源地址改成宿主機 ip,這樣才能接收到響應(yīng)數(shù)據(jù)包。而宿主機網(wǎng)卡接收到的數(shù)據(jù)會通過iptables DNAT 根據(jù)端口號修改目的地址和端口為容器的ip 和端口,然后根據(jù)路由規(guī)則發(fā)送到網(wǎng)橋 docker0 中,并最終由網(wǎng)橋 docker0 發(fā)送到對應(yīng)的容器中。
Docker 里面網(wǎng)絡(luò)模式分為 bridge,host,overlay 等幾種模式,默認(rèn)是采用 bridge 模式網(wǎng)絡(luò)如圖所示。如果使用 host 模式,則不隔離直接使用宿主機網(wǎng)絡(luò)。overlay 網(wǎng)絡(luò)則是更加高級的模式,可以實現(xiàn)跨主機的容器通信,后面會多帶帶總結(jié)下 Docker 網(wǎng)絡(luò)這個專題。
USER Namespaceuser namespace 用于隔離用戶和組信息,在不同的 namespace 中用戶可以有相同的 UID 和 GID,它們之間互相不影響。父子 namespace 之間可以進(jìn)行用戶映射,如父 namespace (宿主機)的普通用戶映射到子 namespace (容器)的 root 用戶,以減少子 namespace 的 root 用戶操作父 namespace 的風(fēng)險。user namespace 功能雖然在很早就出現(xiàn)了,但是直到 Linux kernel 3.8之后這個功能才趨于完善。
創(chuàng)建新的 user namespace 之后第一步就是設(shè)置好 user 和 group 的映射關(guān)系。這個映射通過設(shè)置 /proc/PID/uid_map(gid_map) 實現(xiàn),格式如下,ID-inside-ns 是容器內(nèi)的 uid/gid,而 ID-outside-ns 則是容器外映射的真實 uid/gid。比如0 1000 1表示將真實的 uid =1000映射為容器內(nèi)的 uid=0,length 為映射的范圍。
ID-inside-ns ID-outside-ns length
不是所有的進(jìn)程都能隨便修改映射文件的,必須同時具備如下條件:
修改映射文件的進(jìn)程必須有 PID 進(jìn)程所在 user namespace 的 CAP_SETUID/CAP_SETGID 權(quán)限。
修改映射文件的進(jìn)程必須是跟 PID 在同一個 user namespace 或者 PID 的父 namespace。
映射文件 uid_map 和 gid_map 只能寫入一次,再次寫入會報錯。
docker 1.10之后的版本可以通過在 docker daemon 啟動時加上 --userns-remap=[USERNAME] 來實現(xiàn) USER Namespace 的隔離。我們指定了 username = ssj 啟動 dockerd,查看 subuid 文件可以發(fā)現(xiàn) ssj 映射的 uid 范圍是165536到165536+65536= 231072,而且在docker目錄下面對應(yīng) ssj 有一個獨立的目錄165536.165536存在。
root@stretch:/home/vagrant# cat /etc/subuid vagrant:100000:65536 ssj:165536:65536 root@stretch:/home/vagrant# ls /var/lib/docker/165536.165536/ builder/ containerd/ containers/ image/ network/ ...
運行 docker images -a 等命令可以發(fā)現(xiàn)在啟用 user namespace 之前的鏡像都看不到了。此時只能看到在新的 user namespace 里面創(chuàng)建的 docker 鏡像和容器。而此時我們創(chuàng)建一個測試容器,可以在容器外看到容器進(jìn)程的 uid_map 已經(jīng)設(shè)置為 ssj,這樣容器中的 root 用戶映射到宿主機就是 ssj 這個用戶了,此時如果要刪除我們掛載的 /bin 目錄中的文件,會提示沒有權(quán)限,增強了安全性。
### dockerd 啟動時加了 --userns-remap=ssj root@stretch:/home/vagrant# docker run -it -v /bin:/host/bin --name demo alpine /bin/ash / # rm /host/bin/which rm: remove "/host/bin/which"? y rm: can"t remove "/host/bin/which": Permission denied ### 宿主機查看容器進(jìn)程uid_map文件 root@stretch:/home/vagrant# CPID=`ps -ef|grep "/bin/ash"|awk "{printf $2}"` root@stretch:/home/vagrant# cat /proc/$CPID/uid_map 0 165536 65536其他Namespace
UTS namespace 用于隔離主機名等。可以看到在新的 uts namespace 修改主機名并不影響原 namespace 的主機名。
root@stretch:/home/vagrant# unshare --uts --fork bash root@stretch:/home/vagrant# hostname stretch root@stretch:/home/vagrant# hostname modified root@stretch:/home/vagrant# hostname modified root@stretch:/home/vagrant# exit root@stretch:/home/vagrant# hostname stretch
IPC Namespace 用于隔離 IPC 消息隊列等。可以看到,新老 ipc namespace 的消息隊列互不影響。
root@stretch:/home/vagrant# ipcmk -Q Message queue id: 0 root@stretch:/home/vagrant# ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages 0x26c3371c 0 root 644 0 0 root@stretch:/home/vagrant# unshare --ipc --fork bash root@stretch:/home/vagrant# ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages
CGROUP Namespace 是 Linux 4.6以后才支持的新 namespace。容器技術(shù)使用 namespace 和 cgroup 實現(xiàn)環(huán)境隔離和資源限制,但是對于 cgroup 本身并沒有隔離。沒有 cgroup namespace 前,容器中一旦掛載 cgroup 文件系統(tǒng),便可以修改整全局的 cgroup 配置。有了 cgroup namespace 后,每個 namespace 中的進(jìn)程都有自己的 cgroup 文件系統(tǒng)視圖,增強了安全性,同時也讓容器遷移更加方便。在我測試的 Docker 18.03.1-ce 版本中容器暫時沒有用到 cgroup namespace,這里就不再展開。
3.2 CGroupsLinux CGroups 用于資源限制,包括限制 CPU、內(nèi)存、blkio 以及網(wǎng)絡(luò)等。通過工具 cgroup-bin (sudo apt-get install cgroup-bin) 可以創(chuàng)建 CGroup 并進(jìn)入該 CGroup 執(zhí)行命令。
root@stretch:/home/vagrant# cgcreate -a vagrant -g cpu:cg1 root@stretch:/home/vagrant# ls /sys/fs/cgroup/cpu/cg1/ cgroup.clone_children cpu.cfs_period_us cpu.shares cpuacct.stat cpuacct.usage_all cpuacct.usage_percpu_sys cpuacct.usage_sys notify_on_release cgroup.procs cpu.cfs_quota_us cpu.stat cpuacct.usage cpuacct.usage_percpu cpuacct.usage_percpu_user cpuacct.usage_user tasks
cpu.cfs_period_us 和 cpu.cfs_quota_us,它們分別用來限制該組中的所有進(jìn)程在單位時間里可以使用的 cpu 時間,這里的 cfs(Completely Fair Scheduler) 是完全公平調(diào)度器的意思。cpu.cfs_period_us 是時間周期,默認(rèn)為100000,即100毫秒。而 cpu.cfs_quota_us 是在時間周期內(nèi)可以使用的時間,默認(rèn)為-1即無限制。cpu.shares 用于限制cpu使用的,它用于控制各個組之間的配額。比如組 cg1的 cpu.shares 為1024,組 cg2的cpu.shares 也是1024,如果都有進(jìn)程在運行則它們都可以使用最多50%的限額。如果 cg2 組內(nèi)進(jìn)程比較空閑,那么 cg1 組可以將使用幾乎整個 cpu,tasks 存儲的是該組里面的進(jìn)程 ID。( 注: debian8 默認(rèn)沒有 cfs 和 memory cgroup 支持,需要重新編譯內(nèi)核及修改啟動參數(shù),debian9 默認(rèn)已經(jīng)支持)
我們先在默認(rèn)的分組里面運行一個死循環(huán)程序 loop.py,因為默認(rèn)分組 /sys/fs/cgroup/cpu/cpu.cfs_period_us 和 cfs_quota_us 是默認(rèn)值,所以是沒有限制 cpu 使用的。可以發(fā)現(xiàn)1個 cpu us 立馬接近100%了。
# loop.py while True: pass
設(shè)置 cg1 組 的cfs_quota_us 位50000,即表示該組內(nèi)進(jìn)程最多使用50%的 cpu 時間,運行 cgexec 命令進(jìn)入 cg1 的 cpu 組,然后運行 loop.py,可以發(fā)現(xiàn) cpu us 在50%以內(nèi)了,此時也可以在 tasks 文件中看到我們剛剛 cgexec 創(chuàng)建的進(jìn)程 ID。
root@stretch:/home/vagrant# echo 50000 > /sys/fs/cgroup/cpu/cg1/cpu.cfs_quota_us root@stretch:/home/vagrant# cgexec -g cpu:cg1 /bin/bash
Docker 里面要限制內(nèi)存和 CPU 使用,可以在啟動時指定相關(guān)參數(shù)即可。比如限制 cpu 使用率,加 cpu-period 和 cpu-quota 參數(shù),限制執(zhí)行的 cpu 核,加 --cpuset-cpus 參數(shù)。限制內(nèi)存使用,加--memory參數(shù)。當(dāng)然,我們可以看到在 /sys/fs/cgroup/cpu/docker/ 目錄下有個以 containerid 為名的分組,該分組下面的 cpu.cfs_period_us 和 cpu.cfs_quota_us 的值就是我們在啟動容器時指定的值。
root@stretch:/home/vagrant# docker run -i -t --cpu-period=100000 --cpu-quota=50000 --memory=512000000 alpine /bin/ash3.3 Capabilities
我們在啟動容器時會時常看到這樣的參數(shù) --cap-add=NET_ADMIN,這是用到了 Linux 的 capability 特性。 capability 是為了實現(xiàn)更精細(xì)化的權(quán)限控制而加入的。我們以前熟知通過設(shè)置文件的 SUID 位,這樣非 root 用戶的可執(zhí)行文件運行后的 euid 會成為文件的擁有者 ID,比如 passwd 命令運行起來后有 root 權(quán)限,有 SUID 權(quán)限的可執(zhí)行文件如果存在漏洞會有安全風(fēng)險。(查看文件的 capability 的命令為 filecap -a,而查看進(jìn)程 capability 的命令為 pscap -a,pscap 和 filecap工具需要安裝 libcap-ng-utils這個包)。
對于 capability,可以看一個簡單的例子便于理解。如 Debian 系統(tǒng)中自帶的 ping 工具,它是有設(shè)置 SUID 位的。這里拷貝 ping 重命名為 anotherping,anotherping 的 SUID 位沒有設(shè)置,運行會提示權(quán)限錯誤。這里,我們只要將其加上 cap_net_raw 權(quán)限即可,不需要設(shè)置 SUID 位那么大的權(quán)限。
vagrant@stretch:~$ ls -ls /bin/ping 60 -rwsr-xr-x 1 root root 61240 Nov 10 2016 /bin/ping vagrant@stretch:~$ cp /bin/ping anotherping vagrant@stretch:~$ ls -ls anotherping 60 -rwxr-xr-x 1 vagrant vagrant 61240 May 19 10:18 anotherping vagrant@stretch:~$ ./anotherping -c1 yue.uu.163.com ping: socket: Operation not permitted vagrant@stretch:~$ sudo setcap cap_net_raw+ep ./anotherping vagrant@stretch:~$ ./anotherping -c1 yue.uu.163.com PING yue.uu.163.com (59.111.137.252) 56(84) bytes of data. 64 bytes from 59.111.137.252 (59.111.137.252): icmp_seq=1 ttl=63 time=53.9 ms --- yue.uu.163.com ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 53.919/53.919/53.919/0.000 ms3.4 Union File System
UnionFS (聯(lián)合文件系統(tǒng))簡單來說就是支持將不同的目錄掛載到同一個目錄中的技術(shù)。Docker 支持的 UnionFS 包 括OverlayFS,AUFS,devicemapper,vfs 以及 btrfs 等,查看 UnionFS 版本可以用 docker info 查看對應(yīng)輸出中的 Storage 項即可,早期的 Docker 版本用 AUFS 和 devicemapper 居多,新版本 Docker 在 Linux 3.18之后版本基本默認(rèn)用 OverlayFS,這里以 OverlayFS 來分析。
OverlayFS 與早期用過的 AUFS 類似,不過它比 AUFS 更簡單,讀寫性能更好,在 docker-ce18.03 版本中默認(rèn)用的存儲驅(qū)動是 overlay2,老版本 overlay 官方已經(jīng)不推薦使用。它將兩個目錄 upperdir 和 lowdir 聯(lián)合掛載到一個 merged 目錄,提供統(tǒng)一視圖。其中 upperdir 是可讀寫層,對容器修改寫入在該目錄中,它也會隱藏 lowerdir 中相同的文件。而 lowdir 是只讀層, Docker 鏡像在這層。
在看 Docker 鏡像和容器存儲結(jié)構(gòu)前,可以先簡單操作下 OverlayFS 看下基本概念。創(chuàng)建了 lowerdir 和 upperdir 兩個目錄,然后用 overlayfs 掛載到 merged 目錄,這樣在 merged 目錄可以看到兩個目錄的所有文件 both.txt 和 only.txt。其中 upperdir 是可讀寫的,而 lowerdir 只讀。通過 merged 目錄來操作文件可以發(fā)現(xiàn):
讀取文件時,如果 upperdir 不存在該文件,則會從 lowerdir 直接讀取。
修改文件時并不影響 lowerdir 中的文件,因為它是只讀的。
如果修改的文件在 upperdir 不存在,則會從 lowerdir 拷貝到 upperdir,然后在 upperdir 里面修改該文件,并不影響 lowerdir 目錄的文件。
刪除文件則是將 upperdir 中將對應(yīng)文件設(shè)置成了 c 類型,即字符設(shè)備類型來隱藏已經(jīng)刪除的文件(與 AUFS 創(chuàng)建一個 whiteout 文件略有不同)。
root@stretch:/home/vagrant/overlaytest# tree -a . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged |-- upperdir | `-- both.txt `-- workdir `-- work 5 directories, 3 files root@stretch:/home/vagrant/overlaytest# mount -t overlay overlay -olowerdir=./lowerdir,upperdir=./upperdir,workdir=./workdir ./merged root@stretch:/home/vagrant/overlaytest# tree . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged | |-- both.txt | `-- only.txt |-- upperdir | `-- both.txt `-- workdir `-- work 5 directories, 5 files root@stretch:/home/vagrant/overlaytest# tree -a . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged | |-- both.txt | `-- only.txt |-- upperdir | `-- both.txt `-- workdir `-- work 5 directories, 5 files root@stretch:/home/vagrant/overlaytest# echo "modified both" > merged/both.txt root@stretch:/home/vagrant/overlaytest# cat upperdir/both.txt modified both root@stretch:/home/vagrant/overlaytest# cat lowerdir/both.txt lower both.txt root@stretch:/home/vagrant/overlaytest# echo "modified only" > merged/only.txt root@stretch:/home/vagrant/overlaytest# tree . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged | |-- both.txt | `-- only.txt |-- upperdir | |-- both.txt | `-- only.txt `-- workdir `-- work 5 directories, 6 files root@stretch:/home/vagrant/overlaytest# cat upperdir/only.txt modified only root@stretch:/home/vagrant/overlaytest# cat lowerdir/only.txt lower only.txt root@stretch:/home/vagrant/overlaytest# tree -a . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged | |-- both.txt | `-- only.txt |-- upperdir | |-- both.txt | `-- only.txt `-- workdir `-- work 5 directories, 6 files root@stretch:/home/vagrant/overlaytest# rm merged/both.txt root@stretch:/home/vagrant/overlaytest# tree -a . |-- lowerdir | |-- both.txt | `-- only.txt |-- merged | `-- only.txt |-- upperdir | |-- both.txt | `-- only.txt `-- workdir `-- work root@stretch:/home/vagrant/overlaytest# ls -ls upperdir/both.txt 0 c--------- 1 root root 0, 0 May 19 02:31 upperdir/both.txt
回到 Docker 里面,我們拉取一個 nginx 鏡像,有三層鏡像,可以看到在 overlay2 對應(yīng)每一層都有個目錄(注意,這個目錄名跟鏡像層名從 docker1.10 版本后名字已經(jīng)不對應(yīng)了),另外的 l 目錄是指向鏡像層的軟鏈接。最底層存儲的是基礎(chǔ)鏡像 debian/alpine,上一層是安裝了 nginx 增加的可執(zhí)行文件和配置文件,而最上層是鏈接 /dev/stdout 到 nginx 日志文件。而每個子目錄下面的 diff 目錄用于存儲鏡像內(nèi)容,work 目錄是 OverlayFS 內(nèi)部使用的,而 link 文件存儲的是該鏡像層對應(yīng)的短名稱,lower 文件存儲的是下一層的短名稱。
root@stretch:/home/vagrant# docker pull nginx Using default tag: latest latest: Pulling from library/nginx f2aa67a397c4: Pull complete 3c091c23e29d: Pull complete 4a99993b8636: Pull complete Digest: sha256:0fb320e2a1b1620b4905facb3447e3d84ad36da0b2c8aa8fe3a5a81d1187b884 Status: Downloaded newer image for nginx:latest root@stretch:/home/vagrant# ls -ls /var/lib/docker/overlay2/ total 16 4 drwx------ 4 root root 4096 May 19 04:17 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0 4 drwx------ 3 root root 4096 May 19 04:17 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554 4 drwx------ 4 root root 4096 May 19 04:17 f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe 4 drwx------ 2 root root 4096 May 19 04:17 l root@stretch:/var/lib/docker/overlay2# ls 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0/ diff link lower work
從我們示例可以看到,三層中 f311是最頂層,下面分別是0949和8af9這兩層。
root@stretch:/var/lib/docker/overlay2# cat f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe/lower l/7B2WM6DC226TCJU6QHJ4ABKRI6:l/4FHO2G5SWWRIX44IFDHU62Z7X2 root@stretch:/var/lib/docker/overlay2# cat 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0/lower l/4FHO2G5SWWRIX44IFDHU62Z7X2 root@stretch:/var/lib/docker/overlay2# cat 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554/link 4FHO2G5SWWRIX44IFDHU62Z7X2
此時我們啟動一個 nginx 容器,可以看到 overlay2 目錄多了兩個目錄,多出來的就是容器層的目錄和只讀的容器 init 層。容器目錄下面的 merged 就是我們前面提到的聯(lián)合掛載目錄了,而 lowdir 則是它下層目錄。而容器 init 層用來存儲與這個容器內(nèi)環(huán)境相關(guān)的內(nèi)容,如 /etc/hosts和/etc/resolv.conf 文件,它居于其他鏡像層之上,容器層之下。
root@stretch:/var/lib/docker/overlay2# docker run -idt --name nginx nginx 01a873eeba41f00a5a3deb083adf5ed892c55b4680fbc2f1880e282195d3087b root@stretch:/var/lib/docker/overlay2# ls -ls 4 drwx------ 4 root root 4096 May 19 04:17 09495e5085bced25e8017f558147f82e61b012a8f632a0b6aac363462b1db8b0 4 drwx------ 5 root root 4096 May 19 09:11 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1 4 drwx------ 4 root root 4096 May 19 09:11 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1-init 4 drwx------ 3 root root 4096 May 19 04:17 8af95287a343b26e9c3dd679258773880e7bdbbe914198ba63a8ed1b4c5f5554 4 drwx------ 4 root root 4096 May 19 04:17 f311565fe9436eb8606f846e1f73f38287841773e8d041933a41259fe6f96afe 4 drwx------ 2 root root 4096 May 19 09:11 l root@stretch:/home/vagrant# ls -ls /var/lib/docker/overlay2/11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/ 4 drwxr-xr-x 4 root root 4096 May 19 09:11 diff 4 -rw-r--r-- 1 root root 26 May 19 09:11 link 4 -rw-r--r-- 1 root root 115 May 19 09:11 lower 4 drwxr-xr-x 1 root root 4096 May 19 09:11 merged 4 drwx------ 3 root root 4096 May 19 09:11 work root@stretch:/var/lib/docker/overlay2# ls 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/merged/ bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@stretch:/var/lib/docker/overlay2# ls 11b7579a1f1775ad71fe0f0f45fcb74c241fce319f5125b1b92cb442385065b1/diff/ run var
如果我們在容器中修改文件,則會反映到容器層的 merged 目錄相關(guān)文件,容器層的 diff 目錄相當(dāng)于 upperdir,其他層是 lowerdir。如果之前容器層 diff 目錄不存在該文件,則會拷貝該文件到 diff 目錄并修改。讀取文件時,如果 upperdir 目錄找不到,則會直接從下層的鏡像層中讀取。
4 總結(jié)隨著版本不斷更新,Docker 的一些技術(shù)細(xì)節(jié)也在變化,如鏡像層存儲目錄的變化,默認(rèn) UnionFileSystem 換成 OverlayFS,新的 Namespace 的支持等。這篇文章主要對以前的學(xué)習(xí)筆記和 Docker 的一些新的變化做了些許總結(jié),如想了解更詳細(xì)內(nèi)容,可以查看參考資料和 Docker 官方相關(guān)文檔。
作者:__七把刀__
鏈接:https://www.jianshu.com/p/7a1...
來源:簡書
更多相關(guān)閱讀:
Docker 容器操作
Docker 的那點事兒
Docker 基礎(chǔ)技術(shù)-Linux Namespace
docker-compose.yml 配置詳解
如果你還想了解更多,想和技術(shù)同僚分享切磋,可掃下方二維碼加好友,回復(fù)yw,加入掘金運維技術(shù)交流群
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/27364.html
摘要:底層技術(shù)用于環(huán)境隔離,支持的包括以及新加入的等,用于隔離主機名和域名,使用標(biāo)識,用于隔離進(jìn)程間通信資源如消息隊列等,使用標(biāo)識,隔離進(jìn)程,用于隔離網(wǎng)絡(luò),用于隔離掛載點,用于隔離用戶組。 本文已獲得原作者_(dá)_七把刀__授權(quán)。 Docker 容器技術(shù)已經(jīng)發(fā)展了好些年,在很多項目都有應(yīng)用,線上運行也很穩(wěn)定。整理了部分 Docker 的學(xué)習(xí)筆記以及新版本特性,對Docker感興趣的同學(xué)可以看看,...
摘要:本文已獲得原作者授權(quán)。在構(gòu)建鏡像的過程中會緩存一系列中間鏡像。鏡像時,會順序執(zhí)行中的指令,并同時比較當(dāng)前指令和其基礎(chǔ)鏡像的所有子鏡像,若發(fā)現(xiàn)有一個子鏡像也是由相同的指令生成,則命中緩存,同時可以直接使用該子鏡像而避免再去重新生成了。 本文已獲得原作者 CodeSheep 授權(quán)。 概述 Dockerfile 是專門用來進(jìn)行自動化構(gòu)建鏡像的編排文件(就像 Jenkins 2.0時代的 J...
摘要:第二具備輕量化特性容器的體積非常小巧。他們大多認(rèn)為自己應(yīng)該將應(yīng)用程序部署至當(dāng)前正在運行的容器當(dāng)中。不要創(chuàng)建大型鏡像體積過大的鏡像會加大其發(fā)布難度。總體來講,在向生產(chǎn)環(huán)境中部署容器時,必須避免使用最新標(biāo)簽。 當(dāng)下最火爆的Docker,是一個開源的應(yīng)用容器引擎。大家已經(jīng)開始認(rèn)同并接受容器技術(shù),并意識到它能夠解決多種現(xiàn)實問題并具備一系列無可比擬的優(yōu)勢。今天小數(shù)就和大家聊一聊容器技術(shù)的優(yōu)勢和誤...
摘要:如果我們的容器使用,文件如下在這個例子中,我們可以重復(fù)創(chuàng)建和銷毀,同一個持久存儲會被提供給新的,無論容器位于哪個節(jié)點上。 前言 臨時性存儲是容器的一個很大的買點。根據(jù)一個鏡像啟動容器,隨意變更,然后停止變更重啟一個容器。你看,一個全新的文件系統(tǒng)又誕生了。 在docker的語境下: # docker run -it centos [root@d42876f95c6a /]# echo H...
摘要:如果我們的容器使用,文件如下在這個例子中,我們可以重復(fù)創(chuàng)建和銷毀,同一個持久存儲會被提供給新的,無論容器位于哪個節(jié)點上。 前言 臨時性存儲是容器的一個很大的買點。根據(jù)一個鏡像啟動容器,隨意變更,然后停止變更重啟一個容器。你看,一個全新的文件系統(tǒng)又誕生了。 在docker的語境下: # docker run -it centos [root@d42876f95c6a /]# echo H...
閱讀 2416·2021-11-18 10:02
閱讀 1929·2021-10-13 09:40
閱讀 3008·2021-09-07 10:07
閱讀 2117·2021-09-04 16:48
閱讀 1015·2019-08-30 13:18
閱讀 2462·2019-08-29 14:03
閱讀 2929·2019-08-29 12:54
閱讀 3167·2019-08-26 11:41