Kubernetes節點的底層由一個叫做“容器運行時”的軟件進行支撐,它負責比如啟停容器這樣的事情。廣為人知的容器運行時當屬Docker,但它不是的。事實上,容器運行時這個領域發展迅速。為了使Kubernetes的擴展變得更容易,我們一直在打磨支持容器運行時的K8s插件API:容器運行時接口(Container Runtime Interface, CRI)。
每種容器運行時各有所長,許多用戶都希望Kubernetes支持更多的運行時。在Kubernetes 1.5發布版里,我們引入了CRI–一個能讓kubelet無需編譯就可以支持多種容器運行時的插件接口。CRI包含了一組protocol buffers,gRPC API,相關的庫,以及在活躍開發下的額外規范和工具。CRI目前是Alpha版本。
支持可替換的容器運行時在Kubernetes中概念中并非首次。在1.3發布版里,我們介紹了rktnetes項目,它可以讓rkt容器引擎作為Docker容器運行時的一個備選。然而,不管是Docker還是Rkt都需要通過內部、不太穩定的接口直接集成到kubelet的源碼中。這樣的集成過程要求十分熟悉kubelet內部原理,并且還會在Kubernetes社區引發巨大的維護反響。這些因素都在為容器運行時的初期造成了巨大的困難。我們通過提供一個清晰定義的抽象層消除了這些障礙,開發者可以專注于構建他們的容器運行時。這是很小的一步,但對于真正提供可插拔的容器運行時和構建一個更健康的生態系統卻意義非凡。
Kubelet與容器運行時通信(或者是CRI插件填充了容器運行時)時,Kubelet就像是客戶端,而CRI插件就像對應的服務器。它們之間可以通過Unix 套接字或者gRPC框架進行通信。
protocol buffers API包含了兩個gRPC服務:ImageService和RuntimeService。ImageService提供了從鏡像倉庫拉取、查看、和移除鏡像的RPC。RuntimeSerivce包含了Pods和容器生命周期管理的RPC,以及跟容器交互的調用(exec/attach/port-forward)。一個單塊的容器運行時能夠管理鏡像和容器(例如:Docker和Rkt),并且通過同一個套接字同時提供這兩種服務。這個套接字可以在Kubelet里通過標識–container-runtime-endpoint和–image-service-endpoint進行設置。
對于Pod和容器的生命周期管理,CRI提供了下面的機制:
service RuntimeService { // Sandbox operations. rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}
rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}
rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}
rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {} // Container operations. rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}
rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}
rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}
…
}
在資源受限的隔離環境里的一組應用容器組成一個Pod。在CRI,這個環境被稱為PodSandbox。我們故意留下一些空間,讓容器運行時根據它們內部不同的原理來產生不同的PodSandbox。對于基于hypervisor的運行時,PodSandbox可能代表的是虛擬機。對于其他的,比如Docker,它可能是Linux命名空間。這個PodSandbox一定遵循著Pod的資源定義。在v1alpha1版API里,kubelet將創建pod級的cgroup限制下的一組進程,并傳遞給容器運行時,由此實現。
在Pod啟動前,kubelet調用RuntimeService.RunPodSandbox來創建環境,包括為Pod設置網絡(例如:分配IP)等。當PodSandbox啟動后,就可以分別創建/啟動/停止/移除獨立的容器。為了刪除Pod,kubelet會在停止和移除所有容器前先停止和移除PodSandbox。
Kubelet負責通過RPC來進行容器生命周期的管理,測試容器生命周期鉤子和健康/可讀性檢查,同時為Pod提供重啟策略。
Kubernetes擁有對Pod資源的聲明式API。我們認為一個可能的設計是為了使CRI能夠在它的抽象里重用這個聲明式的Pod對象,給容器運行時實現和測試達到期望狀態的邏輯的自由。這會極大地簡化API,并讓CRI可以兼容更廣泛的運行時。在早期的設計階段我們討論過這個方法,但由于幾個原因否決了它。首先,Kubelet有許多Pod級的特性和特定的技術(比如crash-loop backoff邏輯),這會成為所有運行時重新實現時的巨大負擔。其次,越來越重要的是,Pod的定義更新快速。只要kubelet直接管理容器,那么許多新特性(比如init container)不需要底層容器運行時做任何改變。CRI包含了一個必要的容器級接口,這樣運行時就可以共享這些特性,擁有更快的開發速度。當然這并不意味著我們偏離了”level triggered”哲學。kubelet負責保證實際狀態到期望狀態的變化。
service RuntimeService {
…
// ExecSync runs a command in a container synchronously.
rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {} // Exec prepares a streaming endpoint to execute a command in the container.
rpc Exec(ExecRequest) returns (ExecResponse) {} // Attach prepares a streaming endpoint to attach to a running container.
rpc Attach(AttachRequest) returns (AttachResponse) {} // PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {}
…
}
Kubernetes提供了一些用戶可以與Pod及其內部容器進行交互的特性(例如kubectl exec/attach/port-forward)。Kubelet現在通過調用容器原生的方法或使用節點上可用的工具(例如nsenter和socat)來支持這些特性。在節點上使用這些工具不是一個可移植的好辦法,因為這些工具的大部分假定Pod是通過Linux命名空間進行隔離的。在CRI,我們顯式定義了這些調用,允許特定的運行時實現。
另外一個潛在的問題是,kubelet如今的實現是kubelet處理所有的流式連接請求。所以這會給節點的網絡流量帶來瓶頸。在設計CRI的時候,我們采納了這個反饋,支持運行時防范中間人。容器運行時可以啟動一個對應請求的單獨的流服務器(甚至可能為Pod審計資源使用),并且將地址返回給Kubelet。Kubelet然后將這個信息再返回給Kubernetes API Server,它會打開直接與運行時提供的服務器相連的流連接,并將它跟客戶端連通。
這篇博文并沒有包含CRI的全部。更多細節請參考設計文檔和建議。
盡管CRI還處于早期階段,已經有不少使用CRI來集成容器運行時的項目在開發中。下面是一些列子:
如果你對上面列出的這些運行時感興趣,你可以關注這些獨立的github倉庫,獲取新的進展和說明。
對集成新的容器運行時感興趣的開發者,請參考開發指南了解這些API的限制和問題。我們會從早期的開發者那里積極采納反饋來提升API。開發者可能會遇到API的一些意外改動(畢是Alpha版)。
Kubelet至今還沒有默認使用CRI,但我們仍在積極地推動。步就是使用CRI將Docker重新集成到kubelet里。在1.5發布版里,我們擴展了Kubelet來支持CRI,并且為Docker添加了內置的CRI插件。kubelet啟動一個gRPC服務器,代表Docker。如果嘗試新的kubelet-CRI-Docker集成,你可能僅僅會使用–feature-gates=StreamingProxyRedirects=true來打開Kubernetes API Server,以啟動新的流重定向特性,并且通過設置kubelet的標識–experimental-cri=true來啟動。
除了一些欠缺的特性,新的集成可以一直通過主要的端到端測試。我們計劃盡快擴展測試的覆蓋率,并且鼓勵社區反應關于這個轉化的任何問題。
如果你想要嘗試新的集成,但是沒有時間在云上啟動一個新的測試集群。minikube是一個很棒的工具,你可以迅速在本地搭建集群。在你開始前,請閱讀說明并下載安裝Minikube:
1.檢查可用的Kubernetes版本,選擇可用的新1.5.x版本。我們使用v1.5.0-beta.1作為示例。
$ minikube get-k8s-versions
2.通過內建的Docker CRI集成啟動一個Minikube集群。 –extra-config=kubelet.EnableCRI=true開啟了kubelet的CRI實現。–network-plugin=kubenet和–extra-
config=kubelet.PodCIDR=10.180.1.0/24設置Kubenet網絡插件,保證分配給節點的PodCIDR。–iso-url設置本地節點啟動的minikube iso鏡像。
$ minikube start --kubernetes-version=v1.5.0-beta.1 --extra-config=kubelet.EnableCRI=true --network-plugin=kubenet --extra-config=kubelet.PodCIDR=10.180.1.0/24 --iso-url=http://storage.apis.com/minikube/iso/buildroot/minikube-v0.0.6.iso
3.檢查minikube日志,查看啟動CRI
$ minikube logs | grep EnableCRI
I1209 01:48:51.150789 3226 localkube.go:116] Setting EnableCRI to true on kubelet.
4.創建pod,檢查它的狀態。你應該可以看見一個”SandboxReceived”事件,證明Kubelet在使用CRI
$ kubectl run foo --image=gcr.io/_containers/pause-amd64:3.0 deployment "foo" created
$ kubectl describe pod foo ... ... From Type Reason Message ... ----------------- ----- --------------- ----------------------------- ...{default-scheduler } Normal Scheduled Successfully assigned foo-141968229-v1op9 to minikube ...{kubelet minikube} Normal SandboxReceived Pod sandbox received, it will be created.
注意:kubectl attach/exec/port-forward還不能在minikube的CRI中運行。但這會在新版本的minikube中得到改善。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。