Pod を起動する containerd と、ネットワークを準備する Cilium は別のプログラムです。 containerd は Cilium に「この Pod のネットワークを用意して」と頼む必要があります。
この依頼では、設定を渡してプラグインの実行ファイルを起動し、結果を受け取ります。 この章では、CNI の設定ファイルと受け渡すデータを読み、IP アドレスが返ってくるまでを追います。
GOAL この章のゴール
- ネットワークプラグインに渡す設定と、返される結果を読める
- 複数のプラグインを順番に呼ぶ仕組みが分かる
- ノード上で CNI の設定ファイルを確認できる
3 つの仕事
Part 2 で、Pod のネットワークには 3 つの仕事があると見ました。 Pod の network namespace に NIC を生やすこと、その NIC に IP を配ること、ノードをまたいで Pod 同士を繋ぐことです。
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 の処理の中で、次のように呼び出します。
渡すものは環境変数と 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 がそのままバイナリ名になります。
配列に複数のプラグインがあるとき、ランタイムはそれを順に実行します。
前のプラグインの結果が prevResult として次のプラグインの stdin に入るので、portmap は「bridge が作った eth0 と IP」を知った上でポート転送のルールを足せます。
これが チェーン です。
DEL では逆順に実行し、prevResult には 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 で確認して消してください。
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 を手で与えます。
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 プラグインには任せません。
参考
- Container Network Interface (CNI) Specification: https://github.com/containernetworking/cni/blob/main/SPEC.md
- Container Network Interface (CNI) README: https://github.com/containernetworking/cni/blob/main/README.md
- CNI plugins (reference plugins) の一覧: https://www.cni.dev/plugins/current/
- Network Plugins, Kubernetes Documentation: https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
- CRI Plugin Config Guide, containerd (CNI の
conf_dir/bin_dir/max_conf_num): https://github.com/containerd/containerd/blob/main/docs/cri/config.md - Component Overview, Cilium Documentation: https://docs.cilium.io/en/stable/overview/component-overview/
- Kubernetes Host Scope (IPAM), Cilium Documentation: https://docs.cilium.io/en/stable/network/concepts/ipam/kubernetes/
- Routing (Encapsulation), Cilium Documentation: https://docs.cilium.io/en/stable/network/concepts/routing/
- Cilium の conflist テンプレート (
daemon/cmd/cni/config.go, v1.20): https://github.com/cilium/cilium/blob/v1.20/daemon/cmd/cni/config.go - Amazon ECS CNI Plugins README: https://github.com/aws/amazon-ecs-cni-plugins/blob/master/README.md
- kubelet パッケージの依存関係 (pkgs.k8s.io, v1.37): https://pkgs.k8s.io/core:/stable:/v1.37/deb/Packages