容器執行時(container runtime) 是一個既熟悉又陌生的話題。在過去的一年裡,隨著Kubernetes的進一步發展,以及CNCF和OCI 在標準化方向的努力,市面上可供選擇的容器執行時也不再只是Docker一家了。容器執行時是什麼? 市面上有哪些可選的執行時?各有什麼優缺點? 在這篇文章裡,你都會得到答案。
我們在之前關於KubeCon + CloudNativeCon概述的文章中曾經簡要提到 現在市面上已經有多個容器“執行時”(container runtime)。容器執行時的定義是:能夠基於線上獲取的映象來建立和執行容器的程式。 容器執行時領域的標準和實現正在慢慢地走向成熟: Docker在KubeCon上釋出了containerd 1.0, 幾個月前CRI-O 1.0也釋出了, 同時還有rkt也是一個執行時。 如果你正打算從0開始設計部署一個自己的容器系統或者Kubernetes叢集的話, 面對這麼多的容器執行時,你或許會感到有點迷茫。這篇文章我就會嘗試解釋什麼是容器執行時,它們是做什麼的,比較它們的特點,然後告訴你該如何選擇適合自己的。 同時本文也會介紹容器規範和標準化方面的基礎知識。
在我們探索容器執行時之前,讓我們先來看看容器到底是什麼。當一個容器被啟動時,主要會發生如下活動:
-
基於映象(image),一個容器會被創建出來。映象image就是附加一個JSON配置檔案的tar包。映象常常是巢狀的:例如Libresonic的映象是基於Tomcat的映象, 而Tomcat的映象又最終基於一個Debian的映象。這樣可以防止重覆的內容佔用空間。此例子中Debian映象(或者其他的中間映象)是上層其他映象的基礎。一個容器的映象常常可以透過使用類似docker build的命令來生成。
-
如果必要, 容器執行時會從某處下載映象,這個地方稱為registry。Registry通常是一個透過HTTP協議暴露映象的元資料和檔案以供下載的容器倉庫。 之前就只有Docker Hub獨一個registry, 但是現在基本上每個玩家都有自己的倉庫了: 比如, Red Hat 有一個倉庫服務於OpenShift專案, 微軟的Azure也有一個倉庫, Gitlab也有一個倉庫服務於它的持續整合平臺。 Registry倉庫就是docker pull/push所互動的伺服器。
-
執行時將層次化的映象解壓到支援Copy on Write(CoW)的檔案系統裡。通常這透過改寫(overlay)檔案系統來實現,所有的層次層層改寫來構成一個合併的檔案系統。 這個步驟通常不能透過命令列直接訪問,而是當執行時建立容器時會自動在後臺發生。
-
最後,執行時來實際地執行容器。 它告訴核心給容器分配合適的資源限制,建立隔離的層(為行程、網路、檔案系統等),使用各種機制的混合(包括cgroups、namespaces、capabilities、seccomp、AppArmor、SELinux等等)。 比如Docker, 當執行docker run時,它會建立並執行容器,但是底層它實際呼叫的是runC命令。
這些概念最先是在Docker的標準容器宣言[1]中描述的, 但是後來被從Docker裡面刪除了。儘管如此,標準化的努力一直在持續。Open Container Initiative(OCI)現在透過如下的一些規範涵蓋了上面所述的大部分內容:
-
映象規範(常被稱為“OCI 1.0 images”)定義了容器映象的內容
-
執行時規範( 常被稱為“CRI 1.0”或者容器執行時介面)表述了容器的”配置,執行環境和生命週期“。
-
容器網路介面(CNI)描述瞭如何在容器內部配置網路介面,不過它是在CNCF下標準化的, 而不是OCI。
這些標準在不同專案裡面的實現是不同的。比如Docker除了映象格式之外的東西基本都是相容的。 Docker早在標準化之前就有了自己的映象格式,而且它也承諾未來很快會轉換到新標準上。容器的執行時介面的實現也是不同的,因為Docker裡面並不是所有的東西都是標準化的,我們接下來會說明。
因為Docker是第一個流行的容器,我們就從它開始說起。最先,Docker使用的是LXC但是層次隔離不太完整,所以後來Docker開發了libcontainer,最後演變為了runC。接下來容器迎來了爆發,而Docker也成為容器部署的事實標準。2014年Kubernetes誕生了,它很自然地使用了Docker,因為Docker是當時唯一的容器執行時。但是,Docker是一家很有野心的公司,一直在獨立開發新的功能和工具。比如Docker Compose,與Kubernetes 同時釋出了1.0,而這兩個專案有很多的重覆的東西。儘管我們使用Kcompose這樣的工具可以讓它們兩個互相互動,但是Docker給大家的感覺是:這個專案太大,做了太多的事情。 這導致CoreOS釋出了一個簡單,獨立的執行時rkt。 當時rkt這樣描述的:
Docker現在正在構建工具做了很多事情,比如啟動雲伺服器,建集群系統,還有一堆的功能:構建映象,執行映象,上傳下載,甚至做改寫網路,最後都編譯進一個大的執行時裡面,當作你伺服器裡面的root程式。標準化容器宣言早就被它刪除了。我們應該停止談論Docker容器,開始談論Docker平臺,因為它已經不再是那個我們曾經希望的,用起來簡單的構建容器的基礎元件了。
rkt的一個創新是透過appc規範來標準化容器格式,這我們在2015年曾經提到過[2]。CoreOS一直都沒有一個容器執行時介面的完整的標準的實現。到現在為止,rkt的Kubernetes相容層(rktlet)並沒透過Kubernetes的所有整合測試,仍然還在開發中。CoreOS CTO Brandon Philips在一封郵件裡面這麼說:
rkt初始是支援OCI image規範的, 但是有些地方不全。在當前支援OCI還不是那麼重要,因為在容器倉庫方面對OCI的支援才剛開始,而且Kubernetes裡面這部分也是沒有的。在rkt裡面,OCI標準的執行時規範沒有使用,消費,也沒有被處理。這是因為rkt是基於pod語意的,而容器執行時規範針對的是單個容器的執行。
儘管如此,在Red Hat容器團隊的主管Dan Walsh在一封郵件訪談中說道:在Kubernetes生態系統中的容器標準化部分,CoreOS功不可沒: “沒有CoreOS我們可能就沒有CNI, CRI可能還在OCI裡面艱難抗爭, CoreOS的成就在市場上沒有得到應有的認可”。 確實如此, Philips也說 “CNI專案和很多規範最開始都來自於rkt,最後我們將它捐給了CNCF。 CNI現在仍然在rkt中廣泛使用, 無論是內部還是使用者配置裡面”。 當前,CoreOS已經把重心轉向了構建自己的Kubernetes平臺(Tectonic)和映象釋出服務(Quay),而不是在執行時這個層面競爭。
見到這些新標準以後,Red Hat的一些人開始想他們可以構建一個更簡單的執行時,而且這個執行時僅僅為Kubernetes所用。這樣就有了“skunkworks”專案,最後定名為CRI-O, 它實現了一個最小的CRI介面。在2017 Kubecon Austin的一個演講[3]中, Walsh解釋說,“CRI-O被設計為比其他的方案都要小,遵從Unix只做一件事並把它做好的設計哲學,實現元件重用。”
根據Red Hat的CRI-O開發者Mrunal Patel在研究裡面說的, 最開始Red Hat在2016年底為它的OpenShift平臺啟動了這個專案,同時專案也得到了Intel和SUSE的支援。CRI-O與CRI規範相容,並且與OCI和Docker映象的格式也相容。它也支援校驗映象的GPG簽名。 它使用CNI的包來做網路部分,支援CNI外掛,OpenShift也用它來做軟體定義儲存層。 它支援多個CoW檔案系統,比如常見的overlay,aufs,也支援不太常見的Btrfs。
但是CRI-O最出名的特點是它支援“受信容器”和“非受信容器”的混合工作負載。比如,CRI-O可以使用Clear Containers做強隔離,這樣在多租戶配置或者執行非信任程式碼時很有用。這個功能如何整合進Kubernetes現在還不太清楚,Kubernetes現在認為所有的後端都是一樣的。
CRI-O有一個有趣的架構(見下圖,摘錄於slides[4]),它重用了很多基礎元件,比如runC來啟動容器,使用containers/image和containers/storage 軟體庫來拉取容器映象,建立容器的檔案系統(這2個庫是為skopeo專案建立的)。還有一個名為oci-runtime-tool的庫來準備容器配置。CRI-O引入了一個新的deamon來處理容器,名字為conmon。 根據Patel所說,conmon程式是“純C編寫的,用來提高穩定性和效能”,conmon負責監控,日誌,TTY分配,以及類似out-of-memory情況的雜事。
conmon需要去做所有systemd不做或者不想做的事情。但是即使CRI-O不直接使用systemd來管理容器,它也將容器分配到sytemd相容的cgroup中,這樣常規的systemd工具比如systemctl就可以看見容器資源使用情況了。因為conmon(不是CRI daemon)是容器的父行程,它允許CRI-O的部分元件重啟而不會影響容器,這樣可以保證更加平滑的升級。現在Docker部署的問題就是Docker升級需要重起所有的容器。 通常這對於Kubernetes叢集來說不是問題,但因為它可以將容器遷移來滾動升級。
CRI-O是OCI標準的實現裡面第一個透過所有Kubernetes整合測試的(拋開Docker本身)。Patel透過一個CRI-O支撐的Kubernetes叢集展現了這些功能。Dan Walsh在一篇部落格[5]裡面解釋了CRI-O與Kubernetes互動的方式:
與其他容器執行時不同,我們的第一標的是永遠不弄壞Kubernetes。為Kubernetes提供穩定可靠的容器執行時是CRI-O的唯一任務。
根據Patel所說,CRI-O現在效能與一個常規的Docker部署對比已經不相上下,但是開發團隊正在進一步最佳化效能實現超越。CRI-O 提供Debian和RPM的包,並且類似minikube、kubeadm這樣的部署工具也都支援切換到CRI-O。在現有的叢集上,切換執行時相當的直接明瞭:只需要一個環境變數來改執行時的socket(Kubernetes用它來與執行時溝通)。
CRI-O 1.0在2017年10月釋出,支援Kubernetes 1.7. 後來CRI-O 1.8、1.9相繼釋出,支援Kubernetes的1.8, 1.9(此時版本命名規則改為與Kubernetes一致)。Patel考慮在2017年11月CRI-O生產可用,在Openshift 3.7中作為beta版提供。在Openshift 3.9中讓它進步一步穩定,在3.10中成為預設的執行時,同時讓Docker作為候選的。下一步的工作包括整合新的Kata Containers的這個基於VM的執行時,增加kube-spawn的支援,支援更多類似NFS, GlusterFS的儲存後端等。 團隊也在討論如何透過支援casync或者libtorrent來最佳化多節點間的映象同步。
containerd:Docker帶API的執行時
當Redhat在做OCI的實現時,Docker也在朝標準努力,他們建立了另一個執行時,containerd。 這個新的Daemon是對Docker內部元件的一個重構以便支援OCI規範,比如執行,儲存,網路介面管理部分等。它在Docker1.12中就是一個特性了,但是直到containerd 1.0釋出時才完成, 並會成為Docker 17.12版本的一部分(Docker已經採用年+月的方式做版本號)。雖然我們稱containerd為執行時,但是它不是直接實現CRI介面,而且是由一個叫cri-containerd的獨立daemon來實現。所以containerd需要的daemon比CRI-O要多(5個, CRI-O 3個)。在寫此文的時候,cri-containerd還是beta版本,但是containerd已經在眾多的生產環境中以Docker的形式被使用了。
在KubeCon的Node SIG會議上, Stephen Day 如此表述containerd: 它被設計為一組松耦合元件的緊核心。 與CRI-O不同,containerd可以透過一個Go API支援Kubernetes生態系統以外的工作負載。不過這個API現在還沒穩定,但是containerd已經定義了一個清晰的釋出流程[6]來更新API和命令列工具。與CRI-O類似的是,containerd本身功能齊全,並透過了Kubernetes的所有測試,但是它無法與systemd的cgroup互操作。
專案的下一步是開發更多測試,在記憶體使用以及延遲上提高效能,他們也在努力提高穩定性。他們準備提供Debian和RPM的包以方便安裝, 並與minikub和kops整合等。 他們也計劃與Kata Containers更平滑的整合;runC已經能在基礎整合方面被Kata替換,但是cri-containerd的整合還沒完全實現。
所有以上這些執行時選項給社群裡面帶來了相當大的困惑。在KubeCon上,應該使用哪個runtime被反覆問到。 Kubernetes很可能從Docker轉到別的執行時,因為它不需要Docker提供的所有功能,人們擔心這種轉換會帶來相容性的問題,因為新的執行時並不是實現了Docker一模一樣的介面。比如日誌檔案,在CRI標準裡面就是不同的。有些程式直接監控Docker socket,相關操作也不是符合標準的, 在新的執行時裡面這些實現方法會不同,甚至完全沒有實現。這些都可能導致在切換到一個不同的執行時的情況下會有風險。
Kubernetes會切換到哪個執行時(如果它確定要改),這個問題也是開放的。這也直接導致了執行時之間的競爭。在KubeCon上這個問題甚至帶來了一些爭議,因為在CNCF的keynote裡面CRI-O都沒被提到。Vicent Batts, Red Hat的一名高階工程師因此在Twitter上發表不滿:
簡直有毛病,KubeCon的keynote上提到了containerd和rktlet這些CRI實現,但是根本隻字未提CRI-O,而CRI-O是Kubernetes專案中的,已經1.0並已經在生產上使用了。
健康的競爭是好的,問題在於不健康的競爭。CNCF應該更好管理好自家的專案而不是迫於壓力吹捧某些專案比其他的好。
Batts補充道, Red Hat可能正處於一個臨界點,一些應用可能開始以容器部署而不是RPM,安全擔憂(也就是安全補丁的跟蹤,在容器格式的包中是缺乏的)是這種轉換的一個阻礙。 透過Atomic專案,Red Hat看來正轉向容器為核心,但是對於Linux發行版卻有大的風險。
當我在KubeCon上問 CNCF的COO Chris Aniszczyk時,他解釋說CNCF現在的政策時優先營銷頂級專案:
類似於CRI-O,Helm的專案一定程度上是Kubernetes的一部分,因此我們可以說它們也是CNCF的一部分。我們只是沒有像我們的頂級專案那樣重點營銷而已,因為頂級專案都已經透過了CNCF TOC的標準。
他補充到, “我們希望提供幫助,我們聽到了反饋,也計劃在2018著手解決”, 同時他建議的一個解決方法是CRI-O申請畢業[7]成為CNCF的一個頂級專案。
在一個container執行時的釋出會上,Philips解釋到,社群會在取得共識後作出決定哪個執行時會成為Kubernetes的預設項。他把執行時比作瀏覽器,說可以把容器的OCI標準比做HTML5或者javascript標準:這些標準透過不同的實現得到進化發展。 他重申這種競爭是健康的,表明有更多的創新。
現在寫這些新的執行時的很多人當初都是Docker的貢獻者:Patel是OCI實現的最初維護者,後來這個實現成為了runC;Philips在啟動rkt專案前也是Docker的核心開發者。這些人與Docker開發者積極合作,標準化介面,都希望看到Kubernetes穩定和提高。如Patrick Chazenon, Docker Inc所說,“標的是為了讓容器執行時成熟穩定,讓人們厭煩在它上面做創新。“ 釋出會上開發者都為他們的成就感到高興和自豪:他們成功的建立了一個容器的互操作性規範,而且規範還在成長。
現在容器標準化領域的火熱話題已經從執行時轉向了映象釋出(例如,容器倉庫),很可能在圍繞Docker的釋出系統產生出一個標準。也有一些工作正跟蹤Linux內核的更新,比如cgroups v2。
實際情況是,每個執行時有它自己的優點:containerd有一個API,所以它可以被用來構建自己的自定義平臺;CRI-O只是一個僅針對Kubernetes的簡單執行時。Docker和rkt在另一個層面上,提供的東西比執行時要多: 比如他們也提供構建容器的方式,釋出推送到倉庫的方式等。
現在大多數的共有雲基礎架構仍然使用Docker做執行時。實際上甚至CoreOS也用Docker而不是rkt在自家的Tectonic平臺上。根據Philips所說,這是因為“我們的客戶依賴docker engine為核心的持續整合系統。相比其他的Kubernetes產品,它是被測試的最充分的。 如果其他的容器執行時提供給Kubernetes更大的提高的話,Tectnoic可能會考慮用它。
在此刻containerd和CRI-O都還是非常年輕的專案,尤其要考慮到每個專案在今年都引入了大量的新程式碼。接下來,它們需要透過與生態系統中的第三方整合測試達到成熟:比如日誌、監控、安全等等。
Philips在部落格中深入解釋了CoreOS的位置:
目前,CRI對於Kubernetes的主要優點是更好的程式碼組織和kubelet裡面更多的程式碼改寫率,這樣給程式碼更高質量,也比以前更加深入徹底的測試。儘管如此,對於幾乎所有的部署來說,我們期望Kubernetes社群在短期內仍然使用Docker Engine。
在釋出會的討論上,Patel也說了類似的話,”我們不需要Kubernetes使用者知道什麼是執行時”。說來也是,只要它工作正常,使用者根本不用關心。 而且OpenShift,Tectonic 等平臺都把執行時的決策抽象化了,它們都會自動選他們自己的最佳預設項來滿足使用者需求。所以Kubernetes到底選用哪個執行時作為預設的這個問題對於開發者來說根本不用操心,只要他們在一個共識的標準裡工作就行。在充滿衝突的世界裡面,看到這些開發者一起開誠佈公的工作本身就是難得的了。
-
https://github.com/moby/moby/commit/0db56e6c519b19ec16c6fbd12e3cee7dfa6018c5
-
https://lwn.net/Articles/631630/
-
https://kccncna17.sched.com/event/CU6T/cri-o-all-the-runtime-kubernetes-needs-and-nothing-more-mrunal-patel-red-hat
-
https://schd.ws/hosted_files/kccncna17/e8/CRI-O-Kubecon-2017.pdf
-
https://medium.com/cri-o/cri-o-support-for-kubernetes-4934830eb98e
-
https://github.com/containerd/containerd/blob/master/RELEASES.md
-
https://www.cncf.io/projects/graduation-criteria/
原文連結:https://lwn.net/Articles/741897/
本次培訓包含:Kubernetes核心概念;Kubernetes叢集的安裝配置、運維管理、架構規劃;Kubernetes元件、監控、網路;針對於Kubernetes API介面的二次開發;DevOps基本理念;微服務架構;微服務的容器化等,點選識別下方二維碼加微信好友瞭解具體培訓內容。
2月1日正式開課,還有最後3個名額,點選閱讀原文連結即可報名。
長按二維碼向我轉賬
受蘋果公司新規定影響,微信 iOS 版的贊賞功能被關閉,可透過二維碼轉賬支援公眾號。