“容器運(yùn)行時(shí)”是一個(gè)被過(guò)度使用的名詞。
在 Red Hat,我們樂(lè)意這么說(shuō),“容器即 Linux,Linux 即容器”。下面解釋一下這種說(shuō)法。傳統(tǒng)的容器是操作系統(tǒng)中的進(jìn)程,通常具有如下 3 個(gè)特性:
資源限制
當(dāng)你在系統(tǒng)中運(yùn)行多個(gè)容器時(shí),你肯定不希望某個(gè)容器獨(dú)占系統(tǒng)資源,所以我們需要使用資源約束來(lái)控制 CPU、內(nèi)存和網(wǎng)絡(luò)帶寬等資源。Linux 內(nèi)核提供了 cgroup 特性,可以通過(guò)配置控制容器進(jìn)程的資源使用。
安全性配置
一般而言,你不希望你的容器可以攻擊其它容器或甚至攻擊宿主機(jī)系統(tǒng)。我們使用了 Linux 內(nèi)核的若干特性建立安全隔離,相關(guān)特性包括 SELinux、seccomp 和 capabilities。
(LCTT 譯注:從 2.2 版本內(nèi)核開(kāi)始,Linux 將特權(quán)從超級(jí)用戶中分離,產(chǎn)生了一系列可以單獨(dú)啟用或關(guān)閉的 capabilities)
虛擬隔離
容器外的任何進(jìn)程對(duì)于容器而言都應(yīng)該不可見(jiàn)。容器應(yīng)該使用獨(dú)立的網(wǎng)絡(luò)。不同的容器對(duì)應(yīng)的進(jìn)程應(yīng)該都可以綁定 80 端口。每個(gè)容器的內(nèi)核映像image、根文件系統(tǒng)rootfs(rootfs)都應(yīng)該相互獨(dú)立。在 Linux 中,我們使用內(nèi)核的名字空間namespace特性提供虛擬隔離virtual separation。
那么,具有安全性配置并且在 cgroup 和名字空間下運(yùn)行的進(jìn)程都可以稱為容器。查看一下 Red Hat Enterprise Linux 7 操作系統(tǒng)中的 PID 1 的進(jìn)程 systemd,你會(huì)發(fā)現(xiàn) systemd 運(yùn)行在一個(gè) cgroup 下。
ps 命令讓我們看到 systemd 進(jìn)程具有 SELinux 標(biāo)簽:
以及 capabilities:
最后,查看 /proc/1/ns 子目錄,你會(huì)發(fā)現(xiàn) systemd 運(yùn)行所在的名字空間。
如果 PID 1 進(jìn)程(實(shí)際上每個(gè)系統(tǒng)進(jìn)程)具有資源約束、安全性配置和名字空間,那么我可以說(shuō)系統(tǒng)上的每一個(gè)進(jìn)程都運(yùn)行在容器中。
容器運(yùn)行時(shí)工具也不過(guò)是修改了資源約束、安全性配置和名字空間,然后 Linux 內(nèi)核運(yùn)行起進(jìn)程。容器啟動(dòng)后,容器運(yùn)行時(shí)可以在容器內(nèi)監(jiān)控 PID 1 進(jìn)程,也可以監(jiān)控容器的標(biāo)準(zhǔn)輸入/輸出,從而進(jìn)行容器進(jìn)程的生命周期管理。
容器運(yùn)行時(shí)
你可能自言自語(yǔ)道,“哦,systemd 看起來(lái)很像一個(gè)容器運(yùn)行時(shí)”。經(jīng)過(guò)若干次關(guān)于“為何容器運(yùn)行時(shí)不使用 systemd-nspawn 工具來(lái)啟動(dòng)容器”的郵件討論后,我認(rèn)為值得討論一下容器運(yùn)行時(shí)及其發(fā)展史。
Docker 通常被稱為容器運(yùn)行時(shí),但“容器運(yùn)行時(shí)container runtime”是一個(gè)被過(guò)度使用的詞語(yǔ)。當(dāng)用戶提到“容器運(yùn)行時(shí)”,他們其實(shí)提到的是為開(kāi)發(fā)人員提供便利的上層high-level工具,包括 Docker,CRI-O 和 RKT。這些工具都是基于 API 的,涉及操作包括從容器倉(cāng)庫(kù)拉取容器鏡像、配置存儲(chǔ)和啟動(dòng)容器等。啟動(dòng)容器通常涉及一個(gè)特殊工具,用于配置內(nèi)核如何運(yùn)行容器,這類(lèi)工具也被稱為“容器運(yùn)行時(shí)”,下文中我將稱其為“底層容器運(yùn)行時(shí)”以作區(qū)分。像 Docker、CRI-O 這樣的守護(hù)進(jìn)程及形如 Podman、Buildah 的命令行工具,似乎更應(yīng)該被稱為“容器管理器”。
早期版本的 Docker 使用 lxc 工具集啟動(dòng)容器,該工具出現(xiàn)在 systemd-nspawn 之前。Red Hat 最初試圖將 libvirt (libvirt-lxc)集成到 Docker 中替代 lxc 工具,因?yàn)?RHEL 并不支持 lxc。libvirt-lxc 也沒(méi)有使用 systemd-nspawn,在那時(shí) systemd 團(tuán)隊(duì)僅將 systemd-nspawn 視為測(cè)試工具,不適用于生產(chǎn)環(huán)境。
與此同時(shí),包括我的 Red Hat 團(tuán)隊(duì)部分成員在內(nèi)的上游upstream Docker 開(kāi)發(fā)者,認(rèn)為應(yīng)該采用 golang 原生的方式啟動(dòng)容器,而不是調(diào)用外部應(yīng)用。他們的工作促成了 libcontainer 這個(gè) golang 原生庫(kù),用于啟動(dòng)容器。Red Hat 工程師更看好該庫(kù)的發(fā)展前景,放棄了 libvirt-lxc。
后來(lái)成立 開(kāi)放容器組織Open Container Initiative(OCI)的部分原因就是人們希望用其它方式啟動(dòng)容器。傳統(tǒng)的基于名字空間隔離的容器已經(jīng)家喻戶曉,但人們也有虛擬機(jī)級(jí)別隔離virtual machine-level isolation的需求。Intel 和 Hyper.sh 正致力于開(kāi)發(fā)基于 KVM 隔離的容器,Microsoft 致力于開(kāi)發(fā)基于 Windows 的容器。OCI 希望有一份定義容器的標(biāo)準(zhǔn)規(guī)范,因而產(chǎn)生了 OCI 運(yùn)行時(shí)規(guī)范Runtime Specification。
OCI 運(yùn)行時(shí)規(guī)范定義了一個(gè) JSON 文件格式,用于描述要運(yùn)行的二進(jìn)制,如何容器化以及容器根文件系統(tǒng)的位置。一些工具用于生成符合標(biāo)準(zhǔn)規(guī)范的 JSON 文件,另外的工具用于解析 JSON 文件并在該根文件系統(tǒng)(rootfs)上運(yùn)行容器。Docker 的部分代碼被抽取出來(lái)構(gòu)成了 libcontainer 項(xiàng)目,該項(xiàng)目被貢獻(xiàn)給 OCI。上游 Docker 工程師及我們自己的工程師創(chuàng)建了一個(gè)新的前端工具,用于解析符合 OCI 運(yùn)行時(shí)規(guī)范的 JSON 文件,然后與 libcontainer 交互以便啟動(dòng)容器。這個(gè)前端工具就是 runc,也被貢獻(xiàn)給 OCI。雖然 runc 可以解析 OCI JSON 文件,但用戶需要自行生成這些文件。此后,runc 也成為了最流行的底層容器運(yùn)行時(shí),基本所有的容器管理工具都支持 runc,包括 CRI-O、Docker、Buildah、Podman 和 Cloud Foundry Garden 等。此后,其它工具的實(shí)現(xiàn)也參照 OCI 運(yùn)行時(shí)規(guī)范,以便可以運(yùn)行 OCI 兼容的容器。
Clear Containers 和 Hyper.sh 的 runV 工具都是參照 OCI 運(yùn)行時(shí)規(guī)范運(yùn)行基于 KVM 的容器,二者將其各自工作合并到一個(gè)名為 Kata 的新項(xiàng)目中。在去年,Oracle 創(chuàng)建了一個(gè)示例版本的 OCI 運(yùn)行時(shí)工具,名為 RailCar,使用 Rust 語(yǔ)言編寫(xiě)。但該 GitHub 項(xiàng)目已經(jīng)兩個(gè)月沒(méi)有更新了,故無(wú)法判斷是否仍在開(kāi)發(fā)。幾年前,Vincent Batts 試圖創(chuàng)建一個(gè)名為 nspawn-oci 的工具,用于解析 OCI 運(yùn)行時(shí)規(guī)范文件并啟動(dòng) systemd-nspawn;但似乎沒(méi)有引起大家的注意,而且也不是原生的實(shí)現(xiàn)。
如果有開(kāi)發(fā)者希望實(shí)現(xiàn)一個(gè)原生的 systemd-nspawn --oci OCI-SPEC.json 并讓 systemd 團(tuán)隊(duì)認(rèn)可和提供支持,那么CRI-O、Docker 和 Podman 等容器管理工具將可以像使用 runc 和 Clear Container/runV (Kata) 那樣使用這個(gè)新的底層運(yùn)行時(shí)。(目前我的團(tuán)隊(duì)沒(méi)有人參與這方面的工作。)
總結(jié)如下,在 3-4 年前,上游開(kāi)發(fā)者打算編寫(xiě)一個(gè)底層的 golang 工具用于啟動(dòng)容器,最終這個(gè)工具就是 runc。那時(shí)開(kāi)發(fā)者有一個(gè)使用 C 編寫(xiě)的 lxc 工具,在 runc 開(kāi)發(fā)后,他們很快轉(zhuǎn)向 runc。我很確信,當(dāng)決定構(gòu)建 libcontainer 時(shí),他們對(duì) systemd-nspawn 或其它非原生(即不使用 golang)的運(yùn)行 namespaces 隔離的容器的方式都不感興趣。


