前言
Docker是一個開源的軟件項目,讓用戶程序部署在一個相對隔離的環境運行,借此在Linux操作系統上提供一層額外的抽象,以及操作系統層虛擬化的自動管理機制。需要額外指出的是,Docker並不等於容器(containers),Docker只是容器的一種,其他的種類的容器還有Kata container,Rocket container等等。
基本原理
Docker利用Linux中的核心分離機制,例如Cgroups,以及Linux的核心Namespace(名字空間)來創建獨立的容器。一句話概括起來Docker就是利用Namespace做資源隔離,用Cgroup做資源限制,利用Union FS做容器文件系統的輕量級虛擬化技術。Docker容器的本質還是一個直接運行在宿主機上面的特殊進程,看到的文件系統是隔離后的,但是操作系統內核是共享宿主機OS,所以說Docker是輕量級的虛擬化技術。
Namespace
Linux Namespace 是Linux 提供的一種內核級別環境隔離的方法,使其中的進程好像擁有獨立的操作系統環境。Linux Namespace 有 Mount Namespace,UTS Namespace, IPC Namespace, PID Namespace, Network Namespace, User Namespace, Cgroup Namespace。詳情看下表:
分類 | 系統調用參數 | 隔離內容 | 內核版本 |
Mount Namespace | CLONE_NEWNS | 文件系統掛載點 | Linux 2.4.19(2002年) |
UTS Namespace | CLONE_NEWUTS | Hostname和domain name | Linux 2.6.19 |
IPC Namespace | CLONE_NEWIPC | 進程間通信方式,例如消息隊列 | Linux 2.6.19 |
PID Namespace | CLONE_NEWPID | 進程ID編號 | Linux 2.6.24 |
Network Namespace, | CLONE_NEWNET | 網絡設備,協議棧,路由表,防火牆規則,端口等 | Linux 2.6.24 start Linux 2.6.29 end |
User Namespace | CLONE_NEWUSER | 用戶及組ID | Linux 2.6.23 start Linux 3.8 end |
Cgroup Namespace | CLONE_NEWCGROUP | Cgroup根目錄 | Linux 4.6 |
上述系統調用參數CLONE_NEWNS等主要應用於以下三個系統調用:
- clone 創建新進程並設置它的Namespace,類似於fork系統調用,可創建新進程並且指定子進程將要執行的函數,通過上述CLONE_NEWNS等參數使某類資源處於隔離狀態。
#include <sched.h> int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
例如:
int pid = clone(call_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
會讓新創建的該進程執行call_function,例如/bin/bash,且該進程的PID進程編號是隔離狀態,也就是新的PID編號,該進程ps將會看到它的PID是1。
如果多次執行上述clone就會創建多個PID Namespace,而每個Namespace里面的應用進程都認為自己是當前容器里的1號進程,它們既看不到宿主機里的真實進程空間,也看不到其他PID Namespace里面的具體情況。
- int unshare(int flags) 使進程脫離某個Namespace,flags參數和clone的用法一致。
- int setns(int fd, int nstype) 使進程進入某個已經存在的Namespace。經常用於從宿主機進入已經啟動的容器Network Namespace,然后設置它的網絡。
Cgroup
上面已經講過Docker 容器運行起來是一個直接運行在宿主機上面的進程,那么如果限定每個容器最多消耗多少CPU資源呢?如果一個容器瘋狂的消耗資源豈不是會影響同一宿主機上面其他的容器?所以Docker就需要一個限制容器能夠使用資源上限的機制,那就是Linux Cgroup技術。Linux Cgroup 全稱是Linux Control Group。它最主要的作用是限制一個進程組能夠使用的資源上限,包括CPU,MEM,DISK,NET等等。
下面我將演示一個利用Cgroup限制進程CPU的例子:
[root@nginx-1 /sys/fs/cgroup/cpu]# ll total 0 -rw-r--r-- 1 root root 0 Sep 26 2018 cgroup.clone_children --w--w--w- 1 root root 0 Sep 26 2018 cgroup.event_control -rw-r--r-- 1 root root 0 Sep 26 2018 cgroup.procs -r--r--r-- 1 root root 0 Sep 26 2018 cgroup.sane_behavior -r--r--r-- 1 root root 0 Sep 26 2018 cpuacct.stat -rw-r--r-- 1 root root 0 Sep 26 2018 cpuacct.usage -r--r--r-- 1 root root 0 Sep 26 2018 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.cfs_relax_thresh_sec -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.rt_period_us -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Sep 26 2018 cpu.shares -r--r--r-- 1 root root 0 Sep 26 2018 cpu.stat drwxr-xr-x 9 root root 0 Jun 6 17:03 docker -rw-r--r-- 1 root root 0 Sep 26 2018 notify_on_release -rw-r--r-- 1 root root 0 Sep 26 2018 release_agent -rw-r--r-- 1 root root 0 Sep 26 2018 tasks [root@nginx-1 /sys/fs/cgroup/cpu]# mkdir mytest #創建mytest目錄,系統會自動添加以下文件 [root@nginx-1 /sys/fs/cgroup/cpu/mytest]# ll total 0 -rw-r--r-- 1 root root 0 Jun 13 16:55 cgroup.clone_children --w--w--w- 1 root root 0 Jun 13 16:55 cgroup.event_control -rw-r--r-- 1 root root 0 Jun 13 16:55 cgroup.procs -r--r--r-- 1 root root 0 Jun 13 16:55 cpuacct.stat -rw-r--r-- 1 root root 0 Jun 13 16:55 cpuacct.usage -r--r--r-- 1 root root 0 Jun 13 16:55 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.cfs_relax_thresh_sec -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.rt_period_us -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Jun 13 16:55 cpu.shares -r--r--r-- 1 root root 0 Jun 13 16:55 cpu.stat -rw-r--r-- 1 root root 0 Jun 13 16:55 notify_on_release -rw-r--r-- 1 root root 0 Jun 13 16:55 tasks [root@nginx-1 /sys/fs/cgroup/cpu/mytest]# while : ; do : ; done & # 運行一個死循環命令 [1] 2459615
top觀察會發現該進程CPU跑到了100%,符合預期
主要的限制參數來源自文件cpu.cfs_quota_us,默認是-1,不做限制,如果改成20000說明限定20%的CPU上限。因為總量存在於cpu.cfs_period_us,是100000,意思cpu時間總量是100000us,20000/100000=20%。然后將bash命令的PID寫到tasks文件中,改完之后的CPU占用是20%,符合預期:
同理可限制MEM,DISK和NET,需要特殊指出的是MEM是硬限制,當容器的內存使用量超過了Cgroup限定值會被系統OOM。
Union FS
每個容器運行起來后都有一個獨立的文件系統,例如Ubuntu鏡像的容器能夠看到Ubuntu的文件系統,Centos能夠看到Centos的文件系統, 不是說容器是運行在宿主機上面的進程嗎,為什么能夠看到和宿主機不一樣的文件系統呢?那就要歸功於Union FS,全稱是Union File System,聯合文件系統。將多個不同位置的目錄聯合掛載到同一個目錄,將相同的部分合並。Docker利用這種聯合掛載能力,將容器鏡像里面的多層內容呈現為統一的rootfs(根文件系統),即root用戶能夠看到的根目錄底下所有的目錄文件。rootfs打包了整個操作系統的文件和目錄,是應用運行時所需要的最完整的“依賴庫”,也就是我們說的“鏡像”。
鏡像分為基礎鏡像只讀層,和Init層,和讀寫層。
Init 層存放的是/etc/hostname,/etc/resolv.conf 等, docker commit的時候不提交。
讀寫層一開始的時候為空,用戶如果修改了文件系統,比如說增刪改了文件,docker commit的時候就會提交這一層信息。
Docker VS 虛擬機
從上面這幅圖就可以看出,虛擬機是正兒八經的存在一層硬件虛擬層,模擬出了運行一個操作系統需要的各種硬件,例如CPU,MEM,IO等設備。然后在虛擬的硬件上安裝了一個新的操作系統Guest OS。所以在Windows宿主機上面可以跑Linux虛擬機。因為多了一層虛擬,所以虛擬機的性能必然會有所損耗。Docker容器是由Docker Deamon(Docker Deamon是運行在宿主機上面的一個后台進程,負責拉起和設置容器)拉起的一個個進程,通過Docker Deamon設置完Namespace和Cgroup之后,本質上就是一個運行在宿主機上面的進程。因為沒有一層虛擬的Guest OS,所以Docker輕量級很多。但是有利就有弊,由於Docker 容器直接運行在宿主機上面,安全性就相對較差些,另外也沒有辦法在Windows上面運行Linux的容器,如果容器里面的應用對特定系統內核有要求也不能運行在不滿足要求的宿主機上面。
總結
Docker 容器總結起來就是利用Linux Namespace做資源隔離,Cgroup做資源上限限制,rootfs做文件系統 運行在宿主機上面的一個特殊進程。