Podman 作為無 Daemon 容器引擎,其網路功能仰賴容器網路介面(CNI)的支援,透過外掛化的設計,提供高度的網路彈性。預設的 bridge 外掛已能滿足基本需求,而更進階的網路組態則可透過整合其他 CNI 外掛達成。Podman 的核心基礎是由 libpod 函式庫所構成,這個以 Go 語言編寫的函式庫負責協調容器生命週期,包含映像管理、資源隔離、Rootless 容器支援等關鍵功能,同時提供與 Docker 相容的 CLI 和 REST API,大幅降低使用者的學習成本。libpod 與底層的容器執行階段、Conmon 等元件緊密合作,構成了 Podman 的運作根本。至於容器的建立與執行,則由 OCI 容器執行階段負責。runc 作為最廣泛使用的 OCI 執行階段,透過 libcontainer 函式庫與核心互動,執行名稱空間隔離、CGroup 資源組態、安全策略設定等底層操作。而另一個以 C 語言編寫的 crun 執行階段,則以更輕量、更快速的特性,提供另一種 OCI 執行階段的選擇。兩種執行階段皆遵循 OCI 規範,可與 Podman 等容器引擎無縫銜接。
Podman 無 Daemon 架構:CNI 網路外掛的應用
容器網路介面 (CNI) 的重要性
Podman 透過 Kubernetes 的容器網路介面(Container Network Interface,CNI)規範來實作容器網路支援。這種方式讓 Podman 的網路功能更具彈性,並採用外掛導向的設計。預設情況下,Podman 使用基本的 bridge CNI 外掛。
若需要更多外掛,可以參考這個儲存函式庫:https://github.com/containernetworking/plugins。
Libpod 函式庫:Podman 的核心基礎
Podman 的核心基礎是 libpod 函式庫,這個函式庫也被其他開源專案(如 CRI-O)採用。Libpod 包含了協調容器生命週期所需的所有邏輯。可以說,libpod 函式庫的開發是 Podman 專案誕生的關鍵。
Libpod 以 Go 語言編寫,因此可以作為 Go 套件存取。它旨在實作引擎的所有高階功能,包括:
- 容器映像格式管理:支援 OCI 和 Docker 映像,包括映像生命週期管理,從驗證、從容器註冊中心提取、本地儲存映像層和中繼資料,到建構新映像並推播到遠端註冊中心。
- 容器生命週期管理:從容器建立(包括所有必要的初步步驟)到執行容器,以及停止、終止、還原、刪除等所有其他執行階段功能,還包括在執行中的容器上執行程式和記錄日誌。
- 管理簡單容器和 Pod:Pod 是一組沙箱容器,它們分享名稱空間(例如 UTC、IPC、網路,以及最近的 PID),並作為一個整體進行管理。
- 支援 Rootless 容器和 Pod:這些容器和 Pod 可以由標準使用者執行,無需許可權提升。
- 管理容器資源隔離:底層使用 CGroup 實作,但 Podman 使用者可以在容器執行期間透過 CLI 選項來管理記憶體和 CPU 預留,或限制儲存裝置的讀/寫速率。
- 提供 Docker 相容的 CLI:大多數 Podman 命令與 Docker CLI 相同。
- 提供 Docker 相容的 REST API:透過本地 Unix Socket 提供(預設未啟用)。Libpod REST API 提供 Podman CLI 提供的所有功能。
Libpod 套件在較底層與容器執行階段、Conmon 以及 container/storage、container/image、Buildah 和 CNI 等套件互動。接下來,我們將重點介紹容器執行階段的執行。
OCI 容器執行階段:runc 與 crun
容器執行階段的重要性
容器引擎負責容器生命週期的高階協調,而建立和執行容器所需的底層操作則由容器執行階段負責。近年來,在主要容器環境貢獻者的幫助下,一個行業標準已經出現:OCI 執行階段規範(OCI Runtime Specification)。
完整的規範可在此處找到:https://github.com/opencontainers/runtime-spec。
其中的 Runtime and Lifecycle 檔案完整描述了容器執行階段應如何處理容器的建立和執行:https://github.com/opencontainers/runtime-spec/blob/master/runtime.md。
runc:廣泛採用的 OCI 容器執行階段
Runc(https://github.com/opencontainers/runc)是目前最廣泛採用的 OCI 容器執行階段。其歷史可以追溯到 2015 年,當時 Docker 宣佈將其所有基礎設施管道分離到一個名為 runC 的專案中。
RunC 完全支援 Linux 容器和 OCI 執行階段規範。該專案儲存函式庫包括 libcontainer 套件,這是一個用於使用名稱空間、cgroup、功能和檔案系統存取控制來建立容器的 Go 套件。Libcontainer 以前是一個獨立的 Docker 專案,在建立 runC 專案時,為了保持一致性和清晰度,它被移到了 runC 的主儲存函式庫中。
Libcontainer 套件定義了從頭開始啟動容器的內部邏輯和底層系統互動,從名稱空間的初始隔離到在容器內以 PID 1 執行二進位程式。
libcontainer 的任務
執行階段回呼 libcontainer 函式庫以完成以下任務:
- 使用 Podman 提供的容器掛載點和容器中繼資料
- 與核心互動以啟動容器並使用
clone()和unshare()系統呼叫執行隔離的程式 - 設定 CGroup 資源預留
- 設定 SELinux 策略、Seccomp 和 App Armor 規則
除了執行程式外,libcontainer 還處理名稱空間和檔案描述符的初始化、建立容器 rootFS 和繫結掛載、從容器程式匯出日誌、使用 seccomp、SELinux 和 AppArmor 管理安全限制,以及建立和對映使用者和群組。
Libcontainer 架構對於本章來說是一個相當複雜的主題,顯然需要進一步研究才能更好地理解其內部結構。
對於有興趣檢視程式碼並理解 Podman 內部結構的讀者,符合 OCI 執行階段規範的容器介面定義在 https://github.com/opencontainers/runc/blob/master/libcontainer/container.go 原始碼檔案中。
實作該介面的 Linux 作業系統的方法定義在 https://github.com/opencontainers/runc/blob/master/libcontainer/container_linux.go 中。
使用 clone() 和 unshare() 系統呼叫來隔離程式名稱空間的底層執行由 nsenter 套件處理,更準確地說是由 nsexec() 函式處理。這是一個嵌入在 Go 程式碼中的 C 函式,使用了 cgo。
nsexec() 的程式碼可以在這裡找到:https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/nsexec.c。
crun:另一種 OCI 容器執行階段
除了 runC 之外,還建立了許多其他容器執行階段。我們將在本章中討論的另一種執行階段是 crun(https://github.com/containers/crun),這是一個快速與低記憶體佔用的 OCI 容器執行階段,完全用 C 語言編寫。crun 背後的想法是提供一個改進的 OCI 執行階段,可以利用 C 設計方法來實作更乾淨和輕量級的執行階段。由於它們都是 OCI 執行階段,因此 runC 和 crun 可以被容器引擎互換使用。
總結來說,Podman 透過 CNI 和 Libpod 等技術,實作了高效與靈活的容器管理。而 runc 和 crun 則提供了底層的容器執行能力,確保容器能夠安全與有效地執行。