底層 Linux 容器運行時之發展史
在 Red Hat,我們樂意這麼說,「容器即 Linux,Linux 即容器」。下面解釋一下這種說法。傳統的容器是操作系統中的進程,通常具有如下 3 個特性:
- 資源限制
當你在系統中運行多個容器時,你肯定不希望某個容器獨佔系統資源,所以我們需要使用資源約束來控制 CPU、內存和網路帶寬等資源。Linux 內核提供了 cgroup 特性,可以通過配置控制容器進程的資源使用。
- 安全性配置
一般而言,你不希望你的容器可以攻擊其它容器或甚至攻擊宿主機系統。我們使用了 Linux 內核的若干特性建立安全隔離,相關特性包括 SELinux、seccomp 和 capabilities。
(LCTT 譯註:從 2.2 版本內核開始,Linux 將特權從超級用戶中分離,產生了一系列可以單獨啟用或關閉的 capabilities)
- 虛擬隔離
容器外的任何進程對於容器而言都應該不可見。容器應該使用獨立的網路。不同的容器對應的進程應該都可以綁定 80 埠。每個容器的 內核映像 、 根文件系統 (rootfs)都應該相互獨立。在 Linux 中,我們使用內核的 名字空間 特性提供 虛擬隔離 。
那麼,具有安全性配置並且在 cgroup 和名字空間下運行的進程都可以稱為容器。查看一下 Red Hat Enterprise Linux 7 操作系統中的 PID 1 的進程 systemd,你會發現 systemd 運行在一個 cgroup 下。
# tail -1 /proc/1/cgroup
1_name=systemd:/
ps 命令讓我們看到 systemd 進程具有 SELinux 標籤:
# ps -eZ | grep systemd
system_u:system_r:init_t:s0 1 ? 00:00:48 systemd
以及 capabilities:
# grep Cap /proc/1/status
...
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapBnd: 0000003fffffffff
最後,查看 /proc/1/ns 子目錄,你會發現 systemd 運行所在的名字空間。
ls -l /proc/1/ns
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 net -> net:[4026532009]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 pid -> pid:[4026531836]
...
如果 PID 1 進程(實際上每個系統進程)具有資源約束、安全性配置和名字空間,那麼我可以說系統上的每一個進程都運行在容器中。
容器運行時工具也不過是修改了資源約束、安全性配置和名字空間,然後 Linux 內核運行起進程。容器啟動後,容器運行時可以在容器內監控 PID 1 進程,也可以監控容器的標準輸入/輸出,從而進行容器進程的生命周期管理。
容器運行時
你可能自言自語道,「哦,systemd 看起來很像一個容器運行時」。經過若干次關於「為何容器運行時不使用 systemd-nspawn 工具來啟動容器」的郵件討論後,我認為值得討論一下容器運行時及其發展史。
Docker 通常被稱為容器運行時,但「 容器運行時 」是一個被過度使用的詞語。當用戶提到「容器運行時」,他們其實提到的是為開發人員提供便利的 上層 工具,包括 Docker,CRI-O 和 RKT。這些工具都是基於 API 的,涉及操作包括從容器倉庫拉取容器鏡像、配置存儲和啟動容器等。啟動容器通常涉及一個特殊工具,用於配置內核如何運行容器,這類工具也被稱為「容器運行時」,下文中我將稱其為「底層容器運行時」以作區分。像 Docker、CRI-O 這樣的守護進程及形如 Podman、Buildah 的命令行工具,似乎更應該被稱為「容器管理器」。
早期版本的 Docker 使用 lxc 工具集啟動容器,該工具出現在 systemd-nspawn 之前。Red Hat 最初試圖將 libvirt (libvirt-lxc)集成到 Docker 中替代 lxc 工具,因為 RHEL 並不支持 lxc。libvirt-lxc 也沒有使用 systemd-nspawn,在那時 systemd 團隊僅將 systemd-nspawn 視為測試工具,不適用於生產環境。
與此同時,包括我的 Red Hat 團隊部分成員在內的 上游 Docker 開發者,認為應該採用 golang 原生的方式啟動容器,而不是調用外部應用。他們的工作促成了 libcontainer 這個 golang 原生庫,用於啟動容器。Red Hat 工程師更看好該庫的發展前景,放棄了 libvirt-lxc。
後來成立 開放容器組織 (OCI)的部分原因就是人們希望用其它方式啟動容器。傳統的基於名字空間隔離的容器已經家喻戶曉,但人們也有 虛擬機級別隔離 的需求。Intel 和 Hyper.sh 正致力於開發基於 KVM 隔離的容器,Microsoft 致力於開發基於 Windows 的容器。OCI 希望有一份定義容器的標準規範,因而產生了 OCI 運行時規範 。
OCI 運行時規範定義了一個 JSON 文件格式,用於描述要運行的二進位,如何容器化以及容器根文件系統的位置。一些工具用於生成符合標準規範的 JSON 文件,另外的工具用於解析 JSON 文件並在該根文件系統(rootfs)上運行容器。Docker 的部分代碼被抽取出來構成了 libcontainer 項目,該項目被貢獻給 OCI。上游 Docker 工程師及我們自己的工程師創建了一個新的前端工具,用於解析符合 OCI 運行時規範的 JSON 文件,然後與 libcontainer 交互以便啟動容器。這個前端工具就是 runc,也被貢獻給 OCI。雖然 runc 可以解析 OCI JSON 文件,但用戶需要自行生成這些文件。此後,runc 也成為了最流行的底層容器運行時,基本所有的容器管理工具都支持 runc,包括 CRI-O、Docker、Buildah、Podman 和 Cloud Foundry Garden 等。此後,其它工具的實現也參照 OCI 運行時規範,以便可以運行 OCI 兼容的容器。
Clear Containers 和 Hyper.sh 的 runV 工具都是參照 OCI 運行時規範運行基於 KVM 的容器,二者將其各自工作合併到一個名為 Kata 的新項目中。在去年,Oracle 創建了一個示例版本的 OCI 運行時工具,名為 RailCar,使用 Rust 語言編寫。但該 GitHub 項目已經兩個月沒有更新了,故無法判斷是否仍在開發。幾年前,Vincent Batts 試圖創建一個名為 nspawn-oci 的工具,用於解析 OCI 運行時規範文件並啟動 systemd-nspawn;但似乎沒有引起大家的注意,而且也不是原生的實現。
如果有開發者希望實現一個原生的 systemd-nspawn --oci OCI-SPEC.json 並讓 systemd 團隊認可和提供支持,那麼CRI-O、Docker 和 Podman 等容器管理工具將可以像使用 runc 和 Clear Container/runV (Kata) 那樣使用這個新的底層運行時。(目前我的團隊沒有人參與這方面的工作。)
總結如下,在 3-4 年前,上游開發者打算編寫一個底層的 golang 工具用於啟動容器,最終這個工具就是 runc。那時開發者有一個使用 C 編寫的 lxc 工具,在 runc 開發後,他們很快轉向 runc。我很確信,當決定構建 libcontainer 時,他們對 systemd-nspawn 或其它非原生(即不使用 golang)的運行 namespaces 隔離的容器的方式都不感興趣。
via: https://opensource.com/article/18/1/history-low-level-container-runtimes
作者:Daniel Walsh 譯者:pinewall 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

















