從早期 chroot 的簡易隔離到現在成熟的容器化技術,應用程式佈署的環境一致性一直是開發者們追求的目標。容器技術的出現,透過 Linux 名稱空間和 cgroups 等核心功能,有效地解決了這個難題。不同於虛擬機器,容器分享主機作業系統核心,實作輕量級的資源利用和更快速的啟動速度。這使得容器在微服務架構、持續整合與快速佈署等場景中,展現出極大的優勢。然而,容器技術並非憑空出現,其背後是 Linux 核心多年演進的成果。理解 Linux 名稱空間如何隔離行程的檔案系統、網路、PID 等資源,以及 cgroups 如何限制容器的 CPU 和記憶體用量,是掌握容器技術的關鍵。此外,容器映像的分層架構,透過寫時複製的機制,實作了映像的快速構建和佈署,同時也降低了儲存空間的佔用。

玄貓解說:容器技術的奧秘與演進

在資訊科技的世界裡,容器技術早已不是新鮮事。但你知道嗎?這項技術的根源可以追溯到 1970 年代。儘管容器的概念簡單直觀,但其背後蘊含著許多值得探討的技術概念。

身為一個技術人,我們不應只停留在使用工具的層面,更要了解其運作原理。唯有深入理解容器技術的底層機制,才能真正掌握其精髓,並在實際應用中發揮其最大價值。

為何容器技術如此風靡?玄貓的觀察

近年來,容器技術已成為企業 IT 架構的根本。根據玄貓的觀察,現今有超過一半的企業,將其應用程式的一半以上佈署在容器中。這究竟是為什麼呢?

容器技術之所以如此受歡迎,原因在於它解決了傳統 IT 環境中的許多痛點,例如:

  • 環境一致性: 容器確保應用程式在不同環境中執行的一致性,避免了「在我機器上可以跑」的問題。
  • 資源利用率: 容器分享底層作業系統核心,相較於虛擬機器,能更有效地利用硬體資源。
  • 快速佈署: 容器的輕量化特性,使得應用程式的佈署速度大幅提升。
  • 可移植性: 容器可以在不同的雲端平台和基礎架構之間輕鬆移植。

玄貓帶你探索容器技術的根本

在探討容器技術之前,讓我們先釐清幾個基本概念:

  • 行程(Process): 行程是作業系統中正在執行的程式例項。每個行程都有自己的記憶體空間和系統資源。
  • 檔案系統(Filesystem): 檔案系統是作業系統組織和儲存檔案的方式。
  • 系統呼叫(System Call): 系統呼叫是行程向作業系統核心請求服務的介面。
  • 行程隔離(Process Isolation): 行程隔離是容器技術的核心。它確保容器內的行程無法存取或修改容器外的資源。

容器與虛擬機器:玄貓的經驗談

許多人會將容器與虛擬機器混淆,但它們在本質上是不同的。虛擬機器透過 hypervisor 模擬完整的硬體環境,而容器則分享底層作業系統核心。

特性容器虛擬機器
資源需求
啟動速度
隔離性行程級硬體級
適用場景微服務、快速佈署、持續整合需要完整作業系統環境、資源隔離要求高的應用

玄貓認為,容器和虛擬機器各有其優勢和適用場景。在選擇時,應根據實際需求進行權衡。

玄貓提醒:本章的閱讀慣例

為了讓讀者更好地理解本章的內容,玄貓在此說明一些閱讀慣例:

  • $ 字元開頭的命令:表示以一般使用者身分執行。
  • # 字元開頭的命令:表示以 root 使用者身分執行。
  • \ 字元:表示該行命令過長,已換行顯示。

什麼是行程?玄貓的解讀

在探討容器之前,我們必須先了解什麼是行程。行程是作業系統中正在執行的程式例項。每個行程都有自己的記憶體空間和系統資源。

理解行程的概念,是理解容器技術的基礎。

總之,容器技術是一項強大與具有廣泛應用前景的技術。透過瞭解其基本概念和運作原理,我們可以更好地利用它來提升 IT 效率和應用程式的可靠性。

玄貓(BlackCat)總結

容器技術的興起,為軟體開發和佈署帶來了革命性的改變。從行程隔離到資源利用率,容器技術的每一個環節都值得我們深入研究。希望透過本文,讀者能對容器技術有更全面的認識,並在實際應用中得心應手。

身為玄貓(BlackCat),我來重新創作這篇文章,讓大家更瞭解容器技術的本質。

容器技術:隔離與資源控制的藝術

什麼是容器?行程、程式與隔離的關係

在 Michael Kerrisk 的《Linux Programming Interface》一書中,行程被定義為執行中的程式例項。程式則是一個包含執行行程所需資訊的檔案。程式可以動態連結到外部函式庫,也可以靜態連結到程式本身(Go 語言預設使用這種方式)。

行程在機器 CPU 中執行,並分配一部分記憶體,包含程式碼和程式碼使用的變數。行程在機器使用者空間中例項化,其執行由作業系統核心協調。當行程執行時,需要存取不同的機器資源,例如 I/O(磁碟、網路、終端等)或記憶體。當行程需要存取這些資源時,它會執行一個系統呼叫到核心空間(例如,讀取磁碟區塊或透過網路介面傳送封包)。

行程使用檔案系統間接與主機磁碟互動。檔案系統是一個多層儲存抽象,有助於檔案和目錄的寫入和讀取存取。

一台機器通常執行多少個行程?很多。它們由作業系統核心協調,使用複雜的排程邏輯,使行程的行為就像它們在專用的 CPU 核心上執行一樣,而同一個核心在許多行程之間分享。相同的程式可以例項化多個同類別程式(例如,在同一台機器上執行的多個 Web 伺服器例項)。必須適當管理衝突,例如許多行程嘗試存取相同的網路埠。

沒有什麼可以阻止我們在主機上執行相同程式的不同版本,假設系統管理員將承擔管理二進位檔案、函式庫及其依賴項的潛在衝突的重擔。這可能成為一項複雜的任務,使用常見的實務不一定容易解決。

容器的本質:應用程式隔離

容器是對執行隔離的行程例項的需求的一個簡單而聰明的答案。我們可以安全地肯定,容器是一種在多個層面上運作的應用程式隔離形式:

  • 檔案系統隔離:容器化的行程具有分離的檔案系統檢視,並且它們的程式是從隔離的檔案系統本身執行的。
  • 行程 ID 隔離:這是一個在獨立的行程 ID (PID) 集合下執行的容器化行程。
  • 使用者隔離:使用者 ID (UID) 和群組 ID (GID) 與容器隔離。行程的 UID 和 GID 在容器內部可以不同,並且只能在容器內部以特權 UID 或 GID 執行。
  • 網路隔離:這種隔離與主機網路資源相關,例如網路裝置、IPv4 和 IPv6 堆積疊、路由表和防火牆規則。
  • IPC 隔離:容器為主機 IPC 資源(例如 POSIX 訊息佇列或 System V IPC 物件)提供隔離。
  • 資源使用隔離:容器依賴 Linux 控制群組 (cgroups) 來限制或監視某些資源(例如 CPU、記憶體或磁碟)的使用。

容器的優勢:一致性與隔離性

從採用的角度來看,容器的主要目的,或者至少是最常見的使用案例,是在隔離的環境中執行應用程式。為了更好地理解這個概念,我們可以看看下面的

  • 原生應用程式:在不提供容器化功能的系統上原生執行的應用程式分享相同的二進位檔案和函式庫,以及相同的核心、檔案系統、網路和使用者。當佈署應用程式的更新版本時,這可能會導致許多問題,尤其是衝突的函式庫問題或不滿足的依賴項。
  • 容器化應用程式:容器為應用程式及其相關依賴項提供了一致的隔離層,確保在同一主機上無縫共存。新的佈署僅包括執行新的容器化版本,因為它不會與其他容器或原生應用程式互動或衝突。

Linux 名稱空間:容器的根本

Linux 容器由不同的原生核心功能啟用,其中最重要的是 Linux 名稱空間。名稱空間抽象特定的系統資源(特別是之前描述的那些,例如網路、檔案系統掛載、使用者等),並使它們對於隔離的行程來說是唯一的。這樣,行程具有與主機資源(例如,主機檔案系統)互動的錯覺,同時暴露了一個替代的和隔離的版本。

目前,我們總共有八種名稱空間:

  • PID 名稱空間:這些在單獨的空間中隔離行程 ID 號碼,允許不同 PID 名稱空間中的行程保留相同的 PID。
  • 使用者名稱空間:這些隔離使用者和群組 ID、根目錄、金鑰環和功能。這允許行程在容器內部具有特權 UID 和 GID,同時在名稱空間外部具有非特權 UID 和 GID。
  • UTS 名稱空間:這些允許隔離主機名稱和 NIS 網域名稱。
  • 網路名稱空間:這些允許隔離網路系統資源,例如網路裝置、IPv4 和 IPv6 協定堆積疊、路由表、防火牆規則、埠號等。使用者可以建立稱為 veth 對的虛擬網路裝置,以在網路名稱空間之間建立隧道。
  • IPC 名稱空間:這些隔離 IPC 資源,例如 System V IPC 物件和 POSIX 訊息佇列。在 IPC 名稱空間中建立的物件只能由作為名稱空間成員的行程存取。行程使用 IPC 在客戶端-伺服器機制中交換資料、事件和訊息。
  • cgroup 名稱空間:這些隔離 cgroup 目錄,提供行程 cgroup 的虛擬化檢視。
  • 掛載名稱空間:這些提供名稱空間中行程看到的掛載點清單的隔離。
  • 時間名稱空間:這些提供系統時間的隔離檢視,允許名稱空間中的行程以相對於主機時間的時間偏移執行。

cgroups:資源使用的控制

cgroups 是 Linux 核心的一個原生功能,其目的是在分層樹中組織行程,並限制或監視它們的資源使用。

核心 cgroups 介面,類別似於 /proc 發生的情況,透過 cgroupfs 偽檔案系統暴露。此檔案系統通常掛載在主機的 /sys/fs/cgroup 下。

cgroups 提供了一系列控制器(也稱為子系統),可用於不同的目的,例如限制行程的 CPU 時間份額、記憶體使用、凍結和還原行程等。

玄貓(BlackCat)認為,容器技術的核心在於隔離與控制。透過 Linux 名稱空間實作資源的隔離,而 cgroups 則負責資源的限制與監控。這種技術的結合,使得容器能夠在分享的基礎設施上安全、高效地執行應用程式。

容器技術概論:隔離、映像與分層架構

在容器技術的世界裡,控制器的組織層級歷經多次演變,目前存在 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

這會使 shell 行程在 PID 和網路名稱空間上都被隔離。檢查網路 IP 組態會發現,主機的原生裝置不再被 unshared 行程直接看到:

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 原生功能密切相關。作業系統提供了一個堅實與完整的介面,有助於容器執行時的開發,而隔離名稱空間和資源的能力是解鎖容器採用的關鍵。容器執行時的作用是抽象化底層隔離機制的複雜性,其中掛載點隔離可能是最關鍵的。因此,它值得更深入的解釋。

隔離掛載:構建獨立的檔案系統檢視

到目前為止,玄貓展示的 unsharing 範例並未影響掛載點以及行程端的檔案系統檢視。為了獲得防止二進位檔案和函式庫衝突的檔案系統隔離,使用者需要為公開的掛載點建立另一個抽象層。

這可以透過掛載名稱空間和繫結掛載來實作。掛載名稱空間於 2002 年在 Linux 核心 2.4.19 中首次引入,它隔離了行程所看到的掛載點列表。每個掛載名稱空間公開一個離散的掛載點列表,從而使不同名稱空間中的行程能夠感知到不同的目錄層級結構。

透過這種技術,可以向執行中的行程公開一個替代的目錄樹,其中包含所有必要的二進位檔案和函式庫。

儘管看似簡單,但掛載名稱空間的管理絕非易事。例如,使用者應處理來自不同發行版本的目錄樹的不同封存版本、提取它們,並將它們繫結掛載到單獨的名稱空間。玄貓會在後面的章節提到,Linux 中容器的早期方法就是遵循這種方式。

容器的成功還與一種創新的、多層的、寫時複製(copy-on-write)方法有關,該方法用於管理目錄樹,引入了一種簡單快速的方法來複製、佈署和使用執行容器所需的樹——容器映像。

容器映像:容器技術的救星

我們必須感謝 Docker 引入了這種用於儲存容器資料的巧妙方法。後來,映像成為開放容器倡議(OCI)的標準規範(https://github.com/opencontainers/image-spec)。

映像可以看作是一個檔案系統包,在第一次執行容器之前,它會被下載(提取)並解壓縮到主機中。

映像是從稱為映像儲存函式庫的儲存函式庫下載的。這些儲存函式庫可以看作是專門的物件儲存,用於儲存映像資料和相關的中繼資料。既有公共與免費使用的儲存函式庫(例如 quay.iodocker.io),也有可以在客戶私人基礎架構、本地或雲端中執行的私人儲存函式庫。

映像可以由 DevOps 團隊建構,以滿足特殊需求或嵌入必須佈署並在主機上執行的 Artifact。

在映像建構過程中,開發人員可以注入預先建構的 Artifact 或原始碼,這些 Artifact 或原始碼可以在建構容器本身中進行編譯。為了最佳化映像大小,可以建立多階段建構,第一階段使用具有必要編譯器和執行時的基本映像來編譯原始碼,第二階段將建構的 Artifact 注入到最小、輕量級的映像中,從而針對快速啟動和最小儲存空間進行最佳化。

建構過程的配方定義在一個名為 Dockerfile 的特殊文字檔案中,該檔案定義了組裝最終映像所需的所有步驟。

建構映像後,使用者可以將自己的映像推播到公共或私人儲存函式庫中,以供以後使用或進行複雜的協調佈署。

下圖總結了建構工作流程:

Image build workflow

玄貓將在本章後面更廣泛地介紹建構主題。

是什麼讓容器映像如此特別?映像背後的巧妙想法是,它們可以被視為一種封裝技術。當使用者使用安裝在作業系統目錄樹中的所有二進位檔案和依賴項建構自己的映像時,他們實際上是在建立一個自我一致的物件,可以佈署到任何地方,而無需進一步的軟體依賴項。從這個角度來看,容器映像是對長期爭論的一句話的回應:「It works on my machine.」

開發團隊喜歡它們,因為他們可以確定其應用程式的執行環境,而營運團隊喜歡它們,因為它們簡化了佈署過程,從而消除了維護和更新伺服器函式庫依賴項的繁瑣任務。

容器映像的另一個巧妙功能是它們的寫時複製、多層方法。映像不是由單個批次二進位封存檔組成,而是由許多稱為 blob 或 layer 的 tar 封存檔組成。層使用映像中繼資料組合在一起,並壓縮到單個檔案系統檢視中。這可以透過多種方式實作,但目前最常見的方法是使用聯合檔案系統。

OverlayFS(https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html)是目前使用最多的聯合檔案系統。儘管它並非完全符合 POSIX 標準,但它仍維護在核心樹中。

玄貓觀點:容器技術的未來

容器技術的發展日新月異,從最初的名稱空間隔離到現在的容器映像分層,每一步都解決了實際的痛點。玄貓認為,容器技術的未來將更加註重安全性、可觀測性和跨平台相容性。期待看到更多創新技術的出現,讓容器技術在各個領域發光發熱。