作者:DevOps旭
來自:DevOps探路者
一、容器是什麼
1、傳統虛擬化和容器
在傳統VM時代,我們要解決的核心問題是資源調配。在這一階段,通過HyperV層抽象底層設施資源,提供互相隔離的機制,實現了統一管理、統一配置、計算資源的可運維性和資源利用率,讓一臺物理機能夠同時運行多臺虛擬主機,實現了資源利用率的有效提升。而到了容器時代,可以直接使用宿主機操作系統調度硬件資源,使得資源利用率遠超傳統VM,同時秒級的創建速度,使得核心問題轉變成了應用開發、測試和部署。而docker則是容器時代用戶最多的容器引擎之一。
2、初始docker
docker是由GO語言開發的,基於Linux內核的CGroup和Namespace以及UnionFS技術,實現的對進程進行封裝隔離的虛擬化技術。docker通過虛擬化、共享內核,可以把應用需要的運行環境,緩存環境,數據庫環境等進行封裝,以最簡化的方式運行應用,以追求最佳的性能。但是由於共享內核,所以在安全性和隔離性上遠低於虛擬機。
那麼,docker在解決應用開發測試和部署方面的優勢有哪些呢?
首先、簡化了配置。可以無縫遷移到任何環境中運行,實現了應用運行環境和底層支持環境的解耦。
第二、開發環境生產化,可以使代碼從開發者機器上無縫發佈到測試和生產環境,極大程度上避免了因為環境不一致導致的各種問題出現,這也是雲原生實現的基礎。
第三、作為雲計算的多租戶容器,可以更容易得為每一個租戶創建,運行實例。
第四、快速部署,快速的啟停容器,實現了秒級的系統啟動,為服務實現灰度發佈,滾動發佈奠定了基礎。
第五、進程級別的隔離,大規模微服務集群的最優選擇。
第六,降低了運維成本。
二、深入理解docker的進程隔離
使用容器的同學都知道,容器的基石為namespace和cgroup,那麼namespace和cgroup是什麼呢?又怎麼幫助容器技術實現隔離的呢?那麼我們需要先認識一下namespace和cgroup,嚴格地講,cgroup也是namespace之一,實現的是資源的隔離
那麼,容器技術既然是實現進程級別的隔離,就先談談PID NAMESPACE吧。
1、docker的進程模型
當我們在centos7的主機上啟動了docker之後,查詢進程,可以看到
<code>[root@k8s-master ~]# ps -ef | grep docker root 1022 1 0 8月19 ? 00:00:33 /usr/bin/dockerd root 1198 1022 0 8月19 ? 00:00:03 containerd --config /var/run/docker/containerd/containerd.toml --log-level info root 1924 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/ca3a70c6cab9fd9b25d5f608460b5463a2b83369bc4e1efef2ee1556ed88da15 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 13766 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/661aa0b4186b09bbe1ad986c56515bbe1d5bdd54579b9511d4967f398968166f -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14388 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/1a52099eac9fdaa12a13943d9ca779d4ea1801e9b3566b422c9bae717bd8f42a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14444 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/94350e389f3d6968a43cfe44b86a92d240f1b55345dd12fe5105f04148f7270c -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14515 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/e5d09f22e24935bbff8e0b21a2755ef2fecaadd50c25675a42e55331d0632053 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14622 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/c1ee8c758c8f05671871a198062facdaa33f90683dce561f0c9c0eb9cff050e2 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14686 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/8c6ad25b86a8b9a6462adf863a96fcb1d141efbb5c1440c5089d88fe32410b69 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14882 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/70567de818e599482930c663e7a8546a2eee5d4426c6b35a788463da6c2fa7dc -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 14911 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/6afb44ca8c2c26997de0b4b484bb65b4095396bc961e3a80e2cb0565bd75f04d -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 15106 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/001a68c9f11b5fd91dcd1cbe7c1949b5517be4f69cdfa56e1e1ab7e497610410 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 15287 1198 0 8月19 ? 00:00:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/c9cc60f7bf49b3aa7a6b82a4f20ccff0352558bdcdf52c0aad1d5806d28f2838 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 35432 35286 0 00:32 pts/0 00:00:00 grep --color=auto docker/<code>
可以看到,docker啟動的第一個進程為/usr/bin/dockerd,這個是整個docker服務的入口。而dockerd的子進程docker-containerd,則是docker服務端的核心進程,負責與docker客戶端進行通信交互,fork出docker容器進程。
那麼每個容器內可以啟動幾個進程呢?每個容器內的進程號是不是隔離的?容器內的應用又是怎麼啟動的呢?容器內是否會有殭屍進程呢?針對這些問題,我想先談一下容器內啟動進程的兩種方式:shell和exec。
在shell模式下,1號進程是以/bin/sh -c "command parameter1 parameter2 ..."的方式啟動的。而exec模式下,則是 command parameter1 parameter2 ...的形式啟動1號進程。下面我將構建兩個鏡像,以便更直觀的看到這一現象。
dockerfile_shell
<code>FROM redis:v1 CMD redis-server/<code>
dockerfile_exec
<code>FROM redis:v1 CMD ["redis-server"]/<code>
然後基於以上內容構建鏡像
<code>[root@harbor dockerfile]# docker build -t redis:shell -f dockerfile_shell . Sending build context to Docker daemon 401.9MB Step 1/2 : FROM redis:v1 ---> 6f28a7e0584f Step 2/2 : CMD redis-server ---> Running in 25ab7fc5450f Removing intermediate container 25ab7fc5450f ---> 75fc5d056605 Successfully built 75fc5d056605 Successfully tagged redis:shell [root@harbor dockerfile]# docker build -t redis:exec -f dockerfile_exec . Sending build context to Docker daemon 401.9MB Step 1/2 : FROM redis:v1 ---> 6f28a7e0584f Step 2/2 : CMD ["redis-server"] ---> Running in ad8a324c4b5e Removing intermediate container ad8a324c4b5e ---> 528277f613e8 Successfully built 528277f613e8 Successfully tagged redis:exec/<code>
接下來分別運行兩個鏡像,我們觀察一下進程
<code>[root@harbor dockerfile]# docker ps --no-trunc CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f1d971b25f26084450f8c8a74c818372e11f98337d5c52d54b162ff5c0fe4b7 redis:exec "redis-server" 18 seconds ago Up 17 seconds adoring_dhawan b29e9823bd53be5c569f49393f15aa44ad3f1b1ff4fe7ba03dadd94aae5d272a redis:shell "/bin/sh -c redis-server" 21 seconds ago Up 20 seconds objective_mcnulty/<code>
我們可以看到 shell模式下,COMMAND為"/bin/sh -c redis-server",在容器內又fork出了/usr/bin/tail進程,而exec模式下為 "redis-server"。
此時,我們在宿主機層面關注一下進程,在宿主機上執行一下ps 命令
<code>[root@harbor ~]# ps -ef | grep docker root 1524 1 0 17:09 ? 00:00:41 /usr/bin/dockerd root 1815 1524 0 17:09 ? 00:00:26 containerd --config /var/run/docker/containerd/containerd.toml --log-level info root 4211 1815 0 17:40 ? 00:00:00 containerd-shim -namespace moby -workdir /mnt/docker-lib/containerd/daemon/io.containerd.runtime.v1.linux/moby/b29e9823bd53be5c569f49393f15aa44ad3f1b1ff4fe7ba03dadd94aae5d272a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 4277 1815 0 17:40 ? 00:00:00 containerd-shim -namespace moby -workdir /mnt/docker-lib/containerd/daemon/io.containerd.runtime.v1.linux/moby/9f1d971b25f26084450f8c8a74c818372e11f98337d5c52d54b162ff5c0fe4b7 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 5824 5784 0 20:00 pts/0 00:00:00 grep --color=auto docker [root@harbor ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f1d971b25f2 redis:exec "redis-server" 2 hours ago Up 2 hours adoring_dhawan b29e9823bd53 redis:shell "/bin/sh -c redis-se…" 2 hours ago Up 2 hours objective_mcnulty [root@harbor ~]# ps -ef | grep redis root 4229 4211 0 17:40 pts/0 00:00:08 redis-server *:6379 root 4293 4277 0 17:40 pts/0 00:00:08 redis-server *:6379 root 5832 5784 0 20:00 pts/0 00:00:00 grep --color=auto redis/<code>
我們可以清晰地看到容器9f1d971b25f2在宿主機上的進程為4277,容器b29e9823bd53的進程為4211是,而這也是其中redis的父進程。
但是當我們進入到兩個容器內,執行ps -ef 命令時,可以看到
<code>[root@9f1d971b25f2 /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 09:40 pts/0 00:00:00 redis-server *:6379 root 8 0 0 09:42 pts/1 00:00:00 bash root 21 8 0 09:42 pts/1 00:00:00 ps -ef [root@9f1d971b25f2 /]# exit exit [root@harbor dockerfile]# docker exec -it b29e9823bd53be5c569f49393f15aa44ad3f1b1ff4fe7ba03dadd94aae5d272a bash [root@b29e9823bd53 /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 09:40 pts/0 00:00:00 redis-server *:6379 root 8 0 0 09:42 pts/1 00:00:00 bash root 21 8 0 09:42 pts/1 00:00:00 ps -ef/<code>
可以很直觀的看到,在每個容器內都是獨立計算PID的,這就是docker容器所實現的進程級別的隔離。
2、PID NameSpace
那麼docker是怎麼實現的進程級別的隔離呢?這個是依託於linux內核提供的PID NameSpaced。我們在日常應用中可以發現,當linux在啟動一個進程時,會為進程分配一個唯一的進程號。
當創建一個新的PID NameSpace後,在這個PID NameSpace中進程從1開始計算,當然,這個不是真正的PID=1,下面可以用命令更直觀的看一下
<code># 在宿主機 [root@harbor ~]# ps -ef | grep docker root 1524 1 0 17:09 ? 00:00:41 /usr/bin/dockerd root 1815 1524 0 17:09 ? 00:00:26 containerd --config /var/run/docker/containerd/containerd.toml --log-level info root 4211 1815 0 17:40 ? 00:00:00 containerd-shim -namespace moby -workdir /mnt/docker-lib/containerd/daemon/io.containerd.runtime.v1.linux/moby/b29e9823bd53be5c569f49393f15aa44ad3f1b1ff4fe7ba03dadd94aae5d272a -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 4277 1815 0 17:40 ? 00:00:00 containerd-shim -namespace moby -workdir /mnt/docker-lib/containerd/daemon/io.containerd.runtime.v1.linux/moby/9f1d971b25f26084450f8c8a74c818372e11f98337d5c52d54b162ff5c0fe4b7 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc root 5824 5784 0 20:00 pts/0 00:00:00 grep --color=auto docker [root@harbor ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f1d971b25f2 redis:exec "redis-server" 2 hours ago Up 2 hours adoring_dhawan b29e9823bd53 redis:shell "/bin/sh -c redis-se…" 2 hours ago Up 2 hours objective_mcnulty [root@harbor ~]# ps -ef | grep redis root 4229 4211 0 17:40 pts/0 00:00:08 redis-server *:6379 root 4293 4277 0 17:40 pts/0 00:00:08 redis-server *:6379 root 5832 5784 0 20:00 pts/0 00:00:00 grep --color=auto redis # 在容器內 [root@harbor dockerfile]# docker exec -it b29e9823bd53be5c569f49393f15aa44ad3f1b1ff4fe7ba03dadd94aae5d272a bash [root@b29e9823bd53 /]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 09:40 pts/0 00:00:00 redis-server *:6379 root 8 0 0 09:42 pts/1 00:00:00 bash root 21 8 0 09:42 pts/1 00:00:00 ps -ef/<code>
進程模擬圖如下,可以看出來,在不同的級別的namespace中,分配的PID是不同的,而這也正是docker實現進程級別隔離的基石。