Linux 容器演化史
Linux 容器是如何演變的
在過去幾年內,容器不僅成為了開發者們熱議的話題,還受到了企業的關注。持續增長的關注使得在它的安全性、可擴展性以及互用性等方面的需求也得以增長。滿足這些需求需要很大的工程量,下面我們講講在紅帽這樣的企業級這些工程是如何發展的。
我在 2013 年秋季第一次遇到 Docker 公司(Docker.io)的代表,那時我們在設法使 Red Hat Enterprise Linux (RHEL) 支持 Docker 容器(現在 Docker 項目的一部分已經更名為 Moby)的運行。在移植過程中,我們遇到了一些問題。處理容器鏡像分層所需的寫時拷貝(COW)文件系統成了我們第一個重大阻礙。Red Hat 最終貢獻了一些 COW 文件系統實現,包括 Device Mapper、btrf,以及 OverlayFS 的第一個版本。在 RHEL 上,我們默認使用 Device Mapper, 但是我們在 OverlayFS 上也已經取得了很大進展。
我們在用於啟動容器的工具上遇到了第二個主要障礙。那時的上游 docker 使用 LXC 工具來啟動容器,然而我們不想在 RHEL 上支持 LXC 工具集。而且在與上游 docker 合作之前,我們已經與 libvrit 團隊攜手構建了 virt-sandbox 工具,它使用 libvrit-lxc
來啟動容器。
在那時,紅帽里有員工提到一個好辦法,換掉 LXC 工具集而添加橋接器,以便 docker 守護進程通過 libvirt-lxc
與 libvirt 通訊來啟動容器。這個方案也有一些顧慮。考慮下面這個例子,使用 Docker 客戶端(docker-cli
)來啟動容器,各層調用會在容器進程(pid1OfContainer
)之前依次啟動:
docker-cli → docker-daemon → libvirt-lxc → pid1OfContainer
我不是很喜歡這個方案,因為它在啟動容器的工具與最終的容器進程之間有兩個守護進程。
我的團隊與上游 docker 開發者合作實現了一個原生的 Go 編程語言 版本的容器運行時,叫作 libcontainer。這個庫作為 [OCI 運行時規範]的最初版實現與 runc 一同發布。
docker-cli → docker-daemon @ pid1OfContainer
大多數人誤認為當他們執行一個容器時,容器進程是作為 docker-cli
的子進程運行的。實際上他們執行的是一個客戶端/服務端請求操作,容器進程是在一個完全單獨的環境作為子進程運行的。這個客戶端/服務端請求會導致不穩定性和潛在的安全問題,而且會阻礙一些實用特性的實現。舉個例子,systemd 有個叫做套接字喚醒的特性,你可以將一個守護進程設置成僅當相應的套結字被連接時才啟動。這意味著你的系統可以節約內存並按需執行服務。套結字喚醒的工作原理是 systemd 代為監聽 TCP 套結字,並在數據包到達套結字時啟動相應的服務。一旦服務啟動完畢,systemd 將套結字交給新啟動的守護進程。如果將守護進程運行在基於 docker 的容器中就會出現問題。systemd 的 unit 文件通過 Docker CLI 執行容器,然而這時 systemd 卻無法簡單地經由 Docker CLI 將套結字轉交給 Docker 守護進程。
類似這樣的問題讓我們意識到我們需要一個運行容器的替代方案。
容器編排問題
上游的 docker 項目簡化了容器的使用過程,同時也是一個絕佳的 Linux 容器學習工具。你可以通過一條簡單的命令快速地體驗如何啟動一個容器,例如運行 docker run -ti fedora sh
然後你就立即處於一個容器之中。
當開始把許多容器組織成一個功能更為強大的應用時,你才能體會到容器真正的能力。但是問題在於伴隨多容器應用而來的高複雜度使得簡單的 Docker 命令無法勝任編排工作。你要如何管理容器應用在有限資源的集群節點間的布局與編排?如何管理它們的生命周期等等?
在第一屆 DockerCon,至少有 7 種不同的公司/開源項目展示了其容器的編排方案。紅帽演示了 OpenShift 的 geard 項目,它基於 OpenShift v2 的容器(叫作 gears)。紅帽覺得我們需要重新審視容器編排,而且可能要與開源社區的其他人合作。
Google 則演示了 Kubernetes 容器編排工具,它來源於 Google 對其自內部架構進行編排時所積累的知識經驗。OpenShift 決定放棄 Gear 項目,開始和 Google 一同開發 Kubernetes。 現在 Kubernetes 是 GitHub 上最大的社區項目之一。
Kubernetes
Kubernetes 原先被設計成使用 Google 的 lmctfy 容器運行時環境來完成工作。在 2014 年夏天,lmctfy 兼容了 docker。Kubernetes 還會在 kubernetes 集群的每個節點運行一個 kubelet 守護進程,這意味著原先使用 docker 1.8 的 kubernetes 工作流看起來是這樣的:
kubelet → dockerdaemon @ PID1
回退到了雙守護進程的模式。
然而更糟糕的是,每次 docker 的新版本發布都使得 kubernetes 無法工作。Docker 1.10 切換鏡像底層存儲方案導致所有鏡像重建。而 Docker 1.11 開始使用 runc
來啟動鏡像:
kubelet → dockerdaemon @ runc @PID1
Docker 1.12 則增加了一個容器守護進程用於啟動容器。其主要目的是為了支持 Docker Swarm (Kubernetes 的競爭者之一):
kubelet → dockerdaemon → containerd @runc @ pid1
如上所述,每一次 docker 發布都破壞了 Kubernetes 的功能,這也是為什麼 Kubernetes 和 OpenShift 請求我們為他們提供老版本 Docker 的原因。
現在我們有了一個三守護進程的系統,只要任何一個出現問題,整個系統都將崩潰。
走向容器標準化
CoreOS、rkt 和其它替代運行時
因為 docker 運行時帶來的問題,幾個組織都在尋求一個替代的運行時。CoreOS 就是其中之一。他們提供了一個 docker 容器運行時的替代品,叫 rkt (rocket)。他們同時還引入一個標準容器規範,稱作 appc (App Container)。從根本上講,他們是希望能使得所有人都使用一個標準規範來管理容器鏡像中的應用。
這一行為為標準化工作樹立了一面旗幟。當我第一次開始和上游 docker 合作時,我最大的擔憂就是最終我們會分裂出多個標準。我不希望類似 RPM 和 DEB 之間的戰爭影響接下來 20 年的 Linux 軟體部署。appc 的一個成果是它說服了上游 docker 與開源社區合作創建了一個稱作 開放容器計劃 (OCI) 的標準團體。
OCI 已經著手制定兩個規範:
OCI 運行時規範:OCI 運行時規範「旨在規範容器的配置、執行環境以及生命周期」。它定義了容器的磁碟存儲,描述容器內運行的應用的 JSON 文件,容器的生成和執行方式。上游 docker 貢獻了 libcontainer 並構建了 runc 作為 OCI 運行時規範的默認實現。
OCI 鏡像文件格式規範:鏡像文件格式規範主要基於上游 docker 所使用的鏡像格式,定義了容器倉庫中實際存儲的容器鏡像格式。該規範使得應用開發者能為應用使用單一的標準化格式。一些 appc 中描述的概念被加入到 OCI 鏡像格式規範中得以保留。這兩份規範 1.0 版本的發布已經臨近(LCTT 譯註:已經發布)。上游 docker 已經同意在 OCI 鏡像規範定案後支持該規範。Rkt 現在既支持運行 OCI 鏡像也支持傳統的上游 docker 鏡像。
OCI 通過為工業界提供容器鏡像與運行時標準化的環境,幫助在工具與編排領域解放創新的力量。
抽象運行時介面
得益於標準化工作, Kubernetes 編排領域也有所創新。作為 Kubernetes 的一大支持者,CoreOS 提交了一堆補丁,使 Kubernetes 除了 docker 引擎外還能通過 rkt 運行容器並且與容器通訊。Google 和 Kubernetes 上游預見到增加這些補丁和將來可能添加的容器運行時介面將給 Kubernetes 帶來的代碼複雜度,他們決定實現一個叫作 容器運行時介面 (CRI) 的 API 協議規範。於是他們將 Kubernetes 由原來的直接調用 docker 引擎改為調用 CRI,這樣任何人都可以通過實現伺服器端的 CRI 來創建支持 Kubernetes 的容器運行時。Kubernetes 上游還為 CRI 開發者們創建了一個大型測試集以驗證他們的運行時對 Kubernetes 的支持情況。開發者們還在努力地移除 Kubernetes 對 docker 引擎的調用並將它們隱藏在一個叫作 docker-shim 的薄抽象層後。
容器工具的創新
伴隨 skopeo 而來的容器倉庫創新
幾年前我們正與 Atomic 項目團隊合作構建 atomic CLI。我們希望實現一個功能,在鏡像還在鏡像倉庫時查看它的細節。在那時,查看倉庫中的容器鏡像相關 JSON 文件的唯一方法是將鏡像拉取到本地伺服器再通過 docker inspect
來查看 JSON 文件。這些鏡像可能會很大,上至幾個 GiB。為了允許用戶在不拉取鏡像的情況下查看鏡像細節,我們希望在 docker inspect
介面添加新的 --remote
參數。上游 docker 拒絕了我們的代碼拉取請求(PR),告知我們他們不希望將 Docker CLI 複雜化,我們可以構建我們自己的工具去實現相同的功能。
我們的團隊在 Antonio Murdaca 的領導下執行這個提議,構建了 skopeo。Antonio 沒有止步於拉取鏡像相關的 JSON 文件,而是決定實現一個完整的協議,用於在容器倉庫與本地主機之間拉取與推送容器鏡像。
skopeo 現在被 atomic CLI 大量用於類似檢查容器更新的功能以及 atomic 掃描 當中。Atomic 也使用 skopeo 取代上游 docker 守護進程拉取和推送鏡像的功能。
Containers/image
我們也曾和 CoreOS 討論過在 rkt 中使用 skopeo 的可能,然而他們表示不希望運行一個外部的協助程序,但是會考慮使用 skopeo 所使用的代碼庫。於是我們決定將 skopeo 分離為一個代碼庫和一個可執行程序,創建了 image 代碼庫。
containers/images 代碼庫和 skopeo 被幾個其它上游項目和雲基礎設施工具所使用。Skopeo 和 containers/image 已經支持 docker 和多個存儲後端,而且能夠在容器倉庫之間移動容器鏡像,還擁有許多酷炫的特性。skopeo 的一個優點是它不需要任何守護進程的協助來完成任務。Containers/image 代碼庫的誕生使得類似容器鏡像簽名等增強功能得以實現。
鏡像處理與掃描的創新
我在前文提到 atomic CLI。我們構建這個工具是為了給容器添加不適合 docker CLI 或者我們無法在上游 docker 中實現的特性。我們也希望獲得足夠靈活性,將其用於開發額外的容器運行時、工具和存儲系統。Skopeo 就是一例。
我們想要在 atomic 實現的一個功能是 atomic mount
。從根本上講,我們希望從 Docker 鏡像存儲(上游 docker 稱之為 graph driver)中獲取內容,把鏡像掛在到某處,以便用工具來查看該鏡像。如果你使用上游的 docker,查看鏡像內容的唯一方法就是啟動該容器。如果其中有不可信的內容,執行容器中的代碼來查看它會有潛在危險。通過啟動容器查看鏡像內容的另一個問題是所需的工具可能沒有被包含在容器鏡像當中。
大多數容器鏡像掃描器遵循以下流程:它們連接到 Docker 的套結字,執行一個 docker save
來創建一個 tar 打包文件,然後在磁碟上分解這個打包文件,最後查看其中的內容。這是一個很慢的過程。
通過 atomic mount
,我們希望直接使用 Docker graph driver 掛載鏡像。如果 docker 守護進程使用 device mapper,我們將掛載這個設備。如果它使用 overlay,我們會掛載 overlay。這個操作很快而且滿足我們的需求。現在你可以執行:
# atomic mount fedora /mnt
# cd /mnt
然後開始探查內容。你完成相應工作後,執行:
# atomic umount /mnt
我們在 atomic scan
中使用了這一特性,實現了一個快速的容器掃描器。
工具協作的問題
其中一個嚴重的問題是 atomic mount
隱式地執行這些工作。Docker 守護進程不知道有另一個進程在使用這個鏡像。這會導致一些問題(例如,如果你先掛載了 Fedora 鏡像,然後某個人執行了 docker rmi fedora
命令,docker 守護進程移除鏡像時就會產生奇怪的操作失敗,同時報告說相應的資源忙碌)。Docker 守護進程可能因此進入一個奇怪的狀態。
容器存儲系統
為了解決這個問題,我們開始嘗試將從上游 docker 守護進程剝離出來的 graph driver 代碼拉取到我們的代碼庫中。Docker 守護進程在內存中為 graph driver 完成所有鎖的獲取。我們想要將這些鎖操作轉移到文件系統中,這樣我們可以支持多個不同的進程來同時操作容器的存儲系統,而不用通過單一的守護進程。
我們創建了 containers/storage 項目,實現了容器運行、構建、存儲所需的所有寫時拷貝(COW)特性,同時不再需要一個單一進程來控制和監控這個過程(也就是不需要守護進程)。現在 skopeo 以及其它工具和項目可以直接利用鏡像的存儲系統。其它開源項目也開始使用 containers/storage,在某些時候,我們也會把這些項目合併回上游 docker 項目。
駛向創新
當 Kubernetes 在一個節點上使用 docker 守護進程運行容器時會發生什麼?首先,Kubernetes 執行一條類似如下的命令:
kubelet run nginx -image=nginx
這個命令告訴 kubelet 在節點上運行 NGINX 應用程序。kubelet 調用 CRI 請求啟動 NGINX 應用程序。在這時,實現了 CRI 規範的容器運行時必須執行以下步驟:
- 檢查本地是否存在名為
nginx
的容器。如果沒有,容器運行時會在容器倉庫中搜索標準的容器鏡像。 - 如果鏡像不存在於本地,從容器倉庫下載到本地系統。
- 使用容器存儲系統(通常是寫時拷貝存儲系統)解析下載的容器鏡像並掛載它。
- 使用標準的容器運行時執行容器。
讓我們看看上述過程使用到的特性:
- OCI 鏡像格式規範定義了容器倉庫存儲的標準鏡像格式。
- Containers/image 代碼庫實現了從容器倉庫拉取鏡像到容器主機所需的所有特性。
- Containers/storage 提供了在寫時拷貝的存儲系統上探查並處理 OCI 鏡像格式的代碼庫。
- OCI 運行時規範以及
runc
提供了執行容器的工具(同時也是 docker 守護進程用來運行容器的工具)。
這意味著我們可以利用這些工具來使用容器,而無需一個大型的容器守護進程。
在中等到大規模的基於 DevOps 的持續集成/持續交付環境下,效率、速度和安全性至關重要。只要你的工具遵循 OCI 規範,開發者和執行者就能在持續集成、持續交付到生產環境的自動化中自然地使用最佳的工具。大多數的容器工具被隱藏在容器編排或上層容器平台技術之下。我們預想著有朝一日,運行時和鏡像工具的選擇會變成容器平台的一個安裝選項。
系統(獨立)容器
在 Atomic 項目中我們引入了 原子主機 ,一種新的操作系統構建方式:所有的軟體可以被「原子地」升級並且大多數應用以容器的形式運行在操作系統中。這個平台的目的是證明將來所有的軟體都能部署在 OCI 鏡像格式中並且使用標準協議從容器倉庫中拉取,然後安裝到系統上。用容器鏡像的形式發布軟體允許你以不同的速度升級應用程序和操作系統。傳統的 RPM/yum/DNF 包分發方式把應用更新鎖定在操作系統的生命周期中。
在以容器部署基礎設施時多數會遇到一個問題——有時一些應用必須在容器運行時執行之前啟動。我們看一個使用 docker 的 Kubernetes 的例子:Kubernetes 為了將 pods 或者容器部署在獨立的網路中,要求先建立一個網路。現在默認用於創建網路的守護進程是 flanneld,而它必須在 docker 守護進程之前啟動,以支持 docker 網路介面來運行 Kubernetes 的 pods。而且,flanneld 使用 etcd 來存儲數據,這個守護進程必須在 flanneld 啟動之前運行。
如果你想把 etcd 和 flanneld 部署到容器鏡像中,那就陷入了雞與雞蛋的困境中。我們需要容器運行時來啟動容器化的應用,但這些應用又需要在容器運行時之前啟動。我見過幾個取巧的方法嘗試解決這個問題,但這些方法都不太乾淨利落。而且 docker 守護進程當前沒有合適的方法來配置容器啟動的優先順序順序。我見過一些提議,但它們看起來和 SysVInit 所使用的啟動服務的方式相似(我們知道它帶來的複雜度)。
systemd
用 systemd 替代 SysVInit 的原因之一就是為了處理服務啟動的優先順序和順序,我們為什麼不充分利用這種技術呢?在 Atomic 項目中我們決定在讓它在沒有容器運行時的情況下也能啟動容器,尤其是在系統啟動早期。我們增強了 atomic CLI 的功能,讓用戶可以安裝容器鏡像。當你執行 atomic install --system etc
,它將利用 skopeo 從外部的容器倉庫拉取 etcd 的 OCI 鏡像,然後把它分解(擴展)為 OSTree 底層存儲。因為 etcd 運行在生產環境中,我們把鏡像處理為只讀。接著 atomic
命令抓取容器鏡像中的 systemd 的 unit 文件模板,用它在磁碟上創建 unit 文件來啟動鏡像。這個 unit 文件實際上使用 runc
來在主機上啟動容器(雖然 runc
不是必需的)。
執行 atomic install --system flanneld
時會進行相似的過程,但是這時 flanneld 的 unit 文件中會指明它依賴 etcd。
在系統引導時,systemd 會保證 etcd 先於 flanneld 運行,並且直到 flanneld 啟動完畢後再啟動容器運行時。這樣我們就能把 docker 守護進程和 Kubernetes 部署到系統容器當中。這也意味著你可以啟動一台原子主機或者使用傳統的基於 rpm 的操作系統,讓整個容器編排工具棧運行在容器中。這是一個強大的特性,因為用戶往往希望改動容器主機時不受這些組件影響。而且,它保持了主機的操作系統的佔用最小化。
大家甚至討論把傳統的應用程序部署到獨立/系統容器或者被編排的容器中。設想一下,可以用 atomic install --system httpd
命令安裝一個 Apache 容器,這個容器可以和用 RPM 安裝的 httpd 服務以相同的方式啟動(systemctl start httpd
,區別是這個容器 httpd 運行在一個容器中)。存儲系統可以是本地的,換言之,/var/www
是從宿主機掛載到容器當中的,而容器監聽著本地網路的 80 埠。這表明了我們可以在不使用容器守護進程的情況下將傳統的負載組件部署到一個容器中。
構建容器鏡像
在我看來,在過去 4 年來容器發展方面最讓人失落的是缺少容器鏡像構建機制上的創新。容器鏡像不過是將一些 tar 包文件與 JSON 文件一起打包形成的文件。基礎鏡像則是一個 rootfs 與一個描述該基礎鏡像的 JSON 文件。然後當你增加鏡像層時,層與層之間的差異會被打包,同時 JSON 文件會做出相應修改。這些鏡像層與基礎文件一起被打包,共同構成一個容器鏡像。
現在幾乎所有人都使用 docker build
與 Dockerfile 格式來構建鏡像。上游 docker 已經在幾年前停止了接受修改或改進 Dockerfile 格式的拉取請求(PR)了。Dockerfile 在容器的演進過程中扮演了重要角色,開發者和管理員/運維人員可以通過簡單直接的方式來構建鏡像;然而我覺得 Dockerfile 就像一個簡陋的 bash 腳本,還帶來了一些尚未解決的問題,例如:
- 使用 Dockerfile 創建容器鏡像要求運行著 Docker 守護進程。
- 沒有可以獨立於 docker 命令的標準工具用於創建 OCI 鏡像。
- 甚至類似
ansible-containers
和 OpenShift S2I (Source2Image) 的工具也在底層使用docker-engine
。
- Dockerfile 中的每一行都會創建一個新的鏡像,這有助於創建容器的開發過程,這是因為構建工具能夠識別 Dockerfile 中的未改動行,復用已經存在的鏡像從而避免了未改動行的重複執行。但這個特性會產生大量的鏡像層。
- 因此,不少人希望構建機制能壓制鏡像消除這些鏡像層。我猜想上游 docker 最後應該接受了一些提交滿足了這個需求。
- 要從受保護的站點拉取內容到容器鏡像,你往往需要某種密鑰。比如你為了添加 RHEL 的內容到鏡像中,就需要訪問 RHEL 的證書和訂閱。
- 這些密鑰最終會被以層的方式保存在鏡像中。開發者要費很大工夫去移除它們。
- 為了允許在 docker 構建過程中掛載數據卷,我們在我們維護的 projectatomic/docker 中加入了
-v volume
選項,但是這些修改沒有被上游 docker 接受。
- 構建過程的中間產物最終會保留在容器鏡像中,所以儘管 Dockerfile 易於學習,當你想要了解你要構建的鏡像時甚至可以在筆記本上構建容器,但它在大規模企業環境下還不夠高效。然而在自動化容器平台下,你應該不會關心用於構建 OCI 鏡像的方式是否高效。
Buildah 起航
在 DevConf.cz 2017,我讓我們團隊的 Nalin Dahyabhai 考慮構建被我稱為 containers-coreutils
的工具,它基本上就是基於 containers/storage 和 containers/image 庫構建的一系列可以使用類似 Dockerfile 語法的命令行工具。Nalin 為了取笑我的波士頓口音,決定把它叫做 buildah。我們只需要少量的 buildah 原語就可以構建一個容器鏡像:
- 最小化 OS 鏡像、消除不必要的工具是主要的安全原則之一。因為黑客在攻擊應用時需要一些工具,如果類似
gcc
,make
,dnf
這樣的工具根本不存在,就能阻礙攻擊者的行動。 - 減小容器的體積總是有益的,因為這些鏡像會通過互聯網拉取與推送。
- 使用 Docker 進行構建的基本原理是在容器構建的根目錄下利用命令安裝或編譯軟體。
- 執行
run
命令要求所有的可執行文件都包含在容器鏡像內。只是在容器鏡像中使用dnf
就需要完整的 Python 棧,即使在應用中從未使用到 Python。 ctr=$(buildah from fedora)
:- 使用 containers/image 從容器倉庫拉取 Fedora 鏡像。
- 返回一個容器 ID (
ctr
)。
mnt=$(buildah mount $ctr)
:- 掛載新建的容器鏡像(
$ctr
). - 返回掛載點路徑。
- 現在你可以使用掛載點來寫入內容。
- 掛載新建的容器鏡像(
dnf install httpd –installroot=$mnt
:- 你可以使用主機上的命令把內容重定向到容器中,這樣你可以把密鑰保留在主機而不導入到容器內,同時構建所用的工具也僅僅存在於主機上。
- 容器內不需要包含
dnf
或者 Python 棧,除非你的應用用到它們。
cp foobar $mnt/dir
:- 你可以使用任何 bash 中可用的命令來構造鏡像。
buildah commit $ctr
:- 你可以隨時創建一個鏡像層,鏡像的分層由用戶而不是工具來決定。
buildah config --env container=oci --entrypoint /usr/bin/httpd $ctr
:- Buildah 支持所有 Dockerfile 的命令。
buildah run $ctr dnf -y install httpd
:- Buildah 支持
run
命令,但它是在一個鎖定的容器內利用runc
執行命令,而不依賴容器運行時守護進程。
- Buildah 支持
buildah build-using-dockerfile -f Dockerfile .
:- 我們希望將移植類似
ansible-containers
和 OpenShift S2I 這樣的工具,改用buildah
以去除對容器運行時守護進程的依賴。 - 使用與生產環境相同的容器運行時構建容器鏡像會遇到另一個大問題。為了保證安全性,我們需要把許可權限制到支持容器構建與運行所需的最小許可權。構建容器比起運行容器往往需要更多額外的許可權。舉個例子,我們默認允許
mknod
許可權,這會允許進程創建設備節點。有些包的安裝會嘗試創建設備節點,然而在生產環境中的應用幾乎都不會這麼做。如果默認移除生產環境中容器的mknod
特權會讓系統更為安全。 - 另一個例子是,容器鏡像默認是可讀寫的,因為安裝過程意味著向
/usr
存入軟體包。然而在生產環境中,我強烈建議把所有容器設為只讀模式,僅僅允許它們寫入 tmpfs 或者是掛載了數據卷的目錄。通過分離容器的構建與運行環境,我們可以更改這些默認設置,提供一個更為安全的環境。 - 當然,buildah 可以使用 Dockerfile 構建容器鏡像。
- 我們希望將移植類似
CRI-O :一個 Kubernetes 的運行時抽象
Kubernetes 添加了 容器運行時介面 (CRI)介面,使 pod 可以在任何運行時上工作。雖然我不是很喜歡在我的系統上運行太多的守護進程,然而我們還是加了一個。我的團隊在 Mrunal Patel 的領導下於 2016 年後期開始構建 [CRI-O] 守護進程。這是一個用來運行 OCI 應用程序的 OCI 守護進程。理論上,將來我們能夠把 CRI-O 的代碼直接併入 kubelet 中從而消除這個多餘的守護進程。
不像其它容器運行時,CRI-O 的唯一目的就只是為了滿足 Kubernetes 的需求。記得前文描述的 Kubernetes 運行容器的條件。
Kubernetes 傳遞消息給 kubelet 告知其運行 NGINX 伺服器:
- kubelet 喚醒 CRI-O 並告知它運行 NGINX。
- CRI-O 回應 CRI 請求。
- CRI-O 在容器倉庫查找 OCI 鏡像。
- CRI-O 使用 containers/image 從倉庫拉取鏡像到主機。
- CRI-O 使用 containers/storage 解壓鏡像到本地磁碟。
- CRI-O 按照 OCI 運行時規範(通常使用
runc
)啟動容器。如前文所述,Docker 守護進程也同樣使用runc
啟動它的容器。 - 按照需要,kubelet 也可以使用替代的運行時啟動容器,例如 Clear Containers
runcv
。
CRI-O 旨在成為穩定的 Kubernetes 運行平台。只有通過完整的 Kubernetes 測試集後,新版本的 CRI-O 才會被推出。所有提交到 https://github.com/Kubernetes-incubator/cri-o 的拉取請求都會運行完整的 Kubernetes 測試集。沒有通過測試集的拉取請求都不會被接受。CRI-O 是完全開放的,我們已經收到了來自 Intel、SUSE、IBM、Google、Hyper.sh 等公司的代碼貢獻。即使不是紅帽想要的特性,只要通過一定數量維護者的同意,提交給 CRI-O 的補丁就會被接受。
小結
我希望這份深入的介紹能夠幫助你理解 Linux 容器的演化過程。Linux 容器曾經陷入一種各自為營的困境,Docker 建立起了鏡像創建的事實標準,簡化了容器的使用工具。OCI 則意味著業界在核心鏡像格式與運行時方面的合作,這促進了工具在自動化效率、安全性、高可擴展性、易用性方面的創新。容器使我們能夠以一種新奇的方式部署軟體——無論是運行於主機上的傳統應用還是部署在雲端的微服務。而在許多方面,這一切還僅僅是個開始。
作者簡介:
Daniel J Walsh - Daniel 有將近 30 年的計算機安全領域工作經驗。他在 2001 年 8 月加入 Red Hat。
via: https://opensource.com/article/17/7/how-linux-containers-evolved
作者:Daniel J Walsh 譯者:haoqixu 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive