Docker特性與原了解析
文章假設你已經熟悉了Docker的基本指令和基本知識
首先看看Docker提供了哪些特性:
- 互動式Shell:Docker可以配置設定一個虛拟終端并關聯到任何容器的标準輸入上,例如運作一個一次性互動shell
- 檔案系統隔離:每個程序容器運作在完全獨立的根檔案系統裡
- 寫時複制:采用寫時複制方式建立根檔案系統,這讓部署變得極其快捷,并且節省記憶體和硬碟空間
- 資源隔離:可以使用cgroup為每個程序容器配置設定不同的系統資源
- 網絡隔離:每個程序容器運作在自己的網絡命名空間裡,擁有自己的虛拟接口和IP位址
- 日志記錄:Docker将會收集和記錄每個程序容器的标準流(stdout/stderr/stdin),用于實時檢索或批量檢索
- 變更管理:容器檔案系統的變更可以送出到新的映像中,并可重複使用以建立更多的容器。無需使用模闆或手動配置
從以上特性分别看實作原理
1. 互動式Shell
首先我們允許一個互動式的容器
$docker run -i -t <image name> /bin/bash
這樣就建立了一個到容器内的互動式連接配接,看到的是如下的指令行:
root@df3880b17407:/#
這裡我們啟動了一個容器,以bash作為其根程序.
root@df3880b17407:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 18164 2020 ? S 06:06 0:00 /bin/bash
可以看到,在這個容器中,bash 的 PID為 1,而實體機平常情況下,是這樣的:
root@ubuntu:~# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 24716 2612 ? Ss Sep04 0:01 /sbin/init
大家都知道,所有程序的共同祖先都是 PID=1的程序
是以在容器中,
所有以後建立的程序都是通過/bin/bash 建立的,PID=1的 bash是容器中所有程序的祖先
了解了這點後,對容器的了解就很簡單了.
2. 檔案系統隔離
對于一個正在運作的容器,其檔案系統都是一個從根目錄開始的虛拟檔案系統,在容器中看到的是這樣的:
root@df3880b17407:/# ll /
total 68
drwxr-xr-x 2 root root 4096 Jul 22 22:51 bin
drwxr-xr-x 2 root root 4096 Apr 10 22:12 boot
drwxr-xr-x 3 root root 4096 Jul 22 22:49 dev
drwxr-xr-x 85 root root 4096 Sep 5 06:49 etc
drwxr-xr-x 2 root root 4096 Apr 10 22:12 home
drwxr-xr-x 16 root root 4096 Jul 22 22:50 lib
drwxr-xr-x 2 root root 4096 Aug 12 03:30 lib64
drwxr-xr-x 2 root root 4096 Jul 22 22:48 media
drwxr-xr-x 2 root root 4096 Apr 10 22:12 mnt
drwxr-xr-x 2 root root 4096 Jul 22 22:48 opt
dr-xr-xr-x 356 root root 0 Sep 5 06:06 proc
drwx------ 2 root root 4096 Jul 22 22:51 root
drwxr-xr-x 7 root root 4096 Sep 5 07:23 run
drwxr-xr-x 2 root root 4096 Aug 12 03:30 sbin
drwxr-xr-x 2 root root 4096 Jul 22 22:48 srv
dr-xr-xr-x 13 root root 0 Sep 5 06:06 sys
drwxrwxrwt 2 root root 4096 Sep 5 06:55 tmp
drwxr-xr-x 20 root root 4096 Sep 5 06:11 usr
drwxr-xr-x 19 root root 4096 Sep 5 06:11 var
其實真是情況是這樣的,容器中的檔案系統都是挂載到了真是系統中的一個目錄下面.
/var/lib/docker/containers/<image-long-id>/rootfs
這個配置是怎麼來的呢,其實所有容器的管理都是通過lxc來管理的,lxc的配置檔案放在
/var/lib/docker/containers/<image-long-id>/config.lxc
檔案中有字段表示容器挂載到哪個檔案目錄, 比如我的是這樣的:
lxc.rootfs = /var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rootfs
打開看一下,一目了然:
root@ubuntu:/var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rootfs# ll
total 84
drwxr-xr-x 53 root root 4096 Sep 5 00:23 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 2 root root 4096 Jul 22 15:51 bin/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 boot/
drwxr-xr-x 3 root root 4096 Jul 22 15:49 dev/
drwxr-xr-x 85 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 home/
drwxr-xr-x 16 root root 4096 Jul 22 15:50 lib/
drwxr-xr-x 2 root root 4096 Aug 11 20:30 lib64/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 media/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 mnt/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 opt/
drwxr-xr-x 2 root root 4096 Apr 10 15:12 proc/
drwx------ 2 root root 4096 Jul 22 15:51 root/
drwxr-xr-x 7 root root 4096 Sep 5 00:23 run/
drwxr-xr-x 2 root root 4096 Aug 11 20:30 sbin/
drwxr-xr-x 2 root root 4096 Jul 22 15:48 srv/
drwxr-xr-x 2 root root 4096 Mar 12 18:41 sys/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 20 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 19 root root 4096 Sep 4 23:11 var/
這些就是容器中的真實目錄了,容器中對于目錄的操作都是操作了這個host機器的真實目錄。
對于不同的容器,挂載點是不一樣的,而容器不能穿越根目錄上一級去通路, 是以這裡對每一個容器都做到了檔案系統隔離。
3. 寫時複制
我們把每一個
/var/lib/docker/containers/<image-long-id>
看做是一個容器的配置目錄的話,可以看到在配置目錄下面有一個
rw/
目錄,打開看有些什麼
total 36
drwxr-xr-x 9 root root 4096 Sep 5 00:23 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 6 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Sep 5 00:23 run/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 7 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 5 root root 4096 Sep 4 23:11 var/
-r--r--r-- 1 root root 0 Sep 4 23:06 .wh..wh.aufs
drwx------ 2 root root 4096 Sep 4 23:06 .wh..wh.orph/
drwx------ 2 root root 4096 Sep 4 23:11 .wh..wh.plnk/
裡面是一些不完整的根目錄,這不能說明什麼,但是我們在container中寫入檔案後,看看其中的變化
在容器中執行以下指令
root@df3880b17407:/# touch /opt/x
在 /opt 下我們生成了一個檔案
再看看
root@ubuntu:/var/lib/docker/containers/df3880b17407575cd642a6b7da3c7e417a55fad5bbd63152f89921925626d2b6/rw# ll
total 40
drwxr-xr-x 10 root root 4096 Sep 5 01:00 ./
drwx------ 4 root root 4096 Sep 5 00:53 ../
drwxr-xr-x 6 root root 4096 Sep 4 23:49 etc/
drwxr-xr-x 2 root root 4096 Sep 5 01:00 opt/
drwxr-xr-x 2 root root 4096 Sep 5 00:23 run/
drwxrwxrwt 2 root root 4096 Sep 4 23:55 tmp/
drwxr-xr-x 7 root root 4096 Sep 4 23:11 usr/
drwxr-xr-x 5 root root 4096 Sep 4 23:11 var/
-r--r--r-- 1 root root 0 Sep 4 23:06 .wh..wh.aufs
drwx------ 2 root root 4096 Sep 4 23:06 .wh..wh.orph/
drwx------ 2 root root 4096 Sep 4 23:11 .wh..wh.plnk/
是的,在host機器上新生成了
opt/
目錄,這裡做到了容器的寫時複制
4. 資源隔離
以系統的三大程序間通信的消息隊列來看
初始狀态在 ghost, ipcs -q 檢視消息隊列
root@ubuntu:~/codes/msq# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
在ghost 建立一個, 這裡樓主自己寫的代碼建立的消息隊列:
root@ubuntu:~/codes/msq# ./main 1
Create msq with key id 65536root@ubuntu:~/codes/msq# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00000001 65536 root 666 0 0
然後在容器中檢視
ipcs -q
root@df3880b17407:/# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
可以看到系統資源是隔離的,這裡隻是說了一部分,其實還包括了可以通過cgoup對其做CPU和Memory的Quota管理.
預設情況下是使用了所有CPU和記憶體的,但是可以在config.lxc增加如下配置設定CPU等,具體可以參考lxc的文檔
lxc.cgroup.cpu.shares=512 lxc.cgroup.cpuset.cpus=1.2
資源隔離的原理就在于利用cgroup,将不同程序的使用隔離開,假設每個容器都是以bash啟動的,那麼在容器内部,每個子程序都隻能使用目前bash下面的資源,對于其他的系統資源是隔離的.
子程序的通路權限由父程序決定
5.網絡隔離
在安裝好docker後,會預設初始化一個
docker0
的網橋
docker0 Link encap:Ethernet HWaddr ee:8c:1f:8b:d7:59
inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0
...
在host機器上,會為每一個容器生成一個預設的網卡類似這樣的
vethdBVa1H
veth*
這個網卡的一端連接配接在容器的eth0,一端連接配接到docker0.這樣就實作了每個容器有一個單獨的IP.
這裡如果需要容器通路外網,需要将eth0設定為混雜模式:
$ifconfig eth0 promisc
這樣看來,容器會從172.17.0.0/24 這個網段選擇一個IP作為eth0的IP,這樣,容器就可以和外部通過 docker0網橋通信了.
在容器内部監聽一個端口
python -m SimpleHTTPServer 80 >> /tmp/log.log &
從ghost通路
telnet 172.17.0.2 80
在容器中看到如下:
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2823/python
tcp 0 0 172.17.0.2:80 172.17.42.1:46142 TIME_WAIT -
在host上看到
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 172.17.42.1:46142 172.17.0.2:80 ESTABLISHED 10244/telnet
如果需要外部能夠通路容器,需要做端口映射規則,和配置虛拟機一樣的道理, 隻不過這裡可以看到的是,80端口并沒有占用了本地端口,而是在容器内部做了監聽,外部是通過docker0 橋接過去的,每個容器間也做到了端口和網絡隔離.
6.日志記錄
不多說,在
/var/lib/docker/containers/<image-long-id>.log
下
7.變更管理
文章屬原創,轉載請注明出處