容器化技術在當前雲計算、微服務等體系下大行其道,而 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 busybox
ide
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_NEWPID
和CLONE_NEWNS
表示Linux NameSpace的調用類別,分別表示建立新的進程命名空間和 掛載命名空間。微服務
CLONE_NEWPID
會讓執行的程序內部從新編號PID
,也就是從1
號進程開始工具
CLONE_NEWNS
會克隆新的掛載環境出來,經過在子進程內部從新掛載proc
文件夾,能夠屏蔽父進程的進程信息。性能
咱們執行一下這段程序來看看效果。
編譯
gcc container.c -o container
複製代碼
執行
[root@host1 luozhou]# ./container
宿主機進程[ 6061] - 開始一個容器!
容器進程[ 1] ----進入容器!
複製代碼
這裏咱們看到輸出在宿主機看來,這個程序的PID
是6061
,在克隆的子進程來看,它的PID
是1
,咱們執行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)
複製代碼
咱們看到上面掛載的目錄有包括 cpu
和memory
那咱們猜想大概就是在這個文件夾下面配置限制信息的了。咱們跑一個容器來驗證下,執行命令:
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%,關於這兩個參數的詳細說明能夠點擊這裏
咱們查看進程消耗狀況發現 剛剛啓動的容器資源確實被限制在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技術因爲是一個普通的宿主機進程,因此具備性能優點,而虛擬機因爲徹底虛擬系統,因此具有了高隔離性和安全性的優點,二者互有優缺點。不過容器化是當下的趨勢,相信隨着技術的成熟,目前的隔離不完全的問題也能解決,容器化走天下不是夢。