1. 程式人生 > 實用技巧 >容器(四)實現容器的底層技術【25】

容器(四)實現容器的底層技術【25】

(九)實現容器的底層技術

​ 為了更好地理解容器的特性,本節我們將討論容器的底層實現技術。 cgroup 和 namespace 是最重要的兩種技術。cgroup 實現資源限額, namespace 實現資源隔離。

(1)cgroup

​ cgroup 全稱 Control Group。Linux 作業系統通過 cgroup 可以設定程序使用 CPU、記憶體 和 IO 資源的限額。相信你已經猜到了:前面我們看到的--cpu-shares-m--device-write-bps實際上就是在配置 cgroup。

​ cgroup 到底長什麼樣子呢?我們可以在 /sys/fs/cgroup 中找到它。還是用例子來說明,啟動一個容器,設定--cpu-shares=512

root@cuiyongchao:~# docker run -it --cpu-shares 512 progrium/stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [6] forked


檢視容器ID:

root@cuiyongchao:~# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                 PORTS               NAMES
b473e4497b3b        progrium/stress          "/usr/bin/stress --v…"   17 seconds ago      Up 16 seconds                              friendly_kepler

在 /sys/fs/cgroup/cpu/docker 目錄中,Linux 會為每個容器建立一個 cgroup 目錄,以容器長ID 命名:

root@cuiyongchao:~# ls /sys/fs/cgroup/cpu/docker/b473e4497b3b63c0ea4907857bcd4ef0ea3a455bce20f0715ecfeb5afd047bba/
cgroup.clone_children  cpuacct.usage_all          cpuacct.usage_sys   cpu.shares
cgroup.procs           cpuacct.usage_percpu       cpuacct.usage_user  cpu.stat
cpuacct.stat           cpuacct.usage_percpu_sys   cpu.cfs_period_us   notify_on_release
cpuacct.usage          cpuacct.usage_percpu_user  cpu.cfs_quota_us    tasks
root@cuiyongchao:~# cat /sys/fs/cgroup/cpu/docker/b473e4497b3b63c0ea4907857bcd4ef0ea3a455bce20f0715ecfeb5afd047bba/cpu.shares 
512
root@cuiyongchao:~# 

​ 目錄中包含所有與 cpu 相關的 cgroup 配置,檔案 cpu.shares 儲存的就是 --cpu-shares 的配置,值為 512。同樣的,/sys/fs/cgroup/memory/docker 和 /sys/fs/cgroup/blkio/docker 中儲存的是記憶體以及 Block IO 的 cgroup 配置。

(2)namespace

​ 在每個容器中,我們都可以看到檔案系統,網絡卡等資源,這些資源看上去是容器自己的。拿網絡卡來說,每個容器都會認為自己有一塊獨立的網絡卡,即使 host 上只有一塊物理網絡卡。這種方式非常好,它使得容器更像一個獨立的計算機。

​ Linux 實現這種方式的技術是 namespace。namespace 管理著 host 中全域性唯一的資源,並可以讓每個容器都覺得只有自己在使用它。換句話說,namespace 實現了容器間資源的隔離。Linux 使用了六種 namespace,分別對應六種資源:Mount、UTS、IPC、PID、Network 和 User,下面我們分別討論。

(3)Mount namespace

​ Mount namespace 讓容器看上去擁有整個檔案系統。容器有自己的 / 目錄,可以執行 mountumount 命令。當然我們知道這些操作只在當前容器中生效,不會影響到 host 和其他容器。

(4)UTS namespace

​ 簡單的說,UTS namespace 讓容器有自己的 hostname。 預設情況下,容器的 hostname 是它的短ID,可以通過-h--hostname引數設定。

root@cuiyongchao:~# docker run -it -h zhangsan ubuntu
root@zhangsan:/# hostname 
zhangsan
root@zhangsan:/# 

(5)IPC namespace

​ IPC namespace 讓容器擁有自己的共享記憶體和訊號量(semaphore)來實現程序間通訊,而不會與 host 和其他容器的 IPC 混在一起。

(6)PID namespace

我們前面提到過,容器在 host 中以程序的形式執行。例如當前 host 中運行了兩個容器:

root@cuiyongchao:~# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                 PORTS               NAMES
1c07fc518fbc        ubuntu                   "/bin/bash"              29 minutes ago      Up 29 minutes                              upbeat_knuth
b473e4497b3b        progrium/stress          "/usr/bin/stress --v…"   55 minutes ago      Up 55 minutes                              friendly_kepler
137cc65e3025        progrium/stress:latest   "/usr/bin/stress --v…"   14 hours ago        Up 14 hours                                container_B
2d54da22fd5f        progrium/stress:latest   "/usr/bin/stress --v…"   14 hours ago        Up 14 hours (Paused)                       container_A
root@cuiyongchao:~# 

通過ps axf可以檢視容器程序:

24709 ?        Ssl    4:43 /usr/bin/containerd
 88927 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 88948 pts/0    Ds+    0:00  |   \_ /usr/bin/stress --verbose --cpu 4
 88990 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88991 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88992 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88993 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 89014 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 89037 pts/0    Ss+    0:00  |   \_ /usr/bin/stress --verbose --cpu 4
 89075 pts/0    R+   139:11  |       \_ /usr/bin/stress --verbose --cpu 4
 89076 pts/0    R+   139:22  |       \_ /usr/bin/stress --verbose --cpu 4
 89077 pts/0    R+   138:13  |       \_ /usr/bin/stress --verbose --cpu 4
 89078 pts/0    R+   136:01  |       \_ /usr/bin/stress --verbose --cpu 4
 90634 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 90659 pts/0    Ss+    0:00  |   \_ /usr/bin/stress --verbose -c 1
 90701 pts/0    R+    55:18  |       \_ /usr/bin/stress --verbose -c 1
 91164 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 91192 pts/0    Ss+    0:00      \_ /bin/bash
 27166 ?        Ss     0:00 /usr/sbin/sshd -D
 86477 ?        Ss     0:00  \_ sshd: root@pts/3
 86604 pts/3    Ss     0:00  |   \_ -bash
 88907 pts/3    Sl+    0:00  |       \_ docker run --name container_A -it -c 1024 progrium/stress:la
 88307 ?        Ss     0:00  \_ sshd: root@pts/4
 88391 pts/4    Ss     0:00  |   \_ -bash
 88994 pts/4    Sl+    0:00  |       \_ docker run --name container_B -it -c 512 progrium/stress:lat
 88502 ?        Ss     0:00  \_ sshd: root@pts/5
 88585 pts/5    Ss+    0:00  |   \_ -bash
 88603 ?        Ss     0:00  \_ sshd: root@pts/6
 88686 pts/6    Ss+    0:00  |   \_ -bash
 89700 ?        Ss     0:00  \_ sshd: root@pts/7
 89828 pts/7    Ss     0:00  |   \_ -bash
 90615 pts/7    Sl+    0:00  |       \_ docker run -it --cpu-shares 512 progrium/stress -c 1
 90702 ?        Ss     0:00  \_ sshd: root@pts/8
 90782 pts/8    Ss     0:00  |   \_ -bash
 91146 pts/8    Sl+    0:00  |       \_ docker run -it -h zhangsan ubuntu
 91249 ?        Ss     0:00  \_ sshd: root@pts/0
 91371 pts/0    Ss     0:00      \_ -bash
 91397 pts/0    R+     0:00          \_ ps -axf
 28349 ?        Ss     0:01 /lib/systemd/systemd --user
 28350 ?        S      0:00  \_ (sd-pam)
 83248 ?        Ss     0:00 /lib/systemd/systemd-networkd
 87520 ?        Ssl    0:02 /usr/bin/dockerd --insecure-registry 10.0.0.20:5000
root@cuiyongchao:~# 

所有容器的程序都掛在 dockerd 程序下,同時也可以看到容器自己的子程序。 如果我們進入到某個容器,ps就只能看到自己的程序了:

root@cuiyongchao:~# docker exec -it 1c07fc518fbc bash
root@zhangsan:/# ps axf        
   PID TTY      STAT   TIME COMMAND
    16 pts/1    Ss     0:00 bash
    25 pts/1    R+     0:00  \_ ps axf
     1 pts/0    Ss+    0:00 /bin/bash
root@zhangsan:/# 

​ 而且程序的 PID 不同於 host 中對應程序的 PID,容器中 PID=1 的程序當然也不是 host 的 init 程序。也就是說:容器擁有自己獨立的一套 PID,這就是 PID namespace 提供的功能。

(7)Network namespace

Network namespace 讓容器擁有自己獨立的網絡卡、IP、路由等資源 。

(8)User namespace

User namespace 讓容器能夠管理自己的使用者,host 不能看到容器中建立的使用者。

root@cuiyongchao:~# docker run -it -h zhangsan ubuntu
root@zhangsan:/# 
root@zhangsan:/# userzhangsan333
bash: userzhangsan333: command not found
root@zhangsan:/# useradd zhangsan333
root@zhangsan:/# exit
exit
root@cuiyongchao:~# su - zhangsan333
No passwd entry for user 'zhangsan333'


在容器中建立了使用者 cloudman,但 host 中並不會建立相應的使用者。

(9)小結

本章首先通過大量實驗學習了容器的各種操作以及容器狀態之間如何轉換,然後討論了限制容器使用 CPU、記憶體和 Block IO 的方法,最後學習了實現容器的底層技術:cgroup 和 namespace。

下面是容器的常用操作命令:

create      建立容器  
run         執行容器  
pause       暫停容器  
unpause     取消暫停繼續執行容器  
stop        傳送 SIGTERM 停止容器  
kill        傳送 SIGKILL 快速停止容器  
start       啟動容器  
restart     重啟容器  
attach      attach 到容器啟動程序的終端  
exec        在容器中啟動新程序,通常使用 "-it" 引數  
logs        顯示容器啟動程序的控制檯輸出,用 "-f" 持續列印  
rm          從磁碟中刪除容器