本文已發布超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

在 Kubernetes 中介紹容器運行時介面 (CRI)

編者註: 這篇文章是 Kubernetes 1.5 新功能系列深入文章 的一部分

在 Kubernetes 節點的最底層,是負責啟動和停止容器的軟體。我們稱之為「容器運行時」。最廣為人知的容器運行時是 Docker,但它並非孤軍奮戰。事實上,容器運行時領域一直在快速發展。為了使 Kubernetes 更具擴展性,我們一直在為 Kubernetes 中的容器運行時開發新的外掛程式 API,稱為「CRI」。

什麼是 CRI?為什麼 Kubernetes 需要它?

每個容器運行時都有自己的優勢,許多使用者都要求 Kubernetes 支援更多的運行時。在 Kubernetes 1.5 版本中,我們很榮幸推出 容器運行時介面 (Container Runtime Interface, CRI) -- 一個外掛程式介面,使 kubelet 能夠使用各種容器運行時,而無需重新編譯。CRI 由 protocol buffersgRPC API,以及 函式庫 組成,並有額外的規範和工具正在積極開發中。CRI 在 Kubernetes 1.5 中以 Alpha 版本發布。

支援可互換的容器運行時在 Kubernetes 中並不是一個新概念。在 1.3 版本中,我們宣布了 rktnetes 專案,以啟用 rkt 容器引擎 作為 Docker 容器運行時的替代方案。然而,Docker 和 rkt 都透過內部且不穩定的介面直接且深入地整合到 kubelet 原始碼中。這種整合過程需要深入了解 Kubelet 內部結構,並為 Kubernetes 社群帶來巨大的維護負擔。這些因素為新興的容器運行時形成了很高的進入門檻。透過提供明確定義的抽象層,我們消除了障礙,並允許開發人員專注於建置他們的容器運行時。這是朝向真正實現可插拔容器運行時並建構更健康的生態系統邁出的一小步,但卻是重要的一步。

CRI 概述
Kubelet 使用 gRPC 框架透過 Unix socket 與容器運行時(或運行時的 CRI shim)進行通訊,其中 kubelet 作為客戶端,CRI shim 作為伺服器。

protocol buffers API 包含兩個 gRPC 服務,ImageService 和 RuntimeService。ImageService 提供 RPC 以從儲存庫拉取映像、檢查和移除映像。RuntimeService 包含管理 Pod 和容器生命週期的 RPC,以及與容器互動的呼叫(exec/attach/port-forward)。管理映像和容器的單體容器運行時(例如,Docker 和 rkt)可以使用單個 socket 同時提供這兩種服務。socket 可以在 Kubelet 中透過 --container-runtime-endpoint 和 --image-service-endpoint 標誌設定。
Pod 和容器生命週期管理

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 中,這是透過啟動 pod 級別 cgroup 中的所有進程來實現的,kubelet 創建該 cgroup 並將其傳遞給運行時。

在啟動 pod 之前,kubelet 呼叫 RuntimeService.RunPodSandbox 以建立環境。這包括為 pod 設定網路(例如,分配 IP)。一旦 PodSandbox 處於活動狀態,就可以獨立建立/啟動/停止/移除個別容器。若要刪除 pod,kubelet 將在停止和移除 PodSandbox 之前停止並移除容器。

Kubelet 負責透過 RPC 管理容器的生命週期、執行容器生命週期 hook 和存活/就緒探針,同時遵守 pod 的重新啟動策略。

為什麼是命令式的以容器為中心的介面?

Kubernetes 具有聲明式 API 和 Pod 資源。我們考慮的一種可能的設計是讓 CRI 在其抽象中使用聲明式 Pod 物件,讓容器運行時可以自由地實作和執行自己的控制邏輯以達到期望的狀態。這將大大簡化 API,並使 CRI 能夠與更廣泛的運行時協同工作。我們在設計階段的早期討論了這種方法,但由於幾個原因而決定反對。首先,kubelet 中有許多 Pod 級別的功能和特定機制(例如,crash-loop backoff 邏輯),所有運行時都必須重新實作,這將是一個沉重的負擔。其次,更重要的是,Pod 規範仍在快速發展(並且仍在發展)。只要 kubelet 直接管理容器,許多新功能(例如,init 容器)就不需要對底層容器運行時進行任何更改。CRI 採用命令式的容器級別介面,以便運行時可以共享這些通用功能,以獲得更好的開發速度。這並不意味著我們偏離了「level triggered」哲學 - kubelet 負責確保實際狀態被驅動到聲明狀態。

Exec/attach/port-forward 請求

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 提供了功能(例如 kubectl exec/attach/port-forward),供使用者與 pod 及其中的容器互動。如今,Kubelet 透過調用容器運行時的原生方法呼叫或使用節點上可用的工具(例如,nsenter 和 socat)來支援這些功能。使用節點上的工具不是一種可移植的解決方案,因為大多數工具都假設 pod 使用 Linux 命名空間進行隔離。在 CRI 中,我們在 API 中明確定義了這些呼叫,以允許運行時特定的實作。

今天 kubelet 實作的另一個潛在問題是 kubelet 處理所有串流請求的連線,因此它可能成為節點上網路流量的瓶頸。在設計 CRI 時,我們納入了此回饋,以允許運行時消除中間人。容器運行時可以在請求時啟動單獨的串流伺服器(並且可以潛在地將資源使用情況計入 pod!),並將伺服器的位置返回給 kubelet。然後,Kubelet 將此資訊返回給 Kubernetes API 伺服器,後者直接開啟與運行時提供的伺服器的串流連線,並將其連線到用戶端。

本部落格文章未涵蓋 CRI 的許多其他方面。請參閱 設計文件和提案 列表以了解所有詳細資訊。

目前狀態

儘管 CRI 仍處於早期階段,但已經有幾個專案正在開發中,以使用 CRI 整合容器運行時。以下是一些範例

如果您有興趣嘗試這些替代運行時,您可以追蹤各個儲存庫以獲取最新進度和說明。

對於有興趣整合新容器運行時的開發人員,請參閱 開發人員指南,了解 API 的已知限制和問題。我們正在積極採納早期開發人員的回饋以改進 API。開發人員應該預期偶爾會出現 API 破壞性變更(畢竟它是 Alpha 版本)。

嘗試新的 CRI-Docker 整合

Kubelet 尚未預設使用 CRI,但我們正在積極努力實現這一目標。第一步是使用 CRI 將 Docker 與 kubelet 重新整合。在 1.5 版本中,我們擴展了 kubelet 以支援 CRI,並為 Docker 新增了內建的 CRI shim。這允許 kubelet 代表 Docker 啟動 gRPC 伺服器。若要試用新的 kubelet-CRI-Docker 整合,您只需使用 --feature-gates=StreamingProxyRedirects=true 啟動 Kubernetes API 伺服器以啟用新的串流重新導向功能,然後使用 --experimental-cri=true 啟動 kubelet。

除了少數 缺少的功能 外,新的整合已持續通過主要的端對端測試。我們計劃很快擴大測試覆蓋範圍,並希望鼓勵社群報告任何問題,以協助過渡。

搭配 Minikube 的 CRI

如果您想試用新的整合,但還沒有時間在雲端中啟動新的測試叢集,minikube 是一個快速啟動本地叢集的好工具。在開始之前,請按照 說明 下載並安裝 minikube。

  1. 檢查可用的 Kubernetes 版本,並選擇最新的 1.5.x 版本。我們將以 v1.5.0-beta.1 為例。
$ minikube get-k8s-versions
  1. 啟動具有內建 docker CRI 整合的 minikube 叢集。
$ 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.googleapis.com/minikube/iso/buildroot/minikube-v0.0.6.iso

「--extra-config=kubelet.EnableCRI=true」 啟用 kubelet 中的 CRI 實作。「--network-plugin=kubenet」和「--extra-config=kubelet.PodCIDR=10.180.1.0/24」 將網路外掛程式設定為 kubenet,並確保將 PodCIDR 指派給節點。或者,您可以使用 cni 外掛程式,它不依賴 PodCIDR。「--iso-url」設定 ISO 映像檔,供 minikube 用於啟動節點。範例中使用的映像檔

  1. 檢查 minikube 日誌以確認 CRI 已啟用。
$ minikube logs | grep EnableCRI

I1209 01:48:51.150789    3226 localkube.go:116] Setting EnableCRI to true on kubelet.
  1. 建立 Pod 並檢查其狀態。您應該會看到「SandboxReceived」事件,證明 Kubelet 正在使用 CRI!
$ kubectl run foo --image=gcr.io/google\_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 中獲得解決。 _

社群

CRI 正由 Kubernetes SIG-Node 社群積極開發和維護。我們很樂意聽取您的意見。若要加入社群