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

Docker特性與原理解析

文章假設你已經熟悉了Docker的基本命令和基本知識python

首先看看Docker提供了哪些特性:

  • 交互式Shell:Docker能夠分配一個虛擬終端並關聯到任何容器的標準輸入上,例如運行一個一次性交互shell
  • 文件系統隔離:每一個進程容器運行在徹底獨立的根文件系統裏
  • 寫時複製:採用寫時複製方式建立根文件系統,這讓部署變得極其快捷,而且節省內存和硬盤空間
  • 資源隔離:可使用cgroup爲每一個進程容器分配不一樣的系統資源
  • 網絡隔離:每一個進程容器運行在本身的網絡命名空間裏,擁有本身的虛擬接口和IP地址
  • 日誌記錄:Docker將會收集和記錄每一個進程容器的標準流(stdout/stderr/stdin),用於實時檢索或批量檢索
  • 變動管理:容器文件系統的變動能夠提交到新的映像中,並可重複使用以建立更多的容器。無需使用模板或手動配置

從以上特性分別看實現原理

1. 交互式Shell

首先咱們容許一個交互式的容器git

$docker run -i -t <image name> /bin/bash

這樣就創建了一個到容器內的交互式鏈接,看到的是以下的命令行:docker

root@df3880b17407:/#

這裏咱們啓動了一個容器,以bash做爲其根進程.shell

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,而實體機日常狀況下,是這樣的:ubuntu

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是容器中全部進程的祖先
理解了這點後,對容器的理解就很簡單了.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機器的真實目錄。
對於不一樣的容器,掛載點是不同的,而容器不能穿越根目錄上一級去訪問, 因此這裏對每個容器都作到了文件系統隔離。tcp

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

Docker的變動管理看作是git的版本管理好了。
生成鏡像的時候,未作改動的部分就是上一個版本的鏡像的引用,若是作了改動,就是一個新的文件。

將剛纔操做的容器作成鏡像
docker commit <image-id> <REPOSITORY>
此時的鏡像多出來的部分,好比我在這個鏡像中安裝了Python,那麼多出來的部分做爲新文件,其餘部分任然是上一個版本的引用。
你能夠搭建本身的鏡像服務器,push到本身的鏡像服務器,從其餘機器拉下來後直接能夠運行。

以上,一點愚見,但願指正.

相關文章
相關標籤/搜索