淺析Docker原理

語言: CN / TW / HK

點選藍字,關注我們

背景簡介

伴隨著網際網路的高速發展,雲端計算、雲原生、微服務架構等諸多專業技術在各個網際網路公司被應用的越來越多,目前各大網際網路公司已經在大規模應用微服務架構且傳統行業也逐漸接受了這種架構模式。雲端計算的到來推動雲原生應用服務的落地實踐,得益於雲端計算快速發展,基於雲端計算特性所設計的雲原生應用相比傳統的單體應用在安全性,擴充套件性,快速迭代,運維等各方便都有巨大的領先優勢。

微服務架構使用更細粒度的服務和設計準則推動服務容器化,服務容器化(docker) 將每個微服務變成無狀態服務(佔用埠,檔案佔用等),將軟體容器中的應用程式和程序作為獨立的應用程式部署單元執行,並作為實現高級別資源隔離的機制。

Docker簡介

Docker是基於Go語言實現的雲開源專案,誕生於2013年初,最初發起者是dotCloud公司。Docker自開源後受到廣泛的關注和討論,目前已有多個相關專案,逐漸形成了圍繞Docker的生態體系。dotCloud公司後來改名為Docker Inc,專注於Docker相關技術和產品的開發。Docker是一個開源的容器引擎,能夠將應用程式和基礎設施層隔離,並可以將基礎設施當作程式一樣進行管理。使用Docker容器可更快地打包、測試以及部署應用程式,減少從編寫到部署執行程式碼的週期。

在雲端計算時代,docker容器依然成為了技術熱潮之一,Docker容器的實現原理就是通過Namespace名稱空間實現程序隔離、UnionFilesystem聯合檔案系統實現檔案系統隔離、ControlGroups控制組實現資源隔離。

NameSpace

一個執行中的服務即一個程序,程序提供了服務執行需要的軟硬體環境,在一臺宿主機上同時啟動多個服務時,可能會出現資源的爭奪、程序互相影響等,因此通過Namespace可以將宿主機上同時執行的多個服務劃分成獨立的服務單獨程序執行。

在Linux系統中建立一個新的 Docker 容器,並通過 exec 進入容器的 bash 並查詢容器中的全部程序

[root@bj-sjhl-wx-zhaodexian-dev 14:35:14 /root]
# docker run -it -d centos
0245401a4fa04b010164652e1c28dd83f6cb1963eb1d8b9f258cbb8a2cd8a59e


[root@bj-sjhl-wx-zhaodexian-dev 14:35:24 /root]
# docker exec -it 0245401a4fa0 /bin/bash
[root@0245401a4fa0 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 06:35 pts/0 00:00:00 /bin/bash
root 16 0 0 06:36 pts/1 00:00:00 /bin/bash
root 30 16 0 06:37 pts/1 00:00:00 ps -ef

通過執行資訊可以看到Docker 最開始執行的 /bin/bash,是容器內部的第 1 號程序(PID=1),容器共有三個程序在執行,啟動時執行的 /bin/bash,以及後續進入容器執行命令/bin/bash與ps查詢程序指令。當前容器內的程序與宿主機器中的程序隔離在不同的環境中,容器與宿主機的程序隔離便是採用Linux 系統中的 Namespace 機制實現。

Linux的Namespace機制包含clone_newcgroup、clone_newipc、clone_newnet、clone_newns、clone_newpid、clone_newuser、clone_newuts。程序的隔離採用的clone_newpid來實現。Linux 作業系統還提供了 Mount、UTS、IPC、Network 和 User 等 Namespace,用來對各種不同的程序上下文進行隔離。

#Linux建立程序clone函式
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

呼叫clone() 系統函式建立新程序,指定 CLONE_NEWPID 引數,新建立的程序將會使用一個全新的程序空間,程序空間的程序ID為1。但在宿主機的程序空間裡,新建立的程序 PID 還是真實的數值。當多次執行 clone()函式呼叫,會建立多個 PID Namespace,每個 Namespace 裡的應用程序都是當前容器裡的第 1 號程序,既看不到宿主機裡真正的程序空間,也看不到其他 PID Namespace 的具體資訊。

建立docker容器時(執行docker run/docker start命令)通過createSpec方法設定程序間隔離的Spec,在setNamespaces方法傳入程序、使用者、網路等引數,實現與宿主機與容器的程序、使用者、網路等隔離。

func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s := oci.DefaultSpec()


// ...
if err := setNamespaces(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux spec namespaces: %v", err)
}


return &s, nil
}


//通過setNamespaces 方法設定程序、使用者、網路、IPC 以及 UTS 相關的名稱空間
func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {

if c.HostConfig.PidMode.IsContainer() {
ns := specs.LinuxNamespace{Type: "pid"}
pc, err := daemon.getPidContainer(c)
if err != nil {
return err
}
ns.Path = fmt.Sprintf("/proc/%d/ns/pid", pc.State.GetPID())
setNamespace(s, ns)
} else if c.HostConfig.PidMode.IsHost() {
oci.RemoveNamespace(s, specs.LinuxNamespaceType("pid"))
} else {
ns := specs.LinuxNamespace{Type: "pid"}
setNamespace(s, ns)
}


return nil
}

ControlGroups

Linux ControlGroups 的全稱是 Linux Control Group,主要作用是限制一個程序組能夠使用的資源上限,包括 CPU、記憶體、磁碟、網路頻寬等。此外,Cgroups 還能夠對程序進行優先順序設定、審計,以及將程序掛起和恢復等操作。

在 Linux 中Cgroups 給使用者暴露出來的操作介面是檔案系統,以檔案和目錄的方式組織在作業系統的 /sys/fs/cgroup 路徑下。

[root@bj-sjhl-wx-zhaodexian-dev 14:28:31 /sys/fs/cgroup/cpu]
# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

Cgroups 的每一個子系統都有獨有的資源限制能力

 blkio   -- 為塊裝置設定輸入/輸出限制,比如物理裝置(磁碟,固態硬碟,USB 等等)。
cpu -- 使用排程程式提供對 CPU 的 cgroup 任務訪問。
cpuacct -- 自動生成 cgroup 中任務所使用的 CPU 報告。
cpuset -- 為 cgroup 中的任務分配獨立 CPU(在多核系統)和記憶體節點。
devices -- 可允許或者拒絕 cgroup 中的任務訪問裝置。
freezer -- 掛起或者恢復 cgroup 中的任務。
memory -- 設定 cgroup 中任務使用的記憶體限制,並自動生成由那些任務使用的記憶體資源報告。
net_cls -- 使用等級識別符(classid)標記網路資料包,可允許 Linux 流量控制程式(tc)識別從具體 cgroup 中生成的資料包。
ns -- 名稱空間子系統。

在/sys/fs/cgroup 目錄下有很多 cpuset、cpu、 memory 等子目錄(子系統),是當前主機可以被 Cgroups 進行限制的資源種類,在子系統對應的資源種類下,可以看到該類資源具體可以被限制的方法。

對 CPU 子系統來說,可以看到如下幾個配置檔案

[root@bj-sjhl-wx-zhaodexian-dev 14:28:03 /sys/fs/cgroup/cpu]
# cd /sys/fs/cgroup/cpu


[root@bj-sjhl-wx-zhaodexian-dev 14:28:10 /sys/fs/cgroup/cpu]
# ll
總用量 0
-rw-r--r-- 1 root root 0 1月 11 2021 cgroup.clone_children
--w--w--w- 1 root root 0 1月 11 2021 cgroup.event_control
-rw-r--r-- 1 root root 0 1月 11 2021 cgroup.procs
-r--r--r-- 1 root root 0 1月 11 2021 cgroup.sane_behavior
drwxr-xr-x 2 root root 0 3月 15 14:26 container
-r--r--r-- 1 root root 0 1月 11 2021 cpuacct.stat
-rw-r--r-- 1 root root 0 1月 11 2021 cpuacct.usage
-r--r--r-- 1 root root 0 1月 11 2021 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.rt_period_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.shares
-r--r--r-- 1 root root 0 1月 11 2021 cpu.stat
drwxr-xr-x 2 root root 0 3月 15 11:44 docker
-rw-r--r-- 1 root root 0 1月 11 2021 notify_on_release
-rw-r--r-- 1 root root 0 1月 11 2021 release_agent
drwxr-xr-x 60 root root 0 3月 14 15:54 system.slice
-rw-r--r-- 1 root root 0 1月 11 2021 tasks
drwxr-xr-x 2 root root 0 3月 10 18:40 user.slice

配置檔案基礎配置使用

在 /sys/fs/cgroup/cpu 目錄下建立名稱為container目錄,作業系統會在新建立的 container 目錄下自動生成該子系統對應的資源限制檔案,目錄container被稱為一個“控制組”。

# cd /sys/fs/cgroup/cpu


[root@bj-sjhl-wx-zhaodexian-dev 14:25:51 /sys/fs/cgroup/cpu]
# mkdir container


[root@bj-sjhl-wx-zhaodexian-dev 14:26:05 /sys/fs/cgroup/cpu]
# ll
總用量 0
-rw-r--r-- 1 root root 0 1月 11 2021 cgroup.clone_children
--w--w--w- 1 root root 0 1月 11 2021 cgroup.event_control
-rw-r--r-- 1 root root 0 1月 11 2021 cgroup.procs
-r--r--r-- 1 root root 0 1月 11 2021 cgroup.sane_behavior
drwxr-xr-x 2 root root 0 3月 15 14:26 container
-r--r--r-- 1 root root 0 1月 11 2021 cpuacct.stat
-rw-r--r-- 1 root root 0 1月 11 2021 cpuacct.usage
-r--r--r-- 1 root root 0 1月 11 2021 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.rt_period_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 1月 11 2021 cpu.shares
-r--r--r-- 1 root root 0 1月 11 2021 cpu.stat
drwxr-xr-x 2 root root 0 3月 15 11:44 docker
-rw-r--r-- 1 root root 0 1月 11 2021 notify_on_release
-rw-r--r-- 1 root root 0 1月 11 2021 release_agent
drwxr-xr-x 60 root root 0 3月 14 15:54 system.slice
-rw-r--r-- 1 root root 0 1月 11 2021 tasks
drwxr-xr-x 2 root root 0 3月 10 18:40 user.slice


[root@bj-sjhl-wx-zhaodexian-dev 14:26:07 /sys/fs/cgroup/cpu]
# ls container/
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

檢視container 控制組 CPU quota 限制(預設 -1),CPU period 預設 100 ms(100000 us)

[root@bj-sjhl-wx-zhaodexian-dev 14:28:37 /sys/fs/cgroup/cpu]
# cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
-1


[root@bj-sjhl-wx-zhaodexian-dev 16:02:34 /sys/fs/cgroup/cpu]
# cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000

限制container控制組程序每 100 ms 的時間只能使用 20 ms 的 CPU 時間,該控制組指定程序只能使用到 20% 的 CPU 頻寬。將限制程序 PID 寫入 container 組裡的 tasks 檔案,設定就會對限制程序生效。

[root@bj-sjhl-wx-zhaodexian-dev 14:26:07 /sys/fs/cgroup/cpu]
# echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us


[root@bj-sjhl-wx-zhaodexian-dev 14:26:07 /sys/fs/cgroup/cpu]
# echo 限制容器程序PID > /sys/fs/cgroup/cpu/container/tasks

 Linux Cgroups 的設計是一個子系統目錄加上一組資源限制檔案的組合。對於 Docker 容器專案資源限制需要在每個子系統下面,為每個容器建立一個控制組(建立一個新目錄),在啟動容器程序之後,將啟動後容器的程序 PID 寫入到對應控制組的 tasks 檔案中使其生效。

UnionFS(Union File System)

Linux 的NameSpace和ControlGroups分別解決了容器資源隔離的問題,NameSpace實現了程序、網路以及檔案系統的隔離,ControlGroups實現了 CPU、記憶體等資源隔離。docker通過Mount NameSpace在容器根目錄下掛載了一個完整的作業系統的檔案系統,用來為容器程序提供隔離後執行環境的檔案系統(rootfs),稱之為“容器映象”。

容器映象把應用程式執行需要的作業系統環境、開發語言的依賴庫、系統lib庫等都打包在一起,作為容器啟動時的根目錄,容器程序的各種依賴呼叫都在這個目錄裡來保障環境的一致性!為了解決在開發新應用或升級應用重複製作rootfs的問題,Docker 在映象的設計中,通過AUFS(Advanced UnionFS聯合檔案系統) 將多個目錄聯合放在同一個目錄下,使用者製作映象的每一步操作,都會生成一個層(docker-layer),也就是一個增量 rootfs。

除了 AUFS 之外,Docker 還支援了不同的儲存驅動,包括aufs、devicemapper、overlay2、zfs、vfs 等等,最新的 Docker 版本中overlay2 取代了aufs 成為推薦的儲存驅動,但是在沒有overlay2 驅動的機器上仍然使用aufs 作為 Docker 的預設驅動。

映象layer-case (centos系統,儲存驅動預設使用overlay2)

[root@bj-sjhl-wx-zhaodexian-dev 20:52:22 /etc/yum.repos.d]
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
apisix-gateway v1 ccee202f7e5c 3 days ago 560MB
hub.xesv5.com/jichufuwuzhongtai-gateway/apisix v2.13LTS-debian 7994600d7480 5 weeks ago 556MB
php latest 13b9b1961ba3 5 months ago 484MB
ubuntu latest ba6acccedd29 7 months ago 72.8MB
centos latest 5d0da3dc9764 8 months ago 231MB


[root@bj-sjhl-wx-zhaodexian-dev 20:50:13 /etc/yum.repos.d]
# docker image inspect hub.xesv5.com/jichufuwuzhongtai-gateway/apisix:v2.13LTS-debian
[
{
"Id": "sha256:7994600d74804a0ceaa887c4e0448797fb4025525caf9aef6821dea9e4025293",
...此處省略.....
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/0f14ca702b16a873c24a80a45ec9788e953389cde39632f8c37adcaa3c6b3ac8/diff:/var/lib/docker/overlay2/677462ba9980933de7e8c5a5225a8a133d2c8d6fca2156062261bc6a9ecbd834/diff:/var/lib/docker/overlay2/d3a657cc43f789351c1ac78974d3d8673b72a5a10684d5596cf5ae2ae5aaa379/diff:/var/lib/docker/overlay2/9809863ba3765112c90799c48bf42f4210c8a3ef74b9acda099a5135f316918f/diff:/var/lib/docker/overlay2/3542427fdd62a49e9e56512a4991a473f36e9c77d3aa4ecba30c26075eac7a16/diff:/var/lib/docker/overlay2/2274937f56770ffeff99369831e98c0497ad2da243cdd3f531e21a39498c9fba/diff:/var/lib/docker/overlay2/058a61f219d2d834a449211999a9dfd05f05fc722166f1e9b7bf0f0e8cc78f91/diff:/var/lib/docker/overlay2/32e8f62aa176dcf79e24252b3e654e95847ce30bd3e929d555ce8558a3b6797a/diff:/var/lib/docker/overlay2/10e0913c3b56e5651d27964890d88d7f6f095ef44293154834ffd7810402d1a0/diff:/var/lib/docker/overlay2/c78bca04d5294fd81939a3e25a77bc70b126ddb46578e27c11bde3662a5e8f49/diff:/var/lib/docker/overlay2/1901543e8a7c5b84bf37468d384769b2b34db760545f924d321aaf848e785557/diff:/var/lib/docker/overlay2/8be8fd781793f8f62b086d4ecf7ce91a9ef30d1cbb95f42610cf4d80ab83a268/diff:/var/lib/docker/overlay2/988dfd6db4c52ff1ea75a5a02a47d473290a73f460ee2cfdda79115f8167e5ad/diff:/var/lib/docker/overlay2/55a3a96d0b8a2e7ced30c08ff625a1b21901ea8afd6fe86aca8523d96f36f43f/diff:/var/lib/docker/overlay2/4c640d88cc3f7d12ae2cb67e98fe4d592040760c853dc66d191ab5cc1c9e90ce/diff",
"MergedDir": "/var/lib/docker/overlay2/892f3278b5c80a53246a520391f8a2ba430f033fc88377b70646ba1d965ccff2/merged",
"UpperDir": "/var/lib/docker/overlay2/892f3278b5c80a53246a520391f8a2ba430f033fc88377b70646ba1d965ccff2/diff",
"WorkDir": "/var/lib/docker/overlay2/892f3278b5c80a53246a520391f8a2ba430f033fc88377b70646ba1d965ccff2/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c",
"sha256:061975be64bd3ac4dd8d47533f54ce6df3f674c16183c3785f46400c02507a83",
"sha256:603a5c8ab158ecab625b156cc701830bb7e8d56a046bec80aa9fa1dd58f10fbf",
"sha256:7ebc3f7a0003ebfbe310a3c6ec73feb5f8d09d48810584962589c55d287bfd1d",
"sha256:139fdf7f2bf100085a3e18bf164d48f6812fc811f0325df0d74bd0ed1d83efe5",
"sha256:438c96d1e69e86680a87a921edf299452126fb778d18ec63d0440ebe0ce513dd",
"sha256:e5c66ac0d1d846cbb37ad7ccc3d532aa67318fb80c79f17df9bcf635b339c5b0",
"sha256:18eb7539f7d48270ff4dd718d740d8309e695dbcf5ead342646c4ec979cc4b93",
"sha256:771c099341aad291c5689c42445791d52c178fa7fe42e5e971205b235ec9f509",
"sha256:e37a609d752f39b3823c0dc3671321eae5a714a80e31ad7f1b7445e9780c5848",
"sha256:1bc81241de68e079ef79c07b4bd61f84ef9f579f319bf6d4ba09a686637c8a79",
"sha256:4e9cb0e4df589906b83eb180803c373711599730319d0753c9087e3cbbf3cbe9",
"sha256:50eef979fa5748f32fe931c80f1be92acce33193da0f37d5f0d4ee72b3ab6110",
"sha256:e7107e8e00aa9bc0a5f66e60dd723555ec4cd50860d3dc2919fcbc3ac34d6b44",
"sha256:69b5fa6d43662d2caa03a122569c863ec625ea807d18730b904af2dbcae7320d",
"sha256:8b6f991cc6571a3815d53b0b5f40e4964eaefb52e092464ee19c21b0a48e6247"
]
}
}
]

通過以上執行結果RootFS/Layers可以看到apisix-gateway映象由16個層組成,每一層都是一個增量 rootfs,在使用映象時,Docker 會把這些增量聯合掛載在一個統一的掛載點上。

容器映象的組裝過程,每一個映象層都是建立在另一個映象層之上,所有的映象層都是隻讀的,只有每個容器最頂層的容器層才可以被使用者直接讀寫,所有的容器都建立在底層服務(Kernel)上,包括名稱空間、控制組、rootfs 等。

掃描下方二維碼新增 「好未來技術」 微信官方賬號

進入好未來技術官方交流群與作者實時互動~

(若掃碼無效,可通過微訊號 TAL-111111 直接新增)

- 也許你還想看 -

給非前端夥伴的前端知識學習引導

關於冪等設計

Python併發程式設計入門

基於雙模檢測的通話錄音質檢解決方案

我知道你“在看”喲!