推薦學習
在現代的開發流程中隨處可見 Docker 的身影,Docker 提供了環境隔離、應用打包等功能讓服務部署變得特別簡單,本文將會淺析 Docker 背後所使用的技術,閱讀完後,你可以搞清楚如下問題:
- 1. 容器與虛擬機之間的差別
- 2.Docker 資源隔離的原理
- 3.Docker 資源限制的原理
- 4.Docker 分層結構的原理
容器 vs 虛擬機
虛擬機(VM)是計算機系統的仿真器,通過軟件模擬具有完整硬件系統功能的、運行在一個完全隔離環境中的完整計算機系統,能提供物理計算機的功能。
虛擬機通過在當前的真實操作系統上通過 Hypervisor 技術進行虛擬機運行環境與體系的建立並通過該技術進行資源控制,一個性能較好的物理機通常可以承載多個虛擬機,每個虛擬機都會有自己操作系統,如圖 1.1 所示。
從圖中可以看出,虛擬機提供了物理機硬件級別的操作系統隔離,這讓不同虛擬機之間的隔離很徹底,但也需要消耗更多資源,而有時不需要這麼徹底的隔離,而更希望不消耗那麼多資源,此時就可以使用容器技術。
容器可以提供操作系統級別的進程隔離,以 Docker 為例,當我們運行 Docker 容器時,此時容器本身只是操作系統中的一個進程,只是利用操作系統提供的各種功能實現了進程間網絡、空間、權限等隔離,讓多個 Docker 容器進程相互不知道彼此的存在,如圖 1.2 所示。
虛擬機技術與容器技術的最大區別在於:多個虛擬機使用多個操作系統內核,而多個容器共享宿主機操作系統內核。
Docker 資源隔離:Linux Namespace
Linux Namespace(Linux 命名空間)是 Linux 內核(Kernel)提供的功能,它可以隔離一系列的系統資源,如 PID(進程 ID,Process ID)、User ID、Network、文件系統等。
如果你熟悉 Linux,你可能會聯想到 linux 中的 chroot 命令,該命令允許將當前目錄修改成根目錄(即根目錄 / 的掛載點切換了),相當於文件系統被隔離了,Namespace 也具有相似的功能,但更加強大。
目前 Linux 主要提供 6 種不同類型的 Namespace,如下表所示。
以一個具體的例子來解釋 Namespace 的作用,假設你有一臺性能非常好的計算機,你向用戶出售自己的計算機的資源,每個用戶買到一個 ssh 實例,為了避免不同客戶之間相互干擾,你可能會對不同用戶進行權限限制,讓用戶只能訪問自己 ssh 實例下的資源。
但有些操作需要 root 權限,而我們不能將 root 權限提供給用戶,此時就可以使用 Namespae 了,通過 User Namespace 對 UID 進行隔離,具體而言,UID 為 x 的用戶在該 Namespace 中具有 root 權限,但在真實物理機中,他依舊是 UID 為 x 的用戶,這就解決了用戶間隔離的問題。
此外還可以通過 PID Namespace 對 PID 進行隔離,從該 Namespace 中的用戶角度看,Namespace 中就像一臺新的 Linux,有自己的 init 進程(初始進程,PID 為 1),其他進程的 PID 在 init 進程 PID 上遞增,如圖 1.3 所示。
圖中,進程 3 在父命名空就中 PID 為 3,而在子命名空間中,其 PID 為 1,用戶在該子命名空間中內看進程 3 就像 init 進程一樣。
Linux 提供了 3 個系統 API 方便我們使用 Namespace:
- clone () 創建新進程,根據系統調用 flags 來決定哪種類型 Namespace 將會被創建,而該進程的子進程也會包含這些 Namespace。
- setns () 將進程加入到已存在的 Namespace 中。
- unshare () 將進程移出某個 Namespace
Docker 利用 Linux Namespace 功能實現多個 Docker 容器相互隔離,具有獨立環境的功能,Go 語言對 Namespce API 進行了相應的封裝,從 Docker 源碼中可以看到相關的實現。
Docker 資源限制:Linux Cgroups
Docker 通過 Linux Namespace 幫進程隔離出自己單獨的空間 / 資源,那 Docker 如何限制進程對這些資源的使用呢?
Docker 容器本質依舊是一個進程,多個 Docker 容器運行時,如果其中一個 Docker 進程佔用大量 CPU 和內存就會導致其他 Docker 進程響應緩慢,為了避免這種情況,可以通過 Linux Cgroups 技術對資源進行限制。
Linux Cgroups(Linux Contorl Groups,簡稱 Cgroups)可以對一組進程及這些進程的子進程進行資源限制、控制和統計的能力,其中包括 CPU、內存、存儲、網絡、設備訪問權限等,通過 Cgroups 可以很輕鬆的限制某個進程的資源佔用並且統計該進程的實時使用情況。
Cgroups 由 3 個組件構成,分別是 cgroup(控制組)、subsystem(子系統)以及 hierarchy(層級樹),3 者相互協同作用。
- cgroup 是對進程分組管理的一種機制,一個 cgroup 通常包含一組(多個)進程,Cgroups 中的資源控制都以 cgroup 為單位實現。
- subsystem 是一組(多個)資源控制的模塊,每個 subsystem 會管理到某個 cgroup 上,對該 cgroup 中的進程做出相應的限制和控制。
- hierarchy 會將一組(多個)cgroup 構建成一個樹狀結構,Cgropus 可以利用該結構實現繼承等功能
3 這具體如何相互協同作用?
Cgroups 會將系統進程分組(cgroup)然後通過 hierachy 構建成獨立的樹,樹的節點就是 cgroup(進程組),每顆樹都可以與一個或多個 subsystem 關聯,subsystem 會對樹中對應的組進行操作。
有個幾個規則需要注意。
1. 一個 subsystem 只能附加到一個 hierarchy,而一個 hierarchy 可以附加多個 subsystem 2. 一個進程可以作為多個 cgroup 的成員,但這些 cgroup 只能在不同的 hierarchy 中 3. 一個進程 fork 出子進程,此時子進程與父進程默認是在同一個 cgroup 中,可以根據需要移動到其他 cgroup
Docker 分層結構:Union File System
我們都知道 Docker 鏡像是一種分層結構,每一層構建在其他層之上,從而實現增量增加內容的功能,這是如何實現的?
要理解這個問題,首先需要理解 Union File System(簡稱,UnionFS),它是為 Linux 系統設計的將其他文件系統聯合到一個聯合掛載點的文件系統服務。UnionFS 使用 branch(分支)將不同文件系統的文件和目錄透明地疊加覆蓋,形成一個單一一致的文件系統,此外 UnionFS 使用寫時複製(Copy on Write,簡稱,CoW)技術來提高合併後文件系統的資源利用。(後續的文章會介紹 CoW 技術)
Docker 使用的第一種存儲驅動為 AUFS(Advanced Multi-layered unification filesytem),AUFS 完全重寫了早期的 UnionFS,目的是提高其性能與可靠性,此外還引入瞭如 branch 負載均衡等新功能。
與 UnionFS 類似,AUFS 可以在基礎的文件系統上增量的增加新的文件系統,通過疊加覆蓋的形式最終形成一個文件系統。通常 AUFS 最上層是可讀可寫層,而其他層只是只讀層,每一層都只是一個普通的文件系統。
Docker 鏡像分層、增量增加等功能正是通過利用 AUFS 的分層文件系統結構、增量增加等功能實現,這也導致了運行 Docker 容器如果沒有指定 volume(數據卷)或 bind mount,則 Docker 容器結束後,運行時產生的數據便丟失了。
Docker 存儲驅動除了 AUFS 外,還有 OverlayFS、Devicemapper、Btrfs、ZFS 等,本文不多討論。
總結
至此,我們知道了 Docker 核心功能的基本原理,Docker 利用 Linux Namespace 進行網絡、用戶、進程等不同資源的隔離,使用 Linux Cgroups 技術對資源的使用進行限制與監控,通過 AUFS 等存儲驅動實現分層結構與增量更新等功能。
現實世界中的 Docker 還使用了很多其他技術,但最核心且最基本的就是 Linux Namespace、Linux Cgrpus 與 AUFS。
Docker 在當前的開發流程中已成必備工具,容器帶來的優勢解放了運維人員也避免了開發人員遇到開發環境與線上環境不一致時導致問題的情況。目前容器編排技術(K8s)快速發展,Docker 容器技術在未來也將會進一步發展,它值得我們花時間與精力去了解其本質。
作者:hackpython二兩
原文鏈接:
https://juejin.im/post/5ea844225188256d657b3b27