容器技術的核心在於利用 Linux cgroups 和名稱空間實作資源隔離和程式管理。cgroups v1 和 v2 的差異主要在於控制器層級結構的組織方式,v2 提供了統一的結構,簡化了資源管理。容器映像作為檔案系統包,透過映像倉函式庫進行儲存和分發,並透過 Dockerfile 定義構建過程。OverlayFS 提供了多層檔案系統的疊加機制,實作了映像的不可變性和高效儲存。容器引擎負責協調容器的建立和執行,而容器執行時則負責底層的資源管理和程式執行。容器與虛擬機器的關鍵區別在於核心共用,容器直接與主機核心互動,而虛擬機器則擁有獨立的核心。這使得容器在啟動速度和資源利用率方面更具優勢,但也需要額外的安全考量。
容器技術簡介
控制器的組織層級隨著時間的演變,目前有兩個版本,V1和V2。在cgroups V1中,不同的控制器可以掛載到不同的層級結構中。相反,cgroups V2提供了一個統一的控制器層級結構,程式位於樹的葉子節點。
容器使用cgroups來限制CPU或記憶體的使用。例如,使用者可以限制CPU配額,這意味著在給定的時間內限制容器可以使用CPU的微秒數,或者限制CPU分享,即每個容器的CPU週期的加權比例。
現在我們已經闡述了程式隔離的工作原理(對於名稱空間和資源),我們可以舉幾個基本的例子。
執行隔離程式
一個有用的事實是,GNU/Linux作業系統提供了執行容器所需的所有功能,可以透過特定的系統呼叫(尤其是unshare()和clone())和實用程式(如unshare命令)來實作。
例如,要在隔離的PID名稱空間中執行程式,比如/bin/sh,使用者可以依賴unshare命令:
# unshare --fork --pid --mount-proc /bin/sh
結果是在隔離的PID名稱空間中執行新的shell程式。使用者可以嘗試監視程式檢視,並將獲得如下輸出:
sh-5.0# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 226164 4012 pts/4 S 22:56 0:00 /bin/sh
root 4 0.0 0.0 227968 3484 pts/4 R+ 22:56 0:00 ps aux
有趣的是,前面的示例中的shell程式正在以PID 1執行,這是正確的,因為它是新的隔離名稱空間中執行的第一個程式。
不過,PID名稱空間將是唯一被抽象的,而所有其他系統資源仍然保持原始主機的資源。如果我們想要新增更多的隔離,例如在網路堆積疊上,我們可以在前面的命令中新增--net標誌:
# unshare --fork --net --pid --mount-proc /bin/sh
結果是在PID和網路名稱空間上都隔離的shell程式。使用者可以檢查網路IP組態,並意識到主機本機裝置不再直接被未分享的程式看到:
sh-5.0# ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
前面的例子有助於理解一個非常重要的概念:容器與Linux本機功能密切相關。作業系統提供了一個堅實且完整的介面,有助於容器執行時的開發,並且隔離名稱空間和資源的能力是解鎖容器採用的關鍵。容器執行時的作用是抽象底層隔離機制的複雜性,其中掛載點隔離可能是最關鍵的。因此,它值得更好的解釋。
隔離掛載點
到目前為止,我們已經看到了一些未分享的例子,它們並沒有影響掛載點和程式端的檔案系統檢視。要獲得防止二進位和函式庫衝突的檔案系統隔離,使用者需要為暴露的掛載點建立另一個抽象層。
這是透過利用掛載名稱空間和繫結掛載來實作的。首先在2002年與Linux核心2.4.19一起引入,掛載名稱空間隔離了程式所看到的掛載點列表。每個掛載名稱空間都暴露了一個離散的掛載點列表,從而使不同名稱空間中的程式意識到不同的目錄層次結構。
透過這種技術,可以向正在執行的程式暴露一個替代的目錄樹,其中包含所有必要的二進位和函式庫。
儘管看起來是一項簡單的任務,但掛載名稱空間的管理卻並非簡單易行。例如,使用者應該處理來自不同發行版的不同版本的目錄樹,提取它們,並在單獨的名稱空間上繫結掛載。我們稍後將看到Linux中容器的第一種方法就是遵循這種方法。
容器成功的關鍵還在於一種創新的、多層次的、寫時複製的方法來管理目錄樹,這種方法引入了一種簡單快速的方法來複製、佈署和使用執行容器所需的樹狀結構——容器映像。
內容解密:
此段落描述瞭如何使用Linux的核心功能來實作容器的基本功能,包括使用unshare命令建立隔離的PID和網路名稱空間,以及如何使用掛載名稱空間和繫結掛載來實作檔案系統的隔離。這些技術是容器技術的基礎,並且是實作容器執行時的關鍵。
容器映像的重要性
我們要感謝Docker引入了這種聰明的方法來儲存容器的資料。後來,映像成為了Open Container Initiative(OCI)標準規範(https://github.com/opencontainers/image-spec)。
映像可以被視為一個檔案系統包,在第一次執行容器之前下載(提取)並解封裝到主機上。
映像是從稱為映像倉函式庫的倉函式庫中下載的。這些倉函式庫可以被視為專門的物件儲存,它們儲存映像資料和相關的中繼資料。有公共且免費使用的倉函式庫(如quay.io或docker.io),也有可以在客戶私有基礎設施、本地或雲中執行的私有倉函式庫。
映像可以由DevOps團隊構建,以滿足特殊需求或嵌入必須佈署和執行在主機上的工件。
在映像構建過程中,開發人員可以注入預先構建的工件或原始碼,然後在構建容器中編譯它們。為了最佳化映像大小,可以建立多階段構建,第一階段使用具有必要編譯器和執行時的基礎映像編譯原始碼,第二階段將構建的工件注入到最小、最輕量的映像中,以實作快速啟動和最小儲存佔用。
構建過程的配方是在一個特殊的文字檔中定義的,稱為Dockerfile,它定義了組裝最終映像所需的所有步驟。
構建完成後,使用者可以將自己的映像推播到公共或私有倉函式庫,以便稍後使用或進行複雜、協調的佈署。
下圖總結了構建工作流程: 此圖示描述了從編寫Dockerfile到執行容器的整個流程。
內容解密:
此段落描述了容器映像的重要性和其工作流程,包括如何構建、推播和提取映像,以及如何使用Dockerfile定義構建過程。容器映像是一種聰明的方法來儲存容器的資料,並且已經成為OCI標準規範。它們可以被視為一個檔案系統包,並且可以用於建立可移植和可重複的容器佈署。圖表清晰地展示了構建工作流程,使讀者能夠更好地理解整個過程。
容器技術簡介
根據核心檔案的說明,「Overlay filesystem 將兩個檔案系統結合起來 - 一個 ‘upper’ 檔案系統和一個 ’lower’ 檔案系統。」這意味著它可以結合多個目錄樹,並提供一個獨特的、壓縮的檢視。這些目錄被稱為層,分別被稱為 lowerdir 和 upperdir,用於定義底層目錄和疊加在其上的目錄。統一的檢視被稱為 merged。它最多支援 128 層。
OverlayFS 並不瞭解容器映像的概念;它僅被用作實作 OCI 映像所使用的多層解決方案的基礎技術。OCI 映像也實作了不可變性的概念。映像的層都是唯讀的,不能被修改。更改底層的唯一方法是重建映像並進行適當的更改。
不可變性是雲端運算方法的重要支柱。它只是意味著基礎架構(如例項、容器或甚至複雜的叢集)只能被不同的版本替換,而不能被修改以達到目標佈署。因此,我們通常不會更改正在執行的容器內的任何內容(例如,手動安裝套件或更新組態檔案),即使在某些情況下這是可能的。相反,我們用新的更新版本替換其基礎映像。這也確保了正在執行的容器的每個副本都保持與其他副本同步。
當容器被執行時,會在映像的頂部建立一個新的讀寫薄層。該層是短暫的,因此對其頂部的任何更改都將在容器被銷毀後丟失。
容器的層
此圖示展示了容器的層結構。
圖示說明:
- 基礎映像層(唯讀)
- 上層(讀寫)
這導致了另一個重要的陳述:我們不將任何資料儲存在容器內。容器的唯一目的是為我們的應用程式提供一個工作且一致的執行環境。資料必須透過在容器內使用繫結掛載或網路儲存(如 NFS、S3、iSCSI 等)來外部存取。
安全考量
從安全的角度來看,有一個殘酷的事實需要分享:如果一個程式在容器內執行,並不意味著它比其他程式更安全。
惡意的攻擊者仍然可以透過主機檔案系統和記憶體資源進行攻擊。為了實作更好的安全隔離,可以使用額外的功能:
- 強制存取控制:可以使用 SELinux 或 AppArmor 來強制容器與父主機之間的隔離。這些子系統及其相關的命令列工具使用根據策略的方法來更好地隔離正在執行的程式的檔案系統和網路存取。
- 功能:當系統中執行一個非特權程式(即有效 UID 非 0 的程式)時,它會受到根據程式憑證(其有效 UID)的許可檢查。這些許可或特權被稱為功能,可以獨立啟用,賦予非特權程式有限的特權許可來存取特定的資源。在執行容器時,我們可以新增或刪除功能。
- 安全計算模式(Seccomp):這是一個原生的核心功能,可以用來限制程式從使用者空間到核心空間的系統呼叫。透過識別程式執行所需的嚴格必要的特權,管理員可以應用 seccomp 組態檔來限制攻擊面。
手動應用上述安全功能並不總是容易和立即的,因為其中一些需要淺層的學習曲線。以自動化和簡化(可能以宣告式方式)這些安全約束的工具提供了很高的價值。
容器引擎與執行時
儘管從學習的角度來看,手動執行和保護容器是可行且特別有用的,但這是一種不可靠且複雜的方法。在生產環境中很難重現和自動化,並且很容易導致不同主機之間的組態偏差。
這就是容器引擎和執行時誕生的原因 - 為了幫助自動化容器的建立以及所有相關任務,最終實作容器的執行。兩個概念非常不同,經常被混淆,因此需要澄清:
- 容器引擎是一種軟體工具,接受並處理使用者建立容器的請求,包括所有必要的引數。它可以被視為一種協調器,因為它負責執行所有必要的操作,以使容器啟動並執行;但它不是容器的實際執行者(這是容器執行時的工作)。
- 引擎通常解決以下問題:
- 提供命令列和/或 REST 介面供使用者互動
- 提取和提取容器映像
- 管理容器掛載點和繫結掛載提取的映像
- 處理容器後設資料
- 與容器執行時互動
我們已經提到,當新的容器被例項化時,會在映像的頂部建立一個薄的 R/W 層。這項任務由容器引擎完成,它負責向容器執行時呈現合併目錄的工作堆積疊。
容器生態系統提供了多種容器引擎的選擇。Docker 無疑是最著名的引擎實作之一,其他著名的實作包括 Podman、CRI-O、rkt 和 LXD。
容器執行時
容器執行時是主機中用於執行容器的低階軟體。它提供以下功能:
- 在目標掛載點(通常由容器引擎提供)啟動容器化程式,並帶有一組自定義後設資料
- 管理 cgroups 的資源分配
- 管理強制存取控制策略(SELinux 和 AppArmor)和功能
目前有多種容器執行時,大多數實作了 OCI 執行時規範參考(https://github.com/opencontainers/runtime-spec)。這是一個業界標準,定義了執行時應該如何行為以及應該實作的介面。
最常見的 OCI 執行時是 runc,被大多數著名的引擎使用,其他實作包括 crun、kata-containers、railcar、rkt 和 gVisor。
這種模組化方法允許容器引擎根據需要交換容器執行時。例如,當 Fedora 33 發布時,它引入了一個新的預設 cgroups 階層,稱為 cgroups V2。runc 在一開始並不支援 cgroups V2,而 Podman 簡單地將 runc 更換為另一個已經符合新階層的 OCI 相容容器執行時(crun)。現在 runc 終於支援 cgroups V2,Podman 將能夠安全地再次使用它,而不會對終端使用者產生影響。
容器與虛擬機器的比較
在介紹了容器執行時和引擎之後,是時候回答在容器介紹中最常被問到的問題之一:容器和虛擬機器之間的區別。
到目前為止,我們已經討論了透過原生 OS 功能實作的隔離,並透過容器引擎和執行時增強了隔離。許多使用者可能會誤以為容器是一種虛擬化。
事實上,容器並不是虛擬機器。那麼,容器和虛擬機器之間的主要區別是什麼?在回答之前,我們可以檢視下圖:
此圖示展示了從容器到核心的系統呼叫。
圖示說明:
- 展示了容器的系統呼叫流程
- 說明瞭容器與主機核心之間的互動關係
程式碼範例:建立一個簡單的容器
# 使用 Docker 建立一個簡單的 Nginx 容器
docker run -d --name mynginx nginx:latest
內容解密:
docker run:Docker 命令,用於建立並執行一個新的容器。-d:後台執行容器的引數。--name mynginx:為新建立的容器指定名稱為mynginx。nginx:latest:指定要使用的 Docker 映像名稱及其標籤,這裡使用的是最新版本的 Nginx 官方映像。
此命令會從 Docker Hub 提取 nginx:latest 映像,並在後台執行一個名為 mynginx 的新容器,提供 Nginx 服務。
容器技術介紹
容器技術是一種輕量級的虛擬化技術,儘管容器是隔離的,但它仍然與主機共用同一個核心(kernel)。容器內的程式直接透過系統呼叫(system calls)與主機核心互動。這些程式可能不知道主機的名稱空間(namespaces),但它們仍然需要進行上下文切換(context-switch)進入核心空間,以執行諸如I/O存取等操作。
另一方面,虛擬機器(Virtual Machine, VM)總是在虛擬機器監控器(hypervisor)上執行,執行一個具有自己的檔案系統、網路、儲存(通常以映像檔的形式)和核心的客戶作業系統。虛擬機器監控器是一種軟體,它為客戶端作業系統提供硬體抽象層和虛擬化,使得單一的裸機(bare-metal)機器能夠在具備足夠硬體能力的情況下例項化多個虛擬機器。客戶端作業系統核心所看到的硬體大多是虛擬化的硬體,但也有一些例外。
這意味著,當虛擬機器內的程式執行系統呼叫時,它總是被導向客戶端作業系統的核心。
總而言之,容器與主機共用同一個核心,而虛擬機器則擁有自己的客戶端作業系統核心。這一事實意味著許多考量。
安全考量
從安全形度來看,虛擬機器提供了更好的隔離,以抵禦潛在的攻擊。然而,一些最新的根據CPU的攻擊(如Spectre或Meltdown)可以利用CPU漏洞來存取虛擬機器的地址空間。
容器技術已經改進了隔離功能,可以透過嚴格的安全策略(如CIS Docker、NIST、HIPAA等)進行組態,使其難以被利用。
可擴充套件性與資源利用
從可擴充套件性的角度來看,容器的啟動速度比虛擬機器快得多。如果映像檔已經存在於主機上,啟動一個新的容器例項只需幾毫秒。這種快速的結果也歸功於容器的無核心(kernel-less)特性。虛擬機器必須啟動一個核心和initramfs,切換到根檔案系統,執行某種初始化程式(如systemd),並啟動多個服務。
虛擬機器通常比容器消耗更多的資源。要啟動一個客戶端作業系統,通常需要分配比啟動容器所需的資源更多的RAM、CPU和儲存。
工作負載聚焦
容器和虛擬機器之間另一個重要的區別是對工作負載的關注。對於容器來說,最佳實踐是為每一個特定的工作負載啟動一個容器。另一方面,一個虛擬機器可以執行多個不同的工作負載。
例如,在非生產或小型生產環境中,將所有元件(Apache、PHP、MySQL和WordPress)安裝在同一台虛擬機器上並不罕見。然而,這種設計可以被拆分成多容器的架構,其中一個容器執行前端(Apache-PHP-WordPress),另一個容器執行MySQL資料函式庫。執行MySQL的容器可以存取儲存卷以持久化資料函式庫檔案。同時,這樣可以更容易地擴充套件前端容器。
為什麼需要容器?
本文描述了容器在現代IT系統中的好處和價值,以及容器如何為技術和業務提供好處。
採用容器的價值在於其能夠提高IT環境的敏捷性和可擴充套件性。當採用新興技術時,公司總是在尋求投資回報率(ROI),同時努力將總擁有成本(TCO)保持在合理的門檻內。這並不容易實作。
開源
驅動容器技術的技術是開源的,並且已成為被許多供應商或社群廣泛採用的開放標準。開源軟體今天被大公司、供應商和雲端提供商採用,具有許多優勢,並為企業提供了巨大的價值。開源往往與高價值和創新解決方案相關聯,這是事實!
首先,由社群驅動的專案通常具有很大的進化推動力,有助於程式碼的成熟和不斷引入新功能。開源軟體對公眾開放,可以被檢查和分析。這是一個偉大的透明度特性,也對軟體的可靠性產生了影響,無論是在健全性還是安全性方面。
可移植性
我們已經說過,容器是一種技術,能夠將應用程式及其整個執行環境封裝並隔離,這意味著執行所需的所有檔案。這一特性解鎖了一個關鍵的好處——可移植性。
這意味著容器映像檔可以被提取並在任何具有容器引擎的主機上執行,無論其下的作業系統發行版是什麼。一個CentOS或nginx映像檔可以同樣地從Fedora或Debian Linux發行版中提取並執行,且組態相同。
DevOps促進者
正如前面所說,當涉及到將應用程式佈署到生產環境時,容器有助於解決開發和維運團隊之間的老問題——「在我的機器上可以執行」。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 容器技術深度解析與實務應用
package "Docker 架構" {
actor "開發者" as dev
package "Docker Engine" {
component [Docker Daemon] as daemon
component [Docker CLI] as cli
component [REST API] as api
}
package "容器運行時" {
component [containerd] as containerd
component [runc] as runc
}
package "儲存" {
database [Images] as images
database [Volumes] as volumes
database [Networks] as networks
}
cloud "Registry" as registry
}
dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置
@enduml此圖示說明瞭容器與虛擬機器之間的主要區別:容器共用主機的核心,而虛擬機器則有自己的客戶端作業系統核心。
內容解密:
此Plantuml圖表呈現了容器技術與虛擬機器架構之間的關鍵差異。容器直接與主機作業系統的核心互動,而虛擬機器則在其自己的客戶端作業系統核心上執行,並由Hypervisor管理。這種架構上的差異對安全、可擴充套件性和資源利用等方面有著深遠的影響。