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

在 Kubernetes 中引入容器執行時介面(CRI)

編者按:這篇博文是關於 Kubernetes 1.5 新功能系列深度文章的一部分。

在 Kubernetes 節點的最低層是負責啟動和停止容器等任務的軟體。我們稱之為“容器執行時”。最廣為人知的容器執行時是 Docker,但它並非唯一。事實上,容器執行時領域一直在快速發展。作為使 Kubernetes 更具可擴充套件性的一部分努力,我們一直在為 Kubernetes 中的容器執行時開發新的外掛 API,稱為“CRI”。

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

每個容器執行時都有其自身的優勢,許多使用者都要求 Kubernetes 支援更多的執行時。在 Kubernetes 1.5 版本中,我們很榮幸推出容器執行時介面 (CRI)——一個外掛介面,它使 kubelet 能夠使用各種容器執行時,而無需重新編譯。CRI 由協議緩衝區gRPC API以及組成,更多規範和工具正在積極開發中。CRI 在 Kubernetes 1.5 中以 Alpha 版釋出。

支援可互換的容器執行時在 Kubernetes 中並不是一個新概念。在 1.3 版本中,我們宣佈了 rktnetes 專案,以支援 rkt 容器引擎作為 Docker 容器執行時的替代方案。然而,Docker 和 rkt 都透過內部和不穩定的介面直接且深度整合到 kubelet 原始碼中。這種整合過程需要深入理解 Kubelet 內部結構,並給 Kubernetes 社群帶來巨大的維護開銷。這些因素為新興容器執行時設定了很高的門檻。透過提供清晰定義的抽象層,我們消除了這些障礙,讓開發者能夠專注於構建他們的容器執行時。這是實現可插拔容器執行時和構建更健康生態系統的一小步,但卻是重要的一步。

CRI 概述
Kubelet 使用 gRPC 框架透過 Unix 套接字與容器執行時(或執行時 CRI shim)通訊,其中 kubelet 充當客戶端,CRI shim 充當伺服器。

協議緩衝區的API包含兩個 gRPC 服務:ImageService 和 RuntimeService。ImageService 提供用於從倉庫拉取、檢查和刪除映象的 RPC。RuntimeService 包含用於管理 Pod 和容器生命週期的 RPC,以及與容器互動(執行/附加/埠轉發)的呼叫。管理映象和容器的單一容器執行時(例如 Docker 和 rkt)可以透過單個套接字同時提供這兩個服務。套接字可以透過 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 中,這是透過在 kubelet 建立並傳遞給執行時的 Pod 級別 cgroup 中啟動所有程序來實現的。

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

Kubelet 負責透過 RPC 管理容器的生命週期,執行容器生命週期鉤子和存活/就緒檢查,同時遵守 Pod 的重啟策略。

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

Kubernetes 具有宣告式 API 和一個 `Pod` 資源。我們考慮過的一種可能設計是 CRI 在其抽象中重用宣告式 `Pod` 物件,讓容器執行時可以自由地實現和執行自己的控制邏輯以達到期望狀態。這會大大簡化 API,並允許 CRI 適用於更廣泛的執行時。我們在設計階段早期討論過這種方法,但出於幾個原因決定反對。首先,kubelet 中有許多 Pod 級別的特性和特定機制(例如,崩潰迴圈回退邏輯),所有執行時重新實現它們將是一個巨大的負擔。其次,更重要的是,Pod 規範(並且仍然)在快速發展。許多新特性(例如,初始化容器)不需要對底層容器執行時進行任何更改,只要 kubelet 直接管理容器即可。CRI 採用命令式容器級別介面,以便執行時可以共享這些常見特性,以提高開發速度。這並不意味著我們偏離了“水平觸發”的理念——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 伺服器,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。

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

CRI 與 Minikube

如果您想嘗試新的整合,但還沒有時間在雲中搭建一個新的測試叢集,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。或者,您可以使用不依賴 PodCIDR 的 cni 外掛。 `--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.

...

_請注意,在 minikube 中啟用 CRI 後,kubectl attach/exec/port-forward 尚無法工作,但這將在新版本的 minikube 中解決。_

社群

CRI 由 Kubernetes SIG-Node 社群積極開發和維護。我們很樂意聽取您的反饋。加入社群: