Kubernetes 解体新書Part 4 インターフェース

Kubernetes 本体と外部プログラムの境界線

コンテナの起動、ネットワーク、ストレージ、クラウド連携。それぞれの担当に仕事を頼む共通の取り決めを見ます。

  • L1 Web ターミナル
  • 更新 2026年10月6日

Kubernetes でアプリを動かすには、コンテナを起動し、ネットワークをつなぎ、必要ならディスクを用意します。 これらは別々のプログラムが担当していますが、利用者は Pod の設定からまとめて頼めます。

担当するプログラム同士は、決まった呼び出し方で仕事を受け渡します。 この章では、その約束事である インターフェース と、担当を交換できる理由を見ていきます。

GOAL この章のゴール

  • コンテナ、ネットワーク、ストレージ、クラウド連携の担当を区別できる
  • 共通の取り決めでプログラムを交換できる理由が分かる
  • 自分のクラスタで各担当のプログラムを調べられる

頼む相手が別のプログラムになっている

たとえば、ネットワークの担当を Cilium から別のプログラムに替えたいとします。 担当ごとに頼み方が違えば、呼び出す側のプログラムも、その都度変更しなければなりません。

そこで「この形式で設定を渡し、この形式で結果を返す」という取り決めを決めます。 呼び出す側と呼ばれる側が同じ取り決めに従えば、担当を交換しやすくなります。 この接点を インターフェース と呼びます。 Kubernetes は、コンテナの起動やストレージの操作などを、こうした接点を通して外部のプログラムに任せています。

仕事ごとに名前が付いていて、次の 4 つが基本です。

  • CRIシーアールアイContainer Runtime Interface。kubelet がコンテナランタイムを呼ぶための gRPC の取り決め。用語集で見る:コンテナを起動する仕事。頼み先は containerd や CRI-O。
  • CNIシーエヌアイContainer Network Interface。Pod に NIC を生やし、IP を配るプラグインの規約。用語集で見る:Pod のネットワークの仕事。頼み先は Cilium や Calico。
  • CSIシーエスアイContainer Storage Interface。ボリュームの作成、ノードへの接続、マウントをストレージベンダーの driver に任せる gRPC の規約。用語集で見る:ディスクの仕事。頼み先は EBS 用の driver など。
  • CPIシーピーアイCloud Provider Interface。cloud-controller-manager がクラウドの API を呼ぶための Go の interface。用語集で見る:クラウドの仕事。頼み先は各クラウド用の cloud-controller-manager。

gRPC は、プログラム同士が関数を呼ぶ形で話すための通信方式です。 この章では、なぜこの形になったのか、4 つがどんな運び方で頼まれているのか、自分のクラスタでは誰が引き受けているのかを順に見ます。

kubelet が太りすぎた

Kubernetes 1.0 (2015 年 7 月) の kubelet のソースには、dockertools と rkt というディレクトリが並んでいました。 前者は Docker の API を直接叩くコード、後者は CoreOS の rkt というランタイム専用のコードです。 kubernetes.io の 2016 年のブログは、当時の構造を「kubelet が Docker と rkt に密に結合していて、ランタイムを 1 つ足すたびに kubelet の内部を深く知った実装が要る」と説明しています。

この構造の問題は、Docker が Kubernetes のためだけに作られていないことにあります。 Docker は自分の都合で API を変え、バージョンを上げます。 そのたびに kubelet 側の dockertools を直す必要があり、修正は Kubernetes のリリースに乗らなければ利用者に届きません。

kubelet の中にランタイム固有コードがあった頃と、CRI を定めた後左: Kubernetes 1.0 から 1.4 の kubelet は dockertools と rkt のコードを内蔵していた。右: 1.5 以降は kubelet が CRI の gRPC を話し、ランタイムは外のプロセスになった。1.0 〜 1.4 (2015 〜 2016)kubeletdockertoolsDocker API 専用rktrkt 専用Pod のライフサイクル管理ボリューム / ネットワーク / クラウドランタイムの API が変わるたびに kubelet を直す1.5 以降 (2016 年 12 月 〜)kubeletCRI クライアントgRPC で RunPodSandbox / CreateContainergRPC over UNIX socketcontainerdCRI プラグイン内蔵CRI-OKubernetes 専用ランタイムは別プロセス。中身を知らなくてよい
図 1左: ランタイム専用コードを kubelet が抱えていた頃。右: CRI を定め、ランタイムを別プロセスにした後。

1.5 (2016 年 12 月) で CRI が入り、kubelet は「コンテナを起動しろ」を gRPC で外のプロセスに投げるだけになりました。 kubelet とランタイムの間のやり取りを gRPC の文書に固定したので、ランタイムは別プロセスとして自分のペースで開発でき、ランタイム側の変更が kubelet のコードに影響しません。 kubernetes.io はこれを「クラスタのコンポーネントを再コンパイルせずに、さまざまなランタイムを使えるようにするプラグインインターフェース」と説明しています。

同じ発想がネットワーク、ディスク、クラウドにも広がった

kubelet はランタイムのほかにも、ネットワーク、ボリューム、クラウドの API に依存していました。 ランタイムで効いた「別プロセスに切り出して、決まった方法で頼む」という手は、これらにも同じように効きます。 Pod のネットワークは CNI、ボリュームは CSI、クラウドとの連携は CPI として、それぞれ Kubernetes 本体の外へ切り出されました。 切り出す利点は、公式ドキュメントでも同じ形で説明されています。 ストレージでは、ベンダーが Kubernetes 本体のコードに触れずに driver を書いて配れること。 クラウド連携では、クラウド事業者が本体とは別のリリース周期で機能を出せることです。

結果として Kubernetes は、ひとつの巨大なプログラムというより、決まった取り決めでつながる別々のプロセスの集まりになっています。 コントロールプレーンの各プロセスは apiserver を介してしか会話せず、ノード側の部品は kubelet から socket 越しに呼ばれます。

Kubernetes 自身がコンポーネントごとのマイクロサービスコントロールプレーンもノードも、別プロセスが apiserver を介して動く。各インターフェースは、プロセスとプロセスの間のやり取りに対応している。コントロールプレーンkube-apiserverschedulercontroller-managerCCMCPI の実装GatewaycontrollerNodekubeletcontainerdCRIcilium-cniCNIcsi nodeCSIdevice plugin/ DRA driverwatch強調した箱が「差し替えられる部品」。取り決めが決まっているから別プロジェクトで作れる
図 2Kubernetes 自身がコンポーネントごとのマイクロサービス。強調した箱は、kubelet などとは別のプロセスで動く「差し替えられる部品」。

AWS の語彙で言うなら、ECS が Docker や containerd と話す部分 (ecs-agent) が CRI、awsvpc モードで ENI を Task に差す部分が CNI、EBS を Task にアタッチする部分が CSI、ELB にターゲットを登録する部分が CPI の Service controller にあたります。 ECS はそれらを AWS が一体で持ちますが、Kubernetes は一つずつ外に出し、誰でも実装できる形にしました。

全体図

主要な 4 つに、その後で増えた周辺を加えると次の図になります。

Kubernetes の主要インターフェースの全体図kube-apiserver と kubelet を中心に、CRI / CNI / CSI / CPI / Device Plugin と DRA / Gateway API / CRD が放射状に並ぶ。ノード側のものは kubelet から、クラスタ側のものは apiserver から線が出る。kube-apiserverクラスタ側の中心: watch で動くkubeletノード側の中心: gRPC / exec で呼ぶPod spec / statusCRIコンテナランタイムCNIPod のネットワークランタイム経由CSI (Node)マウントDevice Plugin / DRAGPU などのデバイスCPI / CCMクラウド APICSI (Controller)sidecar が watchGateway APIL4 / L7 の入口CRD / 集約 API自作の APIkubelet が直接呼ぶ (gRPC / exec)apiserver を watch する controller が動く
図 3kube-apiserver と kubelet を中心にした全体図。ノード側は kubelet が直接呼び、クラスタ側は controller が apiserver を watch して動く。
  • CRI:コンテナランタイム。containerd / CRI-O。
  • CNI:Pod のネットワーク。Cilium / Calico / Flannel / Amazon VPC CNI。
  • CSI:ボリューム。aws-ebs-csi-driver、このクラスタでは kubevirt-csi-driver。
  • CPI:クラウド連携。cloud-provider-aws など、このクラスタでは kubevirt-cloud-controller-manager。
  • Device Plugin / DRA:GPU などのデバイス。デバイスをコンテナに渡す書式として CDI。
  • Gateway API:L4 / L7 の入口。Ingress の後継で、実装は Cilium / Envoy Gateway など。
  • CRD と集約 API:自分でリソース種別を足す入口。cert-manager や Argo CD、metrics-server。

kubernetes.io の「Extending Kubernetes」は、これらを含む拡張点を一覧にしています。 kube-scheduler の中にプラグインを差す scheduling framework や、apiserver への要求を検査、書き換えする admission webhook もその一覧にありますが、この Part では扱いません。 下の図で各項目を選ぶと、「誰が誰を呼ぶか」「どう運ぶか」「無いと何が起きるか」が出ます。

kube-apiserverwatch で動く側の中心kubelet直接呼ぶ側の中心CRICNICSIDevice Plugin / DRACPI / CCMGateway APICRD / 集約 API実線: kubelet が呼ぶ / 破線: apiserver を watch する controller
名前
CRI Container Runtime Interface
誰が呼ぶ
kubelet コンテナランタイム (containerd / CRI-O)
運び方
gRPC over UNIX socket (/run/containerd/containerd.sock)。RuntimeService と ImageService の 2 サービス。
実装の例
containerd / CRI-O / cri-dockerd (Docker を繋ぐ shim)
無いと
kubelet がノードを登録できない。Pod は一つも起動しない。

CRI の中心は kubelet です。ノード側は「kubelet が呼ぶ」、クラスタ側は「controller が apiserver を見て動く」という向きの違いが、設計の分かれ目です。

呼び方は 4 種類

Part 3 の CRI は gRPC で、Part 2 の CNI は実行ファイルの呼び出しでした。 kubelet や controller が外部のプログラムに頼むときの運び方は、全部で 4 種類あります。

インターフェースの 4 つの呼び方gRPC over UNIX socket (CRI / CSI / Device Plugin)、バイナリの exec と JSON (CNI)、sidecar と gRPC (CSI controller)、apiserver を watch する controller (CPI / Gateway API / CRD) の 4 種類。1. gRPC over UNIX socketkubelet.sockcontainerdCRI / CSI node / Device Plugin。常駐デーモン2. バイナリを exec + JSONcontainerdstdin JSONcilium-cniCNI。呼ばれたら終わる使い捨てプロセス3. sidecar が翻訳する gRPCapiserverwatch PVCprovisionersidecarCreateVolumeCSI driverCSI controller。driver はKubernetes を知らなくてよい4. apiserver を watch する controllerapiserverwatch ServiceCCMRESTクラウド APICPI / Gateway API / CRD。kubelet も apiserver も相手を知らない
図 44 つの運び方。常駐デーモンへの gRPC、使い捨てバイナリの exec、sidecar が翻訳する gRPC、apiserver を watch する controller。
運び方 使うインターフェース 特徴
gRPC over UNIX socket CRI、CSI Node、Device Plugin、DRA ノード上の常駐デーモンと話す。kubelet が client
バイナリの exec と JSON CNI 呼ばれたら終わる。デーモン不要、設定は /etc/cni/net.d
sidecar が翻訳する gRPC CSI Controller sidecar が apiserver を watch し、driver は Kubernetes を知らない
apiserver を watch する controller CPI、Gateway API、CRD kubelet は関与しない。クラウド API など外の世界を操作する

CNI だけが exec なのは、CNI が Kubernetes 専用の規約として作られていないからです。 CNI は CoreOS が提案し、「多くのランタイムやオーケストレーターが同じ問題 (ネットワーク層を差し替え可能にすること) を解こうとするので、重複を避けるために共通のインターフェースを定める」という目的で作られました。 常駐プロセスを前提にしない方が、どのランタイムからも呼びやすい。 実際、CNI のプロジェクトは利用者として Kubernetes のほかに Amazon ECS、Apache Mesos、Cloud Foundry などを挙げていて、ECS の awsvpc モードでは ecs-agent が CNI プラグインを呼んでタスクに ENI を差しています。

実機で API グループを眺める

インターフェースの多くは、apiserver に専用の API グループを持ちます。 自分のクラスタで一覧を見てみましょう。

API グループを列挙する
kubectl get --raw /apis | jq -r '.groups[].name' | sort

出力例

apps
cilium.io
gateway.networking.k8s.io
node.k8s.io
resource.k8s.io
storage.k8s.io
...

storage.k8s.io に CSIDriver / CSINode / VolumeAttachment、resource.k8s.io に DRA のリソース、gateway.networking.k8s.io に Gateway API、node.k8s.io に RuntimeClass が入っています。 CRI と CNI はこの一覧に出ません。 どちらもノードの中で完結し、apiserver にリソース種別を持たないからです。 この違いも、次の章から順に見ていきます。

ふりかえり

QCRI が作られた直接の動機はどれ?

1.0 の kubelet は dockertools と rkt のコードを内蔵していて、ランタイムの API 変更のたびに kubelet を直す必要がありました。ランタイムを別プロセスに出して、gRPC で頼む形にしたのが CRI です。

Qkubelet が直接呼ばないインターフェースはどれ?

CPI は cloud-controller-manager が apiserver を watch してクラウドの API を叩く形で、kubelet は関与しません。CRI と CSI Node plugin は kubelet が UNIX socket 越しに gRPC で呼びます。

QCNI だけが「バイナリを exec する」方式なのはなぜ?

CNI は ECS や Mesos など複数のランタイムが同じプラグインを呼べるように作られた規約で、「設定 JSON を stdin で渡してバイナリを実行する」形になっています。

参考