本文已得到原做者__七把刀__受權。node
Docker 容器技術已經發展了好些年,在不少項目都有應用,線上運行也很穩定。整理了部分 Docker 的學習筆記以及新版本特性,對Docker感興趣的同窗能夠看看,以前整理過的 Linux namespace 能夠見以前的博文。
Container (容器)是一種輕量級的虛擬化技術,它不須要模擬硬件建立虛擬機。在 Linux 系統裏面,使用到 Linux kernel 的 cgroups,namespace(ipc,network, user,pid,mount),capability 等用於隔離運行環境和資源限制的技術,咱們稱之爲容器。容器技術早就出現。例如 Solaris Zones 和 BSD jails 就是非 Linux 操做系統上的容器,而用於 Linux 的容器技術也有不少如 Linux-Vserver、OpenVZ 和 FreeVPS。雖然這些技術都已經成熟,可是這些解決方案尚未將它們的容器支持集成到主流 Linux 內核。總的來講,容器不等同於 Docker,容器更不是虛擬機。linux
LXC 項目由一個 Linux 內核補丁和一些 userspace 工具組成,它提供一套簡化的工具來維護容器,用於虛擬環境的環境隔離、資源限制以及權限控制。LXC 有點相似 chroot,可是它比 chroot 提供了更多的隔離性。nginx
Docker 最初目標是作一個特殊的 LXC 的開源系統,最後慢慢演變爲它本身的一套容器運行時環境。Docker 基於 Linux kernel 的 CGroups,Namespace,UnionFileSystem 等技術封裝成一種自定義的容器格式,用於提供一整套虛擬運行環境。毫無疑問,近些年來 Docker 已經成爲了容器技術的代名詞,如其官網介紹的Docker is world's leading software containerization platform
。本文會先簡單介紹 Docker 基礎概念,而後會分析下 Docker 背後用到的技術。Debian 上安裝 Docker 方法參見docker-ce-installation-in-debian。
git
Docker 提供了一個打包和運行應用的隔離環境,稱之爲容器,Docker 的隔離和安全特性容許你在一個主機同時運行多個容器,並且它並不像虛擬機那樣重量級,容器都是基於宿主機的內核運行的,它是輕量的,無論你運行的是ubuntu, debian 仍是其餘 Linux 系統,用的內核都是宿主機內核。Docker 提供了工具和平臺來管理容器,而 Docker Engine 則是一個提供了大部分功能組件的CS架構的應用,如架構圖所示,Docker Engine 負責管理鏡像,容器,網絡以及數據卷等。github
Docker 更詳細的架構如圖所示,採用CS架構,client 經過 RESTFUL API 發送 docker 命令到 docker daemon 進程,docker daemon 進程執行鏡像編譯,容器啓停以及分發,數據卷管理等,一個 client 能夠與多個 docker daemon 通訊。
docker
經過下面命令運行一個 debian 容器,attach 到一個本機的命令行並運行/bin/bash。ubuntu
docker run -i -t debian /bin/bash
這個命令背後都作了什麼?安全
docker pull debian
效果同樣。docker create
同樣。-i -t
參數,容器是以交互模式運行且 attach 到本地終端,咱們能夠在終端上輸入命令並看到輸出。能夠發現,容器的內核版本是跟宿主機同樣的,不一樣的是容器的主機名是獨立的,它默認用容器 ID 作主機名。咱們運行ps -ef
能夠發現容器進程是隔離的,容器裏面看不到宿主機的進程,並且它本身有 PID 爲1的進程。此外,網絡也是隔離的,它有獨立於宿主機的 IP。文件系統也是隔離的,容器有本身的系統和軟件目錄,修改容器內的文件並不影響宿主機對應目錄的文件。bash
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 ## 容器內 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: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> 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 新開發的技術,而是依託 Linux kernel 以及一些已有的技術實現的,主要包括:微信
Namespaces 用於環境隔離,Linux kernel 支持的 Namespace 包括UTS, IPC, PID, NET, NS, USER 以及新加入的 CGROUP 等,UTS 用於隔離主機名和域名,使用標識 CLONE_NEWUTS,IPC 用於隔離進程間通訊資源如消息隊列等,使用標識 CLONE_NEWIPC,PID 隔離進程,NET 用於隔離網絡,NS 用於隔離掛載點,USER 用於隔離用戶組。默認狀況下,經過 clone
系統調用建立子進程的 namespace 與父進程是一致的,而你能夠在 clone 系統調用中經過flag參數設置隔離的名字空間來隔離,固然也能夠更加方便的直接用 unshare
命令來建立新的 namespace。查看一個進程的各 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 爲1的初始進程以及它的子進程,而宿主機的其餘進程容器內是看不到的。一般來講, Linux 啓動後它會先啓動一個 PID 爲1的進程,這是系統進程樹的根進程,根進程會接着建立子進程來初始化系統服務。PID namespace 容許在新的namespace 建立一棵新的進程樹,它能夠有本身的PID爲1的進程。在 PID namespace 的隔離下,子進程名字空間沒法知道父進程名字空間的進程,如在Docker容器中沒法看到宿主機的進程,而父進程名字空間能夠看到子進程名字空間的全部進程。如圖所示:
Linux 內核加入 PID Namespace 後,對 pid 結構進行了修改,新增的 upid 結構用於跟蹤 namespace 和 pid。
## 加入PID Namespace以前的pid結構 struct pid { atomic_t count; /* reference counter */ int nr; /* the pid value */ struct hlist_node pid_chain; /* hash chain */ ... }; ## 加入PID Namespace以後的pid結構 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 進程它的 pid namespace 與父進程的不一樣了,並且它的 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 的掛載點互不影響。建立一個新的 Mount Namespace 效果有點相似 chroot,不過它隔離的比 chroot 更加徹底。這是歷史上的第一個 Linux Namespace,由此獲得了 NS
這個名字而不是用的 Mount
。
在最初的 NS Namespace 版本中,掛載點是徹底隔離的。初始狀態下,子進程看到的掛載點與父進程是同樣的。在新的 Namespace 中,子進程能夠隨意 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
來看掛載點信息,若沒有傳播參數則爲 MS_PRIVATE
的選項。
例如你在初始 namespace 有兩個掛載點,經過 mount --make-shared /dev/sda1 /mntS
設置 /mntS
爲shared類型,mount --make-private /dev/sda1 /mntP
設置 /mntP
爲 private 類型。當你使用 unshare -m bash
新建一個 namespace 並在它們下面掛載子目錄時,能夠發現 /mntS 下面的子目錄 mount/umount 事件會傳播到父 namespace,而 /mntP 則不會。關於 mount 各類模式詳解能夠參考這篇文章。
在前面例子 Pid namespace 隔離後,咱們在新的名字空間執行 ps -ef
能夠看到宿主機進程,這是由於 ps 命令是從 /proc 文件系統讀取的數據,而文件系統咱們尚未隔離,爲此,咱們須要在新的 NS Namespace 從新掛載 proc 文件系統來模擬相似 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個進程了,跟咱們在 Docker 容器中看到的一致。
Docker 容器中另外一個重要特性是網絡獨立(之因此不用隔離一詞是由於容器的網絡仍是要藉助宿主機的網絡來通訊的),使用到 Linux 的 NET Namespace 以及 vet。veth 主要的目的是爲了跨 NET namespace 之間提供一種相似於 Linux 進程間通訊的技術,因此 veth 老是成對出現,以下面的 veth0 和 veth1。它們位於不一樣的 NET namespace 中,在 veth 設備任意一端接收到的數據,都會從另外一端發送出去。veth 實現了不一樣namespace的網絡數據傳輸。
在 Docker 中,宿主機的 veth 端會橋接到網橋中,接收到容器中的 veth 端發過來的數據後會經由網橋 docker0 再轉發到宿主機網卡 eth0,最終經過 eth0 發送數據。固然在發送數據前,須要通過iptables MASQUERADE
規則將源地址改爲宿主機 ip,這樣才能接收到響應數據包。而宿主機網卡接收到的數據會經過iptables DNAT
根據端口號修改目的地址和端口爲容器的ip 和端口,而後根據路由規則發送到網橋 docker0 中,並最終由網橋 docker0 發送到對應的容器中。
Docker 裏面網絡模式分爲 bridge,host,overlay 等幾種模式,默認是採用 bridge 模式網絡如圖所示。若是使用 host 模式,則不隔離直接使用宿主機網絡。overlay 網絡則是更加高級的模式,能夠實現跨主機的容器通訊,後面會單獨總結下 Docker 網絡這個專題。
user namespace 用於隔離用戶和組信息,在不一樣的 namespace 中用戶能夠有相同的 UID 和 GID,它們之間互相不影響。父子 namespace 之間能夠進行用戶映射,如父 namespace (宿主機)的普通用戶映射到子 namespace (容器)的 root 用戶,以減小子 namespace 的 root 用戶操做父 namespace 的風險。user namespace 功能雖然在很早就出現了,可是直到 Linux kernel 3.8以後這個功能才趨於完善。
建立新的 user namespace 以後第一步就是設置好 user 和 group 的映射關係。這個映射經過設置 /proc/PID/uid_map(gid_map) 實現,格式以下,ID-inside-ns 是容器內的 uid/gid,而 ID-outside-ns 則是容器外映射的真實 uid/gid。好比0 1000 1
表示將真實的 uid =1000映射爲容器內的 uid=0,length 爲映射的範圍。
ID-inside-ns ID-outside-ns length
不是全部的進程都能隨便修改映射文件的,必須同時具有以下條件:
CAP_SETUID/CAP_SETGID
權限。docker daemon
啓動時加上 --userns-remap=[USERNAME]
來實現 USER Namespace 的隔離。咱們指定了 username = ssj 啓動 dockerd,查看 subuid 文件能夠發現 ssj 映射的 uid 範圍是165536到165536+65536= 231072,並且在docker目錄下面對應 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
等命令能夠發如今啓用 user namespace 以前的鏡像都看不到了。此時只能看到在新的 user namespace 裏面建立的 docker 鏡像和容器。而此時咱們建立一個測試容器,能夠在容器外看到容器進程的 uid_map 已經設置爲 ssj,這樣容器中的 root 用戶映射到宿主機就是 ssj 這個用戶了,此時若是要刪除咱們掛載的 /bin 目錄中的文件,會提示沒有權限,加強了安全性。
### 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 ### 宿主機查看容器進程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
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。容器技術使用 namespace 和 cgroup 實現環境隔離和資源限制,可是對於 cgroup 自己並無隔離。沒有 cgroup namespace 前,容器中一旦掛載 cgroup 文件系統,即可以修改整全局的 cgroup 配置。有了 cgroup namespace 後,每一個 namespace 中的進程都有本身的 cgroup 文件系統視圖,加強了安全性,同時也讓容器遷移更加方便。在我測試的 Docker 18.03.1-ce 版本中容器暫時沒有用到 cgroup namespace,這裏就再也不展開。
Linux CGroups 用於資源限制,包括限制 CPU、內存、blkio 以及網絡等。經過工具 cgroup-bin (sudo apt-get install cgroup-bin
) 能夠建立 CGroup 並進入該 CGroup 執行命令。
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,它們分別用來限制該組中的全部進程在單位時間裏可使用的 cpu 時間,這裏的 cfs(Completely Fair Scheduler) 是徹底公平調度器的意思。cpu.cfs_period_us 是時間週期,默認爲100000,即100毫秒。而 cpu.cfs_quota_us 是在時間週期內可使用的時間,默認爲-1即無限制。cpu.shares 用於限制cpu使用的,它用於控制各個組之間的配額。好比組 cg1的 cpu.shares 爲1024,組 cg2的cpu.shares 也是1024,若是都有進程在運行則它們均可以使用最多50%的限額。若是 cg2 組內進程比較空閒,那麼 cg1 組能夠將使用幾乎整個 cpu,tasks 存儲的是該組裏面的進程 ID。( 注: debian8 默認沒有 cfs 和 memory cgroup 支持,須要從新編譯內核及修改啓動參數,debian9 默認已經支持)
咱們先在默認的分組裏面運行一個死循環程序 loop.py
,由於默認分組 /sys/fs/cgroup/cpu/cpu.cfs_period_us
和 cfs_quota_us
是默認值,因此是沒有限制 cpu 使用的。能夠發現1個 cpu us 立馬接近100%了。
# loop.py while True: pass
設置 cg1 組 的cfs_quota_us 位50000,即表示該組內進程最多使用50%的 cpu 時間,運行 cgexec 命令進入 cg1 的 cpu 組,而後運行 loop.py,能夠發現 cpu us 在50%之內了,此時也能夠在 tasks 文件中看到咱們剛剛 cgexec 建立的進程 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 裏面要限制內存和 CPU 使用,能夠在啓動時指定相關參數便可。好比限制 cpu 使用率,加 cpu-period 和 cpu-quota 參數,限制執行的 cpu 核,加 --cpuset-cpus 參數。限制內存使用,加--memory參數。固然,咱們能夠看到在 /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/ash
咱們在啓動容器時會時常看到這樣的參數 --cap-add=NET_ADMIN
,這是用到了 Linux 的 capability 特性。 capability 是爲了實現更精細化的權限控制而加入的。咱們之前熟知經過設置文件的 SUID 位,這樣非 root 用戶的可執行文件運行後的 euid 會成爲文件的擁有者 ID,好比 passwd 命令運行起來後有 root 權限,有 SUID 權限的可執行文件若是存在漏洞會有安全風險。(查看文件的 capability 的命令爲 filecap -a
,而查看進程 capability 的命令爲 pscap -a
,pscap 和 filecap工具須要安裝 libcap-ng-utils
這個包)。
對於 capability,能夠看一個簡單的例子便於理解。如 Debian 系統中自帶的 ping 工具,它是有設置 SUID 位的。這裏拷貝 ping 重命名爲 anotherping,anotherping 的 SUID 位沒有設置,運行會提示權限錯誤。這裏,咱們只要將其加上 cap_net_raw 權限便可,不須要設置 SUID 位那麼大的權限。
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 ms
UnionFS (聯合文件系統)簡單來講就是支持將不一樣的目錄掛載到同一個目錄中的技術。Docker 支持的 UnionFS 包 括OverlayFS,AUFS,devicemapper,vfs 以及 btrfs 等,查看 UnionFS 版本能夠用 docker info
查看對應輸出中的 Storage
項便可,早期的 Docker 版本用 AUFS 和 devicemapper 居多,新版本 Docker 在 Linux 3.18以後版本基本默認用 OverlayFS,這裏以 OverlayFS 來分析。
OverlayFS 與早期用過的 AUFS 相似,不過它比 AUFS 更簡單,讀寫性能更好,在 docker-ce18.03 版本中默認用的存儲驅動是 overlay2,老版本 overlay 官方已經不推薦使用。它將兩個目錄 upperdir 和 lowdir 聯合掛載到一個 merged 目錄,提供統一視圖。其中 upperdir 是可讀寫層,對容器修改寫入在該目錄中,它也會隱藏 lowerdir 中相同的文件。而 lowdir 是隻讀層, Docker 鏡像在這層。
在看 Docker 鏡像和容器存儲結構前,能夠先簡單操做下 OverlayFS 看下基本概念。建立了 lowerdir 和 upperdir 兩個目錄,而後用 overlayfs 掛載到 merged 目錄,這樣在 merged 目錄能夠看到兩個目錄的全部文件 both.txt 和 only.txt。其中 upperdir 是可讀寫的,而 lowerdir 只讀。經過 merged 目錄來操做文件能夠發現:
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 對應每一層都有個目錄(注意,這個目錄名跟鏡像層名從 docker1.10 版本後名字已經不對應了),另外的 l 目錄是指向鏡像層的軟連接。最底層存儲的是基礎鏡像 debian/alpine,上一層是安裝了 nginx 增長的可執行文件和配置文件,而最上層是連接 /dev/stdout 到 nginx 日誌文件。而每一個子目錄下面的 diff 目錄用於存儲鏡像內容,work 目錄是 OverlayFS 內部使用的,而 link 文件存儲的是該鏡像層對應的短名稱,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 就是咱們前面提到的聯合掛載目錄了,而 lowdir 則是它下層目錄。而容器 init 層用來存儲與這個容器內環境相關的內容,如 /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 目錄相關文件,容器層的 diff 目錄至關於 upperdir,其餘層是 lowerdir。若是以前容器層 diff 目錄不存在該文件,則會拷貝該文件到 diff 目錄並修改。讀取文件時,若是 upperdir 目錄找不到,則會直接從下層的鏡像層中讀取。
隨着版本不斷更新,Docker 的一些技術細節也在變化,如鏡像層存儲目錄的變化,默認 UnionFileSystem 換成 OverlayFS,新的 Namespace 的支持等。這篇文章主要對之前的學習筆記和 Docker 的一些新的變化作了些許總結,如想了解更詳細內容,能夠查看參考資料和 Docker 官方相關文檔。
做者:__七把刀__
連接:https://www.jianshu.com/p/7a1...
來源:簡書
更多相關閱讀:
Docker 容器操做
Docker 的那點事兒
Docker 基礎技術-Linux Namespace
docker-compose.yml 配置詳解
若是你還想了解更多,想和技術同僚分享切磋,可掃下方二維碼加好友,回覆yw,加入掘金運維技術交流羣