Docker是如何實現隔離的

概述

容器化技術在當前雲計算、微服務等體系下大行其道,而 Docker 即是容器化技術的典型,對於容器化典型的技術,咱們有必要弄懂它,因此這篇文章,我會來分析下 Docker 是如何實現隔離技術的,Docker 與虛擬機又有哪些區別呢?接下來,咱們開始逐漸揭開它的面紗。node

從運行一個容器開始

咱們開始運行一個簡單的容器,這裏以busybox鏡像爲例,它是一個經常使用的Linux工具箱,能夠用來執行不少Linux命令,咱們以它爲鏡像啓動容器方便來查看容器內部環境。 執行命令:docker

docker run -it --name demo_docker busybox /bin/sh
複製代碼

這條命令的意思是:啓動一個busybox鏡像的 Docker 容器,-it參數表示給容器提供一個輸出/輸出的交互環境,也就是TTY。/bin/sh表示容器交互運行的命令或者程序。安全

進程的隔離

執行成功後咱們就會進入到了 Docker 容器內部,咱們執行ps -ef 查看進程bash

/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    8 root      0:00 ps -ef
複製代碼

使用top命令查看進程資源dom

Mem: 1757172K used, 106080K free, 190676K shrd, 129872K buff, 998704K cached
CPU:  0.0% usr  0.2% sys  0.0% nic 99.6% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.00 0.01 0.05 2/497 9
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     S     1300  0.0   1  0.0 /bin/sh
    9     1 root     R     1292  0.0   3  0.0 top
複製代碼

而咱們在宿主機查看下當前執行容器的進程ps -ef|grep busyboxide

root       5866   5642  0 01:19 pts/4    00:00:00 /usr/bin/docker-current run -it --name demo_docker busybox /bin/sh
root       5952   5759  0 01:20 pts/11   00:00:00 grep --color=auto busybox
複製代碼

這裏咱們能夠知道,對於宿主機 docker run執行命令啓動的只是一個進程,它的pid是5866。而對於容器程序自己來講,它被隔離了,在容器內部都只能看到本身內部的進程,那 Docker 是如何作到的呢?它實際上是藉助了Linux內核的Namespace技術來實現的,這裏我結合一段C程序來模擬一下進程的隔離。函數

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mount.h>
/* 定義一個給 clone 用的棧,棧大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};


int container_main(void* arg)
{
    printf("容器進程[%5d] ----進入容器!\n",getpid());
    mount("proc", "/proc", "proc", 0, NULL);
    /**執行/bin/bash */
    execv(container_args[0], container_args);
    printf("出錯啦!\n");
    return 1;
}

int main()
{
    printf("宿主機進程[%5d] - 開始一個容器!\n",getpid());
    /* 調用clone函數 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE,  CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    /* 等待子進程結束 */
    waitpid(container_pid, NULL, 0);
    printf("宿主機 - 容器結束!\n");
    return 0;
}
複製代碼

考慮到不少同窗對C語言不是很熟悉,我這裏簡單解釋下這段程序,這段程序主要就是執行clone()函數,去克隆一個進程,而克隆執行的程序就是咱們的container_main函數,接着下一個參數就是棧空間,而後CLONE_NEWPIDCLONE_NEWNS 表示Linux NameSpace的調用類別,分別表示建立新的進程命名空間和 掛載命名空間。微服務

  • CLONE_NEWPID會讓執行的程序內部從新編號PID,也就是從1號進程開始工具

  • CLONE_NEWNS 會克隆新的掛載環境出來,經過在子進程內部從新掛載proc文件夾,能夠屏蔽父進程的進程信息。性能

咱們執行一下這段程序來看看效果。

編譯

gcc container.c -o container
複製代碼

執行

[root@host1 luozhou]# ./container 
宿主機進程[ 6061] - 開始一個容器!
容器進程[    1] ----進入容器!
複製代碼

這裏咱們看到輸出在宿主機看來,這個程序的PID6061,在克隆的子進程來看,它的PID1,咱們執行ps -ef 查看一下進程列表

[root@host1 luozhou]# ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 01:46 pts/2    00:00:00 /bin/bash
root         10      1  0 01:48 pts/2    00:00:00 ps -ef
複製代碼

咱們發現確實只有容器內部的進程在運行了,再執行top命令

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                  
     1 root      20   0  115576   2112   1628 S   0.0  0.1   0:00.00 bash                                                                                                     
    11 root      20   0  161904   2124   1544 R   0.0  0.1   0:00.00 top  
複製代碼

結果也只有2個進程的信息。

這就是容器隔離進程的基本原理了,Docker主要就是藉助 Linux 內核技術Namespace來作到隔離的,其實包括我後面要說到文件的隔離,資源的隔離都是在新的命名空間下經過mount掛載的方式來隔離的。

文件的隔離

瞭解完進程的隔離,相信大家已經對 Docker 容器的隔離玩法就大概的印象了,咱們接下來看看,Docker 內部的文件系統如何隔離,也就是你在 Docker 內部執行 ls 顯示的文件夾和文件如何來的。

咱們仍是之前面的 Docker 命令爲例,執行ls

bin   dev   etc   home  proc  root  run   sys   tmp   usr   var
複製代碼

咱們發現容器內部已經包含了這些文件夾了,那麼這些文件夾哪裏來的呢?咱們先執行docker info來看看咱們的 Docker 用到的文件系統是什麼?

Server Version: 1.13.1
Storage Driver: overlay2
複製代碼

個人版本是1.13.1,存儲驅動是overlay2,不一樣的存儲驅動在 Docker 中表現不同,可是原理相似,咱們來看看 Docker 如何藉助overlay2來變出這麼多文件夾的。咱們前面提到過,Docker都是經過mount 去掛載的,咱們先找到咱們的容器實例id.

執行docker ps -a |grep demo_docker

c0afd574aea7        busybox                         "/bin/sh"                42 minutes ago      Up 42 minutes 
複製代碼

咱們再根據咱們的容器ID 去查找掛載信息,執行cat /proc/mounts | grep c0afd574aea7

shm /var/lib/docker/containers/c0afd574aea716593ceb4466943bbd13e3a081bf84da0779ee43600de0df384b/shm tmpfs rw,context="system_u:object_r:container_file_t:s0:c740,c923",nosuid,nodev,noexec,relatime,size=65536k 0 0
複製代碼

這裏出現了一個掛載信息,可是這個記錄不是咱們的重點,咱們須要找到overlay2的掛載信息,因此這裏咱們還須要執行一個命令:cat /proc/mounts | grep system_u:object_r:container_file_t:s0:c740,c923

overlay /var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/merged overlay rw,context="system_u:object_r:container_file_t:s0:c740,c923",relatime,lowerdir=/var/lib/docker/overlay2/l/FWESUOVO6DYTXBBJIQBPUWLN6K:/var/lib/docker/overlay2/l/XPKQU6AMUX3AKLAX2BR6V4JQ3R,upperdir=/var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/diff,workdir=/var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/work 0 0
shm /var/lib/docker/containers/c0afd574aea716593ceb4466943bbd13e3a081bf84da0779ee43600de0df384b/shm tmpfs rw,context="system_u:object_r:container_file_t:s0:c740,c923",nosuid,nodev,noexec,relatime,size=65536k 0 0
複製代碼

這裏overlay掛載並無和容器id關聯起來,因此咱們直接根據容器id是找不到 overlay掛載信息的,這裏藉助了context 去關聯的,因此咱們經過context的內容就找到了咱們掛載的地址啦。咱們進入目錄看看結果

[root@host1 l]# ls /var/lib/docker/overlay2/9c9318031bc53dfca45b6872b73dab82afcd69f55066440425c073fe681109d3/merged
bin  dev  etc  home  proc  root  run  sys  tmp  usr  var
複製代碼

咱們發現這個和咱們容器的目錄是一致的,咱們在這個目錄下建立一個新的目錄,而後看看容器內部是否是會出現新的目錄。

文件系統隔離
文件系統隔離

上面的圖片驗證了容器內部的文件內容和掛載的/var/lib/docker/overlay2/ID/merged下是一致的,這就是Docker文件系統隔離的基本原理。

資源的限制

玩過 Docker 的同窗確定知道,Docker 仍是能夠限制資源使用的,好比 CPU 和內存等,那這部分是如何實現的呢? 這裏就涉及到Linux的另一個概念Cgroups技術,它是爲進程設置資源限制的重要手段,在Linux 中,一切皆文件,因此Cgroups技術也會體如今文件中,咱們執行mount -t cgroup 就能夠看到Cgroups的掛載狀況

cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)

複製代碼

咱們看到上面掛載的目錄有包括 cpumemory 那咱們猜想大概就是在這個文件夾下面配置限制信息的了。咱們跑一個容器來驗證下,執行命令:

docker run -d --name='cpu_set_demo' --cpu-period=100000 --cpu-quota=20000 busybox md5sum /dev/urandom 
複製代碼

這個命令表示咱們須要啓動一個容器,這個容器一直產生隨機數進行md5計算來消耗CPU,--cpu-period=100000 --cpu-quota=20000表示限制 CPU 使用率在20%,關於這兩個參數的詳細說明能夠點擊這裏

進程top
進程top

咱們查看進程消耗狀況發現 剛剛啓動的容器資源確實被限制在20%,說明 Docker 的CPU限制參數起做用了,那對應在咱們的cgroup 文件夾下面是怎麼設置的呢? 一樣,這裏的配置確定是和容器實例id掛鉤的,個人文件路徑是在/sys/fs/cgroup/cpu/system.slice/docker-5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036.scope下,5bbf589ae223b347c0d10b7e97cd1461ef82149a6d7fb144e8b01fcafecad036 就是咱們啓動的容器id了。

切換到上面的文件夾下,查看咱們設置的參數:

[root@host1]# cat cpu.cfs_period_us
100000
[root@host1]# cat cpu.cfs_quota_us 
20000
複製代碼

發現這裏咱們的容器啓動設置參數同樣,也就是說經過這裏的文件值來限制容器的cpu使用狀況。這裏須要注意的是,不一樣的Linux版本 Docker Cgroup 文件位置可能不同,有些是在/sys/fs/cgroup/cpu/docker/ID/ 下。

與傳統虛擬機技術的區別

通過前面的進程、文件系統、資源限制分析,詳細各位已經對 Docker 的隔離原理有了基本的認識,那麼它和傳統的虛擬機技術有和區別呢?這裏貼一個網上的Docker和虛擬機區別的圖

圖片來源極客時間
圖片來源極客時間

這張圖應該能夠清晰的展現了虛擬機技術和 Docker 技術的區別了,虛擬機技術是徹底虛擬出一個單獨的系統,有這個系統去處理應用的各類運行請求,因此它實際上對於性能來講是有影響的。而 Docker 技術 徹底是依賴 Linux 內核特性 Namespace 和Cgroup 技術來實現的,本質來講:你運行在容器的應用在宿主機來講仍是一個普通的進程,仍是直接由宿主機來調度的,相對來講,性能的損耗就不多,這也是 Docker 技術的重要優點。

Docker 技術因爲 仍是一個普通的進程,因此隔離不是很完全,仍是共用宿主機的內核,在隔離級別和安全性上沒有虛擬機高,這也是它的一個劣勢。

總結

這篇文章我經過實踐來驗證了 Docker 容器技術在進程、文件系統、資源限制的隔離原理,最後也比較了虛擬機和 Docker 技術的區別,總的來講 Docker技術因爲是一個普通的宿主機進程,因此具備性能優點,而虛擬機因爲徹底虛擬系統,因此具有了高隔離性和安全性的優點,二者互有優缺點。不過容器化是當下的趨勢,相信隨着技術的成熟,目前的隔離不完全的問題也能解決,容器化走天下不是夢。

參考

  1. people.redhat.com/vgoyal/pape…
  2. docs.docker.com/v17.09/engi…
  3. lwn.net/Articles/25…
相關文章
相關標籤/搜索