目次
15 章

CNI 深掘り

同じ NetworkPolicy マニフェストが Calico の上では iptables ルールに、Cilium の上では eBPF プログラムに展開されるデータプレーンの深さを扱います。K8s ネットワークモデルの4つの条件、CNI インターフェースの正体、iptables・IPVS・eBPF の3つのデータプレーンモデル、Calico と Cilium の比較、そして CNI 選択の実戦基準までを一連の流れで整理します。

3部 (深さ) の最初の章です。第14章 RBAC / NetworkPolicy / ResourceQuota で NetworkPolicy を扱ったとき、一行を残しておきました。「NetworkPolicy は K8s マニフェスト次元では標準だが、実際のトラフィックを止める仕事は CNI プラグインがする」。その一行が本章のテーマです。同じ kind: NetworkPolicy マニフェストが Calico の上では iptables ルールに展開され、Cilium の上では eBPF プログラムに展開されます。形が同じでも実行段の動作・性能・可観測性は違います。本章では K8s ネットワークモデルの要求CNI インターフェースの正体データプレーンの3つのモデル (iptables / IPVS / eBPF)Calico と Cilium の比較CNI 選択の実戦基準 までを一連の流れで整理します。

本章の終わりには 同じ K8s マニフェストがどの CNI の上でどんな形に展開されるのか を一行で読み取れる状態になります。CNI 決定のトレードオフ — iptables デバッグの馴染みやすさ vs eBPF の性能と可視性 — も手に掴めます。

K8s ネットワークモデルが要求する4つ #

CNI の話をする前に、K8s がネットワーク実装に要求する条件から押さえておきます。K8s 自体はネットワーク実装を持っていません。代わりにネットワークが 必ず満たさなければならない4つの条件 を仕様として定めておき、この条件を満たす仕事は CNI プラグインの役目です。

条件説明
Pod-to-PodNAT なしですべての Pod がすべての Pod と通信可能
Node-to-Podすべてのノードのエージェントが NAT なしですべての Pod と通信可能
Pod self IPPod が自分自身を認識する IP と、他の Pod がその Pod を呼ぶ IP が同じ
Service 抽象化仮想 IP (ClusterIP) が複数の Pod へ負荷分散

3つ目の条件はコンテナ環境では自明ではありません。Docker のデフォルトのブリッジネットワークは NAT を経由して外部と通信するので、コンテナ内部の自分の IP と外部から見える自分の IP が違います。K8s はこの NAT モデルを拒否します。すべての Pod はクラスタ内で唯一の IP を持ち、その IP で自分自身と他の Pod をすべて呼びます。 この単純なモデルの上で 第5章 Service の ClusterIP・DNS、第14章 の NetworkPolicy のような上位オブジェクトが一貫して回ります。

この4つの条件を満たす方式は1つではありません。ノード間をつなぐオーバーレイを作ることもでき (VXLAN、Geneve)、ルーティングテーブルに直接 BGP で経路を広告することもでき、eBPF でカーネルのパケット処理経路自体を変えることもできます。どの道を選んだかによってデータプレーンの性能特性と可観測性が分かれます。CNI 選択はその道を選ぶ決定です。

CNI — Container Network Interface #

CNI は K8s だけの規格ではなく コンテナランタイムがコンテナにネットワークを付けるときに呼び出す標準インターフェース です。CNCF が管理する仕様で、kubelet 以外にも podman / cri-o / containerd が同じインターフェースを使います。

規格自体は非常に単純です。コンテナランタイムが新しいコンテナを作るとき CNI プラグインの実行ファイルを呼び出しながら、コンテナ ID とネットワーク namespace のパスを渡します。プラグインはその namespace の中にインターフェースを作り、IP を割り当て、ルーティングテーブルを埋め、結果を JSON で返します。

kubelet → CNI プラグイン呼び出しの意味
1. kubeletがPod生成を決定 (Podに新しいsandboxコンテナを作る)
2. コンテナランタイム(containerd等)がnetwork namespace生成
3. そのnamespaceパスを引数にCNIプラグイン実行 (ADDコマンド)
4. プラグインがnamespaceの中にvethインターフェースを作りIP割り当て
5. プラグインがホスト側のルーティング・iptables・eBPF map等を更新
6. プラグインが割り当てたIPをJSONで返す
7. kubeletがそのIPをPod statusに記録

ここで核心は K8s 自体は4段階から6段階の間の実際のネットワーク実装を知らない という点です。veth を作ろうが、MACVLAN を作ろうが、eBPF フックでパケットを横取りしようが — その責任は完全に CNI プラグインにあります。K8s は「Pod に IP が付き、上の4条件が満たされた」という結果だけを受け取ります。

この分離構造のおかげで同じ K8s クラスタで CNI を取り替えられます (クラスタセットアップ時点で)。Calico を入れても Cilium を入れても Flannel を入れても、K8s API ユーザーは同じマニフェストを書きます。ただしそのマニフェストが 実際にどう展開されるか が変わります。本章のテーマがその「どう展開されるか」です。

CNI プラグインの2つの部分 #

運用クラスタの CNI プラグインは普通2つの部分に分かれます。

  • ノードエージェント (DaemonSet) — 各ノードに1つずつ起動してルーティング / ポリシー / IP 割り当てを管理します。Calico の calico-node、Cilium の cilium-agent がこの役割を担います。
  • CNI バイナリ/opt/cni/bin/ にインストールされてコンテナランタイムが直接呼び出します。ノードエージェントが起動時にこのバイナリをノードのディレクトリに展開しておきます。

この2つの部分が一緒に動作しながら K8s が要求する4条件をノード単位で実装します。マニフェストは単純なのに運用段の形は2層に分かれているという点を覚えておくとデバッグが楽になります。Pod に IP が付かない問題は CNI バイナリ・ノードエージェント・kubelet のどこが詰まっているかを一層ずつ辿る仕事です。この診断ツリーは 第27章 kubectl デバッグパターン でもう一度整理します。

データプレーンの3つのモデル #

K8s ネットワークトラフィックが実際に流れる道をデータプレーンと呼びます。クラスタでよく出会うモデルは3つです — iptables ベースIPVS ベースeBPF ベース。1つずつ押さえます。

iptables ベース — 最も古い道 #

K8s のデフォルトコンポーネントである kube-proxy は最初から iptables をベースに作られました。ClusterIP に入ってくるトラフィックを後ろにある Pod IP たちへ分散する仕事を iptables の NAT ルールで扱います。Service 1つにつきルールが追加され、Pod の IP が変わるたびにルールが更新されます。第5章 Service の §「kube-proxy」でこのモデルを一度押さえておき、本章でその深さを足します。

このモデルの長所は単純さと互換性です。ほぼすべての Linux カーネルが iptables をサポートし、デバッグツール (iptables -Liptables-save) が豊富です。短所は 規模が大きくなると性能が削られる という点です。iptables はルールを線形に検査します。Service が1,000個で各 Service の後ろに10個の Pod があるなら、1つのトラフィック決定に平均5,000行近いルールを舐めなければなりません。クラスタ規模が中大型へ向かうとパケットあたりの CPU コストが目に見えて上がります。

kube-proxyのiptablesルールの一部を見る
sudo iptables -t nat -L KUBE-SERVICES -n
出力例 (Service一つにつき一行ずつ)
KUBE-SVC-XYZAB1234567  tcp  --  *  *  0.0.0.0/0  10.96.0.10  /* kube-system/kube-dns:dns */
KUBE-SVC-ABCDE9876543  tcp  --  *  *  0.0.0.0/0  10.96.45.12  /* default/web:http */
...

NetworkPolicy が入ると iptables ルールがさらに増えます。Calico のデフォルトのデータプレーンモードがこの道で、Pod 1つにつき ingress・egress ポリシールールがホストの iptables チェーンに追加されます。ポリシーの数と Pod の数が掛け合わされてルール数が速く増えます。

IPVS ベース — カーネル段の負荷分散 #

IPVS は Linux カーネルが持つ4層の負荷分散モジュールです。iptables が NAT ルールを一般化したツールだとすれば、IPVS は負荷分散そのもののための専用ツールです。ハッシュテーブルベースで動作するのでルール数が増えても検索コストがほぼ一定です。

K8s の kube-proxy は 1.11 から IPVS モードを正式サポートします。--proxy-mode=ipvs で起動すると ClusterIP の負荷分散が IPVS で行われます。大型クラスタ (Service 数千個以上) では iptables モードより平均 latency と CPU 使用量が一貫して低いです。ただし NetworkPolicy のようなポリシー次元は依然として iptables (または nftables) が担当するので、IPVS は部分的な改善です。

eBPF ベース — カーネル自体を書き換える道 #

eBPF (extended Berkeley Packet Filter) は Linux カーネルの中にユーザー定義プログラムを安全に差し込めるメカニズムです。元々はパケットフィルタリング用に始まりましたが、今はカーネルのほぼすべてのフックポイント (システムコール、ネットワーク処理段階、トレーシングポイント) に小さなプログラムをぶら下げられます。

ネットワーク面で eBPF が意味を持つ理由は単純です。iptables / IPVS の役割を eBPF プログラムが代替でき、同じ方向の結果をより少ない CPU でより豊富な観測情報とともに作り出せます。 パケットがカーネルを通過する道に直接コードが割り込めるので、NAT・負荷分散・ポリシー検査が一塊で処理されます。iptables ルールを線形検査することもなく、NetworkPolicy のために別途チェーンを作ることもありません。

iptablesモデル vs eBPFモデル — 同じトラフィック一件
[iptablesモデル]
  パケット → conntrack → KUBE-SERVICESチェーン → KUBE-SVC-XXX → KUBE-SEP-YYY → DNAT → ルーティング
        (ルールN個を線形検査)

[eBPFモデル]
  パケット → tc/XDPフックのeBPFプログラム
        → eBPF map照会(Service → Podリスト, O(1))
        → ポリシーmap照会(許可可否, O(1))
        → DNAT後に転送

Cilium がこのモデルの代表的な実装です。Calico も 3.13 から eBPF データプレーンモードをオプションで提供します。2つの製品の違いは次の節で押さえます。

Calico と Cilium — 2つの道 #

K8s クラスタで最もよく出会う CNI プラグインが Calico と Cilium です。どちらも NetworkPolicy を完全にサポートし、どちらも運用規模で回った実績が十分に積み上がっています。違いは データプレーンのデフォルトモデルeBPF にどれだけ深く依存するか にあります。

Calico — BGP・iptables がデフォルト、eBPF はオプション #

Calico のデフォルトのデータプレーンは2つの部分の組み合わせです。

  • ノード間ルーティング — BGP (Border Gateway Protocol) で各ノードの Pod CIDR を他のノードに広告します。ノード自体がルーターのように動作するのでオーバーレイ (VXLAN のようなカプセル化) が必要ありません。クラウドのルーティングテーブルが Pod CIDR を知らない環境では IP-in-IP や VXLAN でカプセル化するオプションもあります。
  • ノード内のポリシー / NAT — iptables で Service 負荷分散と NetworkPolicy を扱います。kube-proxy の iptables モードと同じ位置です。

この組み合わせの長所は運用段の馴染みやすさです。ルーティングが BGP で回るのでデータセンターの BGP インフラ (特にオンプレミス、ToR スイッチ環境) と自然につながります。iptables ルールは標準ツールでデバッグできます。短所は 規模が大きくなると iptables の限界がそのまま来る ことです。Service / Pod / ポリシーの数が増えるとルール数が速く膨らみ、kube-proxy の同期時間も長くなります。

Calico 3.13 からはデータプレーンを eBPF に取り替えるモードが追加されました。このモードでは kube-proxy がもはや必要なく、Service 負荷分散と NetworkPolicy がすべて eBPF で展開されます。ただし Calico の BGP ルーティングモデルはそのまま維持されるので、「ルーティングは BGP、データプレーンは eBPF」の混合形になります。

Cilium — 最初から eBPF #

Cilium は最初から eBPF を前提に設計された CNI です。Service 負荷分散、NetworkPolicy、7層ポリシー (HTTP・gRPC メソッド単位の許可 / 拒否)、ノード間暗号化 (WireGuard / IPsec)、可観測性 (Hubble) まですべて eBPF プログラムで扱います。

Ciliumのコンポーネント構成 (単純化)
[各ノード]
  cilium-agent (DaemonSet)
    ├─ eBPFプログラムをコンパイル・ロード
    ├─ Service / Endpoint / NetworkPolicyをeBPF mapに埋める
    └─ Pod生成時にveth + eBPFフックを付着

  Hubble (任意)
    └─ eBPFから収集した流れ・メトリクスを公開

Cilium の差別化点は3つです。

  • kube-proxy 代替 — Cilium だけで Service 負荷分散が処理されるので kube-proxy を切れます。クラスタのコンポーネント数が減り、iptables ルールが消えるのでノードのパケット処理経路が短くなります。
  • 7層ポリシー — NetworkPolicy の標準スペックは4層 (IP / ポート) に限定されますが、Cilium は独自の CRD (CiliumNetworkPolicy) で HTTP メソッド・パス、gRPC サービス / メソッド、Kafka トピック単位のポリシーを表現できます。このポリシーも eBPF プログラムで展開されます。
  • Hubble — eBPF ベースの可観測性 — パケットが eBPF フックを通るときにメタデータを収集して流れ単位の可視性を提供します。「どの Pod がどの Pod のどのポートを呼ぶか」をリアルタイムで見られます。可観測性は 第19章 可観測性 で別に扱いますが、Hubble が eBPF の副産物として自然についてくるという点は Cilium の魅力の一つです。

一目で見る比較 #

次元Calico (デフォルト)Calico (eBPFモード)Cilium
Pod ルーティングBGP / IP-in-IP / VXLANBGP / IP-in-IP / VXLANVXLAN / Geneve / native routing
Service 負荷分散kube-proxy (iptables / IPVS)eBPFeBPF (kube-proxy 代替可能)
NetworkPolicy 実行iptableseBPFeBPF
7層ポリシー非対応 (標準スペック)非対応CiliumNetworkPolicy で対応
可観測性外部ツールが必要外部ツールが必要Hubble 内蔵
運用ツールの馴染み標準 iptables ツールeBPF デバッグが必要eBPF デバッグが必要
初導入の参入障壁低い中間中間

同じ K8s マニフェストが3つのカラムどこでも動作します。ただし そのマニフェストがノードの中でどんな形に展開されるか はカラムごとに違います。この違いが性能・可観測性・運用ツールの選択にそのまま反映されます。

eBPF が変えるもの #

CNI 選択の話をするとき eBPF がよく登場するので、eBPF が K8s ネットワークにもたらした変化を一度押さえておきます。eBPF 自体は K8s コンポーネントではなく Linux カーネル機能ですが、K8s ネットワークのデータプレーンがこの機能を積極的に使うことで運用モデルの肌合いが変わりました。

kube-proxy の役割が消える #

長らく kube-proxy は K8s クラスタの必須コンポーネントでした。ClusterIP の仮想 IP を実際の Pod IP たちへ分散する仕事がこのコンポーネントの責任で、その仕事を iptables または IPVS で解いていました。

Cilium が kubeProxyReplacement: true オプションで kube-proxy を完全に代替でき、Calico の eBPF モードも同じ仕事をします。コンポーネントが1つ抜けると運用の表面積がその分減ります — 監視する対象が1つ減り、同期遅延を疑う候補が1つ減り、ルール急増の原因が1つ消えます。

NetworkPolicy のコストモデルが変わる #

iptables ベースの NetworkPolicy はポリシーの数と Pod の数に比例してルールが増えます。eBPF ベースではポリシーが map で表現されるので検索コストがほぼ一定です。ポリシー数が数百・数千個へ増えるマルチテナントクラスタでこの違いがパケットあたりの latency として現れます。

可観測性がデータプレーンの副産物になる #

伝統的なモデルではトラフィックの可視性は別途の作業でした — Pod にサイドカーを付けるか、NodePort に tcpdump をかけるか、別途の監視エージェントを入れるか。eBPF データプレーンではパケットがどのみち eBPF フックを通るので、その道でメタデータ (ソース Pod / 宛先 Pod / ポリシー検査結果 / latency) を一緒に収集すれば自然に流れ単位の可視性が作られます。Cilium の Hubble がこのモデルの直接的な結果物です。

CNI 選択 — 実戦基準 #

理論的にはどの CNI でも K8s が要求する4条件を満たします。しかし運用クラスタの CNI 決定は次の5つの次元が集まる決定です。

次元質問
クラスタ規模Service / Pod / NetworkPolicy の数が何桁か
ネットワーク環境クラウドマネージドか、オンプレミス BGP インフラか
7層ポリシーの要否HTTP / gRPC 単位のポリシーが運用要求に入っているか
運用チームの馴染みiptables デバッグに慣れているか、eBPF ツールへ行く意向があるか
マネージド K8s のデフォルトEKS / GKE / AKS のデフォルト CNI をそのまま使うか、取り替えるか

マネージド K8s にはクラウド事業者が推すデフォルト CNI があります。EKS は aws-vpc-cni (VPC IP を Pod に直接割り当てるモデル)、GKE は独自 CNI (VPC-native モード)、AKS は Azure CNI または kubenet です。このデフォルト CNI はそのクラウドのネットワーキングと最も滑らかにつながりますが、NetworkPolicy のサポートや eBPF 機能が不足することがあるので運用要求に応じて Calico / Cilium に取り替える場合が多いです。EKS では aws-vpc-cni を維持しながら NetworkPolicy だけを Calico または Cilium の chained モードで載せるパターンが一般的です。EKS 上の本格的なセットアップ決定は 第21章 EKS クラスタセットアップ でもう一度扱います。

小規模・単純なクラスタではマネージド事業者のデフォルト CNI をそのまま使う決定が最も合理的です。運用負担が最も小さく、クラウドサポートとの呼吸も最もよいです。NetworkPolicy 要求が本格的に入ってくる段階、またはマルチテナント隔離・7層ポリシー・細やかな流れの可視性が運用要求になる段階で Calico / Cilium の導入検討が自然に始まります。

選択の細やかな肌合いを一行ずつ整理します。

  • Calico (デフォルトモード) — BGP インフラがすでにあり、iptables デバッグに慣れているチームが最も速く導入できる道です。中小規模クラスタで最も負担が少ない選択です。
  • Calico (eBPF モード) — ルーティングはそのままにしてデータプレーンの性能だけを引き上げたいときに使うモードです。BGP 資産を活かしながら eBPF の利点を取ってくる折衷です。
  • Cilium — 7層ポリシー・Hubble 可観測性・kube-proxy 除去を一塊で持っていきたいときに合う選択です。eBPF に本格的にベットする選択です。

この決定は一度下すと変えるのが簡単ではありません。CNI 交換はクラスタ全体のネットワーク再設定に近く、普通はクラスタの新規セットアップ時点で固まります。したがって最初に決定するとき運用の今後 1 ~ 2 年の絵を一緒に見るのがよいです。

練習問題 #

  1. 自分が運用中、または学習中のクラスタの CNI が何かを確認します (kubectl get pods -n kube-system | grep -E 'calico|cilium|flannel|aws-node')。その CNI のデータプレーンモデル (iptables / IPVS / eBPF) を §「データプレーンの3つのモデル」と突き合わせて一行で整理し、同じ K8s マニフェストがその上でどう展開されるかを自分の表現で一段落書きます。
  2. iptables ベースのクラスタで sudo iptables -t nat -L KUBE-SERVICES -n | wc -l でルール数を数え、同じクラスタの Service 数 (kubectl get svc -A | wc -l) とどんな比率で掛け合わされるかをメモします。Service 数が数千個へ増えたとき §「iptables ベース」の線形検査コストがどう累積するかを推論し、eBPF ベースの map 照会 (O(1)) とどう違うかを比較します。
  3. Calico と Cilium の §「一目で見る比較」表を見て、自分が運用しようとするクラスタの5つの決定次元 (規模、ネットワーク環境、7層ポリシーの要否、チームの馴染み、マネージドのデフォルト) に照らしてどちらを選ぶかを一段落で決定し理由を書きます。EKS 環境なら aws-vpc-cni + Calico/Cilium chained モードと単一 Cilium のどちらが自分の要求に合うかも一緒に比較します。

一行まとめ: K8s ネットワークモデルの4条件 (NAT なしの Pod-to-Pod、Node-to-Pod、Pod self IP、Service 抽象化) を満たす責任は CNI プラグインにある。同じマニフェストが Calico (BGP + iptables)・Calico eBPF・Cilium (最初から eBPF) の上で異なって展開される。eBPF データプレーンは kube-proxy の役割を代替し、NetworkPolicy のコストモデルを O(N) から O(1) へ変え、可観測性 (Hubble) をデータプレーンの副産物にする。CNI 選択は一度下すと変えにくい決定なのでクラスタセットアップ時点で運用 1 ~ 2 年の絵を一緒に見る。

次の章 #

本章で CNI のデータプレーンを解いてみたので、次の章では同じ方向で 第14章 の RBAC 節で一行で切っておいた部分を扱います — Aggregated ClusterRole、impersonation、EKS の IRSA と GKE の Workload Identity のように K8s 権限モデルが外部 IAM と連携する道です。

第16章 RBAC / ServiceAccount 深掘り では ClusterRole の aggregation メカニズム、--as オプションの impersonation の流れ、ServiceAccount トークンのライフサイクル (projected token)、そしてクラウド IAM とのマッピング — EKS の IRSA・Pod Identity、GKE の Workload Identity — までを一連の流れで整理します。本章の CNI のように K8s 標準オブジェクトがクラウドインフラとどう接続されるかのもう一つの断面です。

X