天天看點

[原][Docker]特性與原了解析

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.變更管理

文章屬原創,轉載請注明出處