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

CNI: Pod のネットワークを実行ファイルで頼む方法

コンテナランタイムからネットワークプラグインへ、どんな設定が渡るのでしょうか。CNI のファイルとデータを読みます。

  • L2 ノードの中
  • 更新 2026年10月6日

Pod を起動する containerd と、ネットワークを準備する Cilium は別のプログラムです。 containerd は Cilium に「この Pod のネットワークを用意して」と頼む必要があります。

この依頼では、設定を渡してプラグインの実行ファイルを起動し、結果を受け取ります。 この章では、CNI の設定ファイルと受け渡すデータを読み、IP アドレスが返ってくるまでを追います。

GOAL この章のゴール

  • ネットワークプラグインに渡す設定と、返される結果を読める
  • 複数のプラグインを順番に呼ぶ仕組みが分かる
  • ノード上で CNI の設定ファイルを確認できる

3 つの仕事

Part 2 で、Pod のネットワークには 3 つの仕事があると見ました。 Pod の network namespace に NIC を生やすこと、その NIC に IP を配ること、ノードをまたいで Pod 同士を繋ぐことです。

CNI プラグインの 3 つの仕事1. Pod の netns に仮想 NIC (veth) を生やす、2. IP アドレスを配る (IPAM)、3. ノード間で Pod ネットワークを繋ぐ (VXLAN / BGP / クラウドの経路)。Node A10.0.1.10Pod10.244.1.5app1. veth を生やすeth0 ↔ lxc…2. IP を配る (IPAM)このクラスタは Node の podCIDR からNode B10.0.1.11Pod10.244.2.3db1. veth を生やすeth0 ↔ lxc…2. IP を配る (IPAM)10.244.2.0/24 から3. ノード間を繋ぐ (VXLAN / BGP / VPC 経路)
図 1veth を生やす、IP を配る、ノード間を繋ぐ。3 つ目だけはノードをまたぐ仕事で、VXLAN か BGP かクラウドの経路表かは実装が選ぶ。

CNI の仕様が決めているのは、このうち 1 と 2 の呼び方だけです。 仕様は ADD の役割を「CNI_NETNS のコンテナの中に CNI_IFNAME で指定された interface を作る」ことと定め、IP の割り当てもその結果に含めるよう求めています。 3 のノード間接続は仕様の外で、プラグインの裏で動くエージェント (Cilium なら cilium-agent) が担います。 このクラスタの Cilium は、Cilium のドキュメントで既定とされている encapsulation モードでノード間に VXLAN のトンネルを張り、IPAM は kubernetes モードなので各ノードの podCIDR (kube-controller-manager が Node に割り当てた範囲) から配ります。

実行モデル

CNI プラグインはただの実行ファイルです。 kubernetes.io は、CNI プラグインを読み込んで呼ぶのはコンテナランタイムの役目だと説明しています。 containerd は RunPodSandbox の処理の中で、次のように呼び出します。

CNI の実行モデル: 環境変数と stdin の JSON でバイナリを exec するcontainerd が /etc/cni/net.d の conflist を読み、CNI_COMMAND などの環境変数と設定 JSON を stdin に渡して /opt/cni/bin のプラグインを実行する。プラグインは stdout に結果の JSON を返して終了する。containerd はその結果を保存し、Pod の IP として kubelet に返す。containerd (CRI)RunPodSandbox の中で/etc/cni/net.d/05-cilium.conflist読む環境変数CNI_COMMAND=ADDCNI_CONTAINERID=…CNI_NETNS=/var/run/netns/…CNI_IFNAME=eth0CNI_PATH=/opt/cni/binstdin (JSON){ "cniVersion": …, "type": "cilium-cni", "prevResult": … }exec/opt/cni/bin/cilium-cni使い捨てプロセスstdout (JSON)"interfaces": [eth0…]"ips": [10.244.1.5/32]"routes", "dns"終了コード 0結果を保存し、Pod の IP として kubelet に返す
図 2containerd が conflist を読み、環境変数と stdin の JSON を添えてバイナリを exec する。結果は stdout の JSON で返り、プロセスは終了する。

渡すものは環境変数と stdin の JSON です。 環境変数は、CNI_COMMAND が操作の種類 (ADD / DEL / CHECK / GC / STATUS / VERSION)、CNI_CONTAINERID がコンテナの識別子、CNI_NETNS が Pod の network namespace への参照 (パス)、CNI_IFNAME がコンテナ側に作る NIC の名前、CNI_PATH がプラグインの実行ファイルを探すパスです。 ほかに CNI_ARGS で追加の key=value を渡せます。 stdin の JSON は、conflist のうちそのプラグインの設定に cniVersion や name を足したものです。

結果は stdout に JSON で返ります。 作った interface の一覧、割り当てた IP、ルート、DNS 設定です。 ランタイムはこの結果を保存しておき、後の DEL や CHECK でプラグインに渡します。 kubectl get pod -o wide に出る IP は、この結果から来ています。

conflist とチェーン

/etc/cni/net.d/ に置く設定は、.conflist という JSON です。 plugins 配列の各要素が 1 つのプラグインで、type がそのままバイナリ名になります。

conflist の構造: plugins 配列が実行順になるconflist は cniVersion と name と plugins 配列を持つ。plugins の各要素の type がバイナリ名で、配列の順にチェーン実行される。IPAM はメインのプラグインが別の IPAM プラグインに委譲できる。10-example.conflist (例){ "cniVersion": "1.0.0", "name": "podnet", "plugins": [ { "type": "bridge", "ipam": { "type": "host-local" } }, { "type": "portmap" }, { "type": "bandwidth" } ]}/opt/cni/bin/bridgeveth + bridge。IP は host-local に委譲/opt/cni/bin/portmaphostPort の DNAT ルール/opt/cni/bin/bandwidthtc で帯域制限prevResultprevResult
図 3conflist の例。bridge が IP の割り当てを host-local (IPAM プラグイン) に任せ、portmap と bandwidth が後ろに繋がる。

配列に複数のプラグインがあるとき、ランタイムはそれを順に実行します。 前のプラグインの結果が prevResult として次のプラグインの stdin に入るので、portmap は「bridge が作った eth0 と IP」を知った上でポート転送のルールを足せます。 これが チェーン です。 DEL では逆順に実行し、prevResult には ADD のときに保存した結果が入ります。

チェーンの実行順: ADD は前から、DEL は後ろからADD では plugins 配列の順に実行し、各プラグインの結果が prevResult として次に渡る。DEL では逆順に実行する。CHECK は ADD と同じ順。ADD (Pod 作成時)1. cilium-cni2. portmap3. bandwidthprevResultprevResult最後の結果を containerd が保存DEL (Pod 削除時)3. cilium-cni2. portmap1. bandwidth保存した結果を渡すDEL は何度呼ばれても壊れないこと (冪等) が仕様で要求される
図 4ADD は配列の順、DEL は逆順。CHECK は ADD と同じ順で、保存した結果と現状を比べる。

IP の割り当て (IPAM) も、同じように別のプラグインに任せられます。 メインのプラグイン (bridge など) が、設定の ipam.type に書かれたプラグイン (host-local / dhcp / static) を同じ環境変数と設定で exec し、返ってきた IP とルートを自分の結果に取り込みます。 Cilium は IP の割り当てを cilium-agent の中で行うので、conflist に ipam の項目はありません。

設定ファイルの置き場所を決めるのも、ランタイムの仕事です。 1.24 より前は kubelet にも network-plugin や cni-bin-dir というオプションがあり、kubelet が CNI を管理する構成が残っていましたが、1.24 でそれらが取り除かれ、設定ディレクトリ (既定は /etc/cni/net.d) とプラグインの置き場所 (既定は /opt/cni/bin) はランタイム側の設定になりました。 containerd の既定 (max_conf_num = 1) では、設定ディレクトリから読む設定ファイルは 1 つだけです。

CNI は Kubernetes 以外も使う

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

実機: conflist とバイナリを読む

ノードの中に入るノードの中で実行
kubectl debug node/$(kubectl get node -o jsonpath='{.items[0].metadata.name}') -it \
  --profile=sysadmin --image=busybox:1.37 -- chroot /host nsenter -t 1 -m -u -i -n -p -- bash

抜けるときは exit。デバッグ用 Pod は自動では消えないので、終わったら kubectl delete pod -l app.kubernetes.io/managed-by=kubectl-debug かkubectl get pods で確認して消してください。

Cilium の conflist を読むノードの中で実行
cat /etc/cni/net.d/05-cilium.conflist

出力例

{
  "cniVersion": "1.0.0",
  "name": "cilium",
  "plugins": [
    {
      "type": "cilium-cni",
      "enable-debug": false,
      "log-file": "/var/run/cilium/cilium-cni.log"
    }
  ]
}

プラグインは cilium-cni の 1 つだけで、ipam の項目もノード間接続の設定も持ちません。 Cilium のドキュメントは cilium-cni を「Pod がノードにスケジュール、終了されたときに呼ばれ、そのノードの Cilium の API を叩いて、Pod のネットワーク、ロードバランス、ネットワークポリシーに必要なデータパスの設定を起動させる」ものだと説明しています。 IP の割り当ても eBPF プログラムの取り付けも、依頼を受けた cilium-agent 側で行われます。

プラグインバイナリを列挙するノードの中で実行
ls -la /opt/cni/bin

出力例

-rwxr-xr-x 1 root root  ... bandwidth
-rwxr-xr-x 1 root root  ... bridge
-rwxr-xr-x 1 root root  ... cilium-cni
-rwxr-xr-x 1 root root  ... host-local
-rwxr-xr-x 1 root root  ... loopback
-rwxr-xr-x 1 root root  ... portmap
...

conflist に無い loopback や bridge が並んでいるのは、kubernetes-cni パッケージが参照プラグインを一式入れるからです。 kubernetes.io によると、Kubernetes はランタイムに対して sandbox ごとの lo interface を用意するよう求めていて、loopback プラグインがその役を担えます。 hostPort を使うには portmap、帯域制限 (実験的機能) には bandwidth のプラグインが要ります。

CNI がただのバイナリであることを、VERSION 操作で確かめましょう。 環境変数と stdin を手で与えます。

VERSION 操作を手で呼ぶノードの中で実行
echo '{"cniVersion":"1.0.0"}' | CNI_COMMAND=VERSION /opt/cni/bin/cilium-cni

出力例

{"cniVersion":"1.0.0","supportedVersions":["0.1.0","0.2.0","0.3.0","0.3.1","0.4.0","1.0.0"]}

containerd がやっていることは、これに CNI_COMMAND=ADD と CNI_NETNS などを足しただけです。 ADD を手で呼ぶと本当に IP が払い出されて agent の状態と食い違うので、ここでは VERSION に留めます。

ふりかえり

QCNI プラグインに操作の種類 (ADD / DEL など) を伝えるのは?

操作は環境変数 CNI_COMMAND で、設定は stdin の JSON で渡します。結果は stdout の JSON です。

Qconflist に複数のプラグインがあるとき、2 つ目のプラグインが 1 つ目の結果を知る方法は?

チェーンでは、前のプラグインの結果が prevResult として次の stdin に含まれます。DEL は逆順で、ADD のときに保存した結果が渡されます。

Qこのクラスタの conflist に ipam の項目が無い理由は?

cilium-cni は依頼を agent に中継するだけで、IP の割り当ても eBPF の取り付けも cilium-agent が行います。host-local のような IPAM プラグインには任せません。

参考