作者 | Daniel Walsh
譯者 | Andy Song (pinewall) ? ? ? 共計翻譯:22 篇 貢獻時間:80 天
“容器執行時”是一個被過度使用的名詞。
在 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[2] 和 RKT[3]。這些工具都是基於 API 的,涉及操作包括從容器倉庫拉取容器映象、配置儲存和啟動容器等。啟動容器通常涉及一個特殊工具,用於配置核心如何執行容器,這類工具也被稱為“容器執行時”,下文中我將稱其為“底層容器執行時”以作區分。像 Docker、CRI-O 這樣的守護行程及形如 Podman[4]、Buildah[5] 的命令列工具,似乎更應該被稱為“容器管理器”。
早期版本的 Docker 使用 lxc
工具集啟動容器,該工具出現在 systemd-nspawn
之前。Red Hat 最初試圖將 libvirt[6] (libvirt-lxc
)整合到 Docker 中替代 lxc
工具,因為 RHEL 並不支援 lxc
。libvirt-lxc
也沒有使用 systemd-nspawn
,在那時 systemd 團隊僅將 systemd-nspawn
視為測試工具,不適用於生產環境。
與此同時,包括我的 Red Hat 團隊部分成員在內的上游 Docker 開發者,認為應該採用 golang 原生的方式啟動容器,而不是呼叫外部應用。他們的工作促成了 libcontainer 這個 golang 原生庫,用於啟動容器。Red Hat 工程師更看好該庫的發展前景,放棄了 libvirt-lxc
。
後來成立 開放容器組織[7](OCI)的部分原因就是人們希望用其它方式啟動容器。傳統的基於名字空間隔離的容器已經家喻戶曉,但人們也有虛擬機器級別隔離的需求。Intel 和 Hyper.sh[8] 正致力於開發基於 KVM 隔離的容器,Microsoft 致力於開發基於 Windows 的容器。OCI 希望有一份定義容器的標準規範,因而產生了 OCI 執行時規範[9]。
OCI 執行時規範定義了一個 JSON 檔案格式,用於描述要執行的二進位制,如何容器化以及容器根檔案系統的位置。一些工具用於生成符合標準規範的 JSON 檔案,另外的工具用於解析 JSON 檔案併在該根檔案系統(rootfs)上執行容器。Docker 的部分程式碼被抽取出來構成了 libcontainer 專案,該專案被貢獻給 OCI。上游 Docker 工程師及我們自己的工程師建立了一個新的前端工具,用於解析符合 OCI 執行時規範的 JSON 檔案,然後與 libcontainer 互動以便啟動容器。這個前端工具就是 runc[10],也被貢獻給 OCI。雖然 runc
可以解析 OCI JSON 檔案,但使用者需要自行生成這些檔案。此後,runc
也成為了最流行的底層容器執行時,基本所有的容器管理工具都支援 runc
,包括 CRI-O、Docker、Buildah、Podman 和 Cloud Foundry Garden[11] 等。此後,其它工具的實現也參照 OCI 執行時規範,以便可以執行 OCI 相容的容器。
Clear Containers[12] 和 Hyper.sh 的 runV
工具都是參照 OCI 執行時規範執行基於 KVM 的容器,二者將其各自工作合併到一個名為 Kata[12] 的新專案中。在去年,Oracle 建立了一個示例版本的 OCI 執行時工具,名為 RailCar[13],使用 Rust 語言編寫。但該 GitHub 專案已經兩個月沒有更新了,故無法判斷是否仍在開發。幾年前,Vincent Batts 試圖建立一個名為 nspawn-oci[14] 的工具,用於解析 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[17] 譯者:pinewall 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出