Skip to content
Published on

Ciliumデータパスアーキテクチャ — kube-proxyのないクラスタの内部

Authors

はじめに

従来構成のKubernetesクラスタでは、サービスへのリクエスト1つがPodに到達するまでに数千のiptablesチェーンを通過します。サービスが5,000を超えるクラスタでは、ルール更新1回に数秒かかり、カーネルはパケットごとにほぼ線形探索に近いコストを支払います。Ciliumはこの経路をeBPFプログラムとハッシュマップで置き換え、サービス数に関係なくほぼO(1)のルックアップでパケットを処理します。

Ciliumは2023年10月にCNCFの卒業(Graduated)プロジェクトとなり、その後マネージドKubernetes陣営の標準CNIとして急速に定着しました。GKE Dataplane V2はCiliumベースであり、AKSにはAzure CNI Powered by Ciliumがあり、EKSでもCiliumを直接インストールしてkube-proxyを完全に排除する構成が一般的になりました。本記事ではCiliumのeBPFデータパスをパケットの旅路に沿って解剖し、kube-proxy replacement・identityセキュリティモデル・ルーティングモード・IPAM・インストールと検証・トラブルシューティングまで、運用者が知るべき内部を整理します。

Ciliumの位置づけ — CNI以上の存在

表面的にはCiliumはCNIプラグインですが、実際にカバーする領域ははるかに広いです。

領域機能置き換え対象
PodネットワーキングIP割り当て、Pod間ルーティング従来のCNI(flannel、calicoなど)
サービスロードバランシングClusterIP、NodePort、LoadBalancerkube-proxy
ネットワークポリシーL3/L4/L7ポリシー、DNSポリシーNetworkPolicyと別途プロキシ
可観測性フロー可視化(Hubble)別途モニタリングエージェント
マルチクラスタClusterMeshサービスメッシュの一部機能
暗号化WireGuard、IPsec別途オーバーレイソリューション

これらすべての機能の共通基盤がeBPFです。eBPFは、カーネルの再コンパイルやモジュールのロードなしに、検証器(verifier)を通過したプログラムをカーネル内のフックポイントで実行できる技術です。ネットワーキングの観点で重要なフックは、XDP(ドライバレベル)とtc(トラフィックコントロール、L2/L3の入出力地点)です。

eBPFデータパスの解剖 — パケットの旅路

外部からNodePort経由で入ってきたパケットがPodに到達するまでの旅路を追ってみましょう。

[外部クライアント]
      |
      v
+---------------------------------------------------------------+
| ノード (Linuxカーネル)                                          |
|                                                               |
|  NIC受信                                                      |
|   |                                                           |
|   v                                                           |
|  [XDPフック] ──────── bpf_xdp.o                               |
|   |  - NodePort高速化、DDoSフィルタリング、LB(ドライバレベル)   |
|   |  - ここでDROP/TX(折り返し)/PASSを決定                       |
|   v                                                           |
|  [tc ingressフック] ── bpf_host.o (cil_from_netdev)           |
|   |  - サービス変換: ClusterIP/NodePort -> バックエンドPod IP   |
|   |    (cilium_lb4_services_v2, cilium_lb4_backends マップ)    |
|   |  - conntrack記録 (cilium_ct4_global マップ)               |
|   |  - ipcacheルックアップ: 宛先IP -> identity                 |
|   v                                                           |
|  ルーティング/リダイレクト (bpf_redirect_peer でveth越え)        |
|   |                                                           |
|   v                                                           |
|  [Pod lxcインターフェース] ── bpf_lxc.o (cil_to_container)     |
|   |  - ポリシー評価: src identity + dst port/proto マッチ      |
|   |    (エンドポイント別 cilium_policy_v2 マップ)               |
|   v                                                           |
|  [Podネットワーク名前空間 -> アプリケーションソケット]            |
+---------------------------------------------------------------+

重要なポイントを押さえると:

  1. XDPはNICドライバがパケットをsk_buffに変換する前に実行されます。メモリ割り当て前の段階のため最速であり、CiliumはNodePort/LoadBalancerの高速化(loadBalancer.acceleration=native)やスタンドアロンL4LBモードで活用します。
  2. tcフックがCiliumデータパスの本体です。サービス変換、conntrack、ポリシー評価、トンネルカプセル化のすべてがここで行われます。
  3. bpf_redirect_peerは、ホスト側vethからPod名前空間内のピアデバイスへパケットを直接渡し、ソフトIRQの再スケジューリングなしに名前空間の境界を一度に越えます。
  4. ポリシー評価はIPではなくidentity(後述)を基準に行われます。

iptables経路との比較

同じNodePortリクエストがkube-proxy(iptablesモード)クラスタで処理される経路です。

NIC -> netfilter PREROUTING
        -> KUBE-SERVICES チェーン (サービス数分のルールを線形走査)
        -> KUBE-SVC-XXXX (確率ベースのDNAT分岐、バックエンド数分)
        -> KUBE-SEP-XXXX (DNAT実行)
      -> conntrack (nf_conntrack テーブル)
      -> FORWARD チェーン -> CNIブリッジ/ルーティング -> veth -> Pod
項目iptables (kube-proxy)eBPF (Cilium)
サービスルックアップルールチェーンの線形走査ハッシュマップでO(1)
ルール更新テーブル全体の書き換え(サービスが多いほど遅い)マップエントリ単位の増分更新
バックエンド選択statisticモジュールの確率分岐ランダムまたはMaglev一貫性ハッシュ
conntracknf_conntrack(グローバル、ロック競合)BPFマップベースの専用CT
ポリシー表現IP/CIDRベースidentityベース(ラベルの意味を保持)
L7認識不可Envoy連携で可能

サービスが数百程度なら体感差は小さいですが、数千を超えるとiptablesの更新遅延と初回パケットのレイテンシが運用上の問題として表面化します。eBPFデータパスはこの2軸(ルックアップコスト、更新コスト)の両方を定数時間にします。

kube-proxy replacementの原理

Ciliumのkube-proxy replacement(KPR)は、Kubernetesのサービス抽象を2層のeBPFマップで実装します。

サービスルックアップの流れ (簡略化)

  宛先 10.96.0.10:443 (ClusterIP)
        |
        v
  cilium_lb4_services_v2  マップ
    key:   (IP, port, scope)
    value: (backend count, rev_nat index, flags)
        |
        v
  cilium_lb4_backends_v3  マップ
    key:   backend id
    value: (pod IP, port, state)
        |
        v
  DNAT実行 + cilium_lb4_reverse_nat に逆変換を記録

エージェントはKubernetes APIでService/EndpointSliceをwatchし、変更分のみをマップに反映します。データプレーンはカーネル内で完結しているため、エージェントが一時的に停止しても既存の接続と既存のサービス変換は動作し続けます。

DSRとMaglev

NodePortトラフィックがバックエンドのないノードに着信すると、別のノードへもう1ホップ発生します。デフォルトのSNATモードでは応答が再び入口ノードを経由して戻りますが、**DSR(Direct Server Return)**モードではバックエンドPodがクライアントに直接応答し、応答経路のホップと帯域消費を削減し、クライアントのソースIPも保持されます。

Maglev一貫性ハッシュはGoogleが発表したL4ロードバランサのアルゴリズムで、バックエンドの増減時にも既存フローの大部分が同じバックエンドにマッピングされ続けるようにします。複数ノードがECMPで同じVIPトラフィックを受ける構成では、ノード間でバックエンド選択が一貫し、ノード障害時の接続切断を最小化します。

# helm values - KPR + DSR + Maglev の例
kubeProxyReplacement: true
loadBalancer:
  mode: dsr
  algorithm: maglev
  acceleration: native     # XDP高速化 (対応NICのみ)
maglev:
  tableSize: 16381         # バックエンド数 x 100 より大きい素数を推奨

DSRはネイティブルーティングモードで最も自然に動作し、トンネルモードと組み合わせる場合はGeneve DSRなどの別オプションが必要になる点を覚えておいてください。

identityベースのセキュリティモデル

Ciliumセキュリティモデルの核心は「IPではなくidentityで判断する」ことです。

Podラベル                          identity (数値)
---------------------------       ----------------
app=frontend, env=prod      --->  identity 51234
app=backend,  env=prod      --->  identity 60917
(予約) host                 --->  1
(予約) world                --->  2
(予約) remote-node          --->  6

ipcacheマップ: IP/CIDR -> identity
  10.0.1.23/32 -> 51234
  10.0.2.40/32 -> 60917
  0.0.0.0/0    -> 2 (world)

動作の順序は次のとおりです。

  1. Podが作成されると、エージェントがセキュリティ関連のラベル集合を抽出し、同一のラベル集合には同一の数値identityを割り当てます(kvstoreまたはCRDによるグローバル合意)。
  2. すべてのノードのcilium_ipcacheマップに、該当PodのIPとidentityのマッピングが伝播されます。
  3. パケット送信時、ソースidentityはメタデータ(トンネルヘッダまたはパケットマーク)として運ばれるか、受信側でipcacheルックアップにより復元されます。
  4. 受信エンドポイントのポリシーマップは「identity 51234がTCP 8080にアクセスできるか」をO(1)で判定します。

このモデルの利点は、ポリシーの意味がIPの変動と無関係に維持されることです。Podが再スケジュールされてIPが変わっても、ラベルが同じならidentityも同じで、ポリシーマップを修正する必要はありません。ipcacheの1箇所だけが更新されれば済みます。

# identityとipcacheを直接確認
kubectl -n kube-system exec ds/cilium -- cilium identity list
kubectl -n kube-system exec ds/cilium -- cilium bpf ipcache list
kubectl -n kube-system exec ds/cilium -- cilium endpoint list

トンネリング vs ネイティブルーティング

Pod間のパケットがノード境界を越える方法は大きく2つあります。

トンネルモード (VXLAN/Geneve)           ネイティブルーティング
+--------+  カプセル化  +--------+      +--------+  素のIP   +--------+
| node A | ==========> | node B |      | node A | --------> | node B |
+--------+  UDP 8472   +--------+      +--------+ ルータ/BGP +--------+
 Podパケットを外部ヘッダで包む            ネットワークがPodCIDRの経路を知る必要
基準VXLANGeneveネイティブルーティング (BGP/クラウド)
ネットワーク要件ノード間UDP 8472の許可のみノード間UDP 6081の許可のみアンダーレイがPodCIDRをルーティング可能であること
オーバーヘッド約50バイトのカプセル化約50バイト以上(可変オプション)なし
MTUへの影響あり(MTU縮小が必要)ありなし
DSR親和性限定的DSRオプションの運搬に有利最も自然
運用難易度低い低いBGPまたはクラウドルーティングの理解が必要
適した環境アンダーレイを制御できない環境メタデータ拡張が必要な場合オンプレBGP、ENIなどクラウドネイティブ

選択基準をざっくりまとめると:

  • アンダーレイネットワークを制御できない場合(社内共用網、単純なL3環境)→ トンネルモードから始める
  • オンプレでToRスイッチとBGPピアリングが可能 → ネイティブルーティング + BGP Control Plane
  • AWS/AzureでENI/Azure IPAMを使う → ネイティブルーティングが基本前提
# ネイティブルーティング + BGP の例 (helm values)
routingMode: native
ipv4NativeRoutingCIDR: 10.0.0.0/8
autoDirectNodeRoutes: true     # 同一L2の場合にノード間直接経路を設置
bgpControlPlane:
  enabled: true                # CiliumBGPPeeringPolicy/ClusterConfig を使用

IPAMモード

Pod IPをどこからどう割り当てるかも、データパス設計の一部です。

IPAMモード割り当て主体特徴
cluster-pool (デフォルト)Cilium operatorCiliumNode CRDでノード別PodCIDRを分配、柔軟なプールサイズ
kuberneteskube-controller-managerNode.spec.podCIDRを使用、既存クラスタの慣行と互換
eniAWS ENIPodにVPCネイティブIPを付与、ルーティングモードはnative
azureAzure IPAMAzure CNI Powered by Ciliumの基盤
multi-poolCilium operator名前空間/ノードごとに異なるプールを使用可能

cluster-poolモードで最初にプールを小さく取りすぎると、ノード増設時にCIDR枯渇でPodのスケジューリングが止まる可能性があるため、clusterPoolIPv4PodCIDRListは将来のノード数まで考慮して余裕を持たせるべきです。

インストールとアップグレードの実践

カーネル要件

機能最小カーネル
基本データパス4.19以上(実質的には5.4以上を推奨)
WireGuard暗号化5.6以上
XDP高速化NICドライバの対応 + 5.x推奨
BIG TCP、netkitなどの新機能6.x系

2026年現在、主要ディストリビューション(RHEL 9、Ubuntu 22.04/24.04)はすべて5.14以上のため通常は問題ありませんが、古いオンプレミスノードは必ずuname -rで確認してください。

helmインストール例

helm repo add cilium https://helm.cilium.io/
helm repo update

# kube-proxyなしでクラスタを作成したか、削除済みであることを想定
helm install cilium cilium/cilium \
  --version 1.18.5 \
  --namespace kube-system \
  -f values.yaml
# values.yaml — kube-proxy代替 + Hubble有効化の基本形
kubeProxyReplacement: true
k8sServiceHost: api.mycluster.internal   # KPR時はAPIサーバの直接指定が必須
k8sServicePort: 6443

routingMode: tunnel
tunnelProtocol: vxlan

ipam:
  mode: cluster-pool
  operator:
    clusterPoolIPv4PodCIDRList:
      - 10.128.0.0/12
    clusterPoolIPv4MaskSize: 24

hubble:
  enabled: true
  relay:
    enabled: true
  ui:
    enabled: true

operator:
  replicas: 2

prometheus:
  enabled: true

k8sServiceHostを指定する理由が重要です。kube-proxyがない場合、ClusterIPの変換はCiliumが担当しますが、Ciliumエージェント自身が起動する前はkubernetes.defaultのClusterIPを解決できません。鶏と卵の問題を避けるために、APIサーバの実アドレスを直接教えるのです。

cilium-cliの活用

# インストール状態の総合チェック
cilium status --wait

# クラスタ全体の接続性テスト (テストPodを自動デプロイ)
cilium connectivity test

# 設定の確認
cilium config view | grep -i kube-proxy

アップグレード手順

# 1) リリースノートのアップグレードガイドを必ず確認 (マイナーバージョンの飛ばしは禁止)
# 2) pre-flight検査で新イメージの事前プルとCRD互換性を確認
helm install cilium-preflight cilium/cilium --version 1.18.5 \
  --namespace kube-system \
  --set preflight.enabled=true \
  --set agent=false --set operator.enabled=false

# 3) preflight正常確認後に本アップグレード
helm upgrade cilium cilium/cilium --version 1.18.5 \
  --namespace kube-system -f values.yaml

# 4) ローリング再起動の状態を観察
kubectl -n kube-system rollout status ds/cilium

アップグレード時、エージェントPodが再起動してもeBPFプログラムとマップはカーネルに残るため、既存トラフィックが途切れないのが正常です。ただしマップレイアウトが変わるメジャーな変更では一時的な再生成が発生し得るため、リリースノートの確認が必須です。

kube-proxy代替の検証

KPRが実際に動作しているか、必ず自分の目で確認すべきです。

# 1) KPRモードの確認 - "True" 表示を確認
kubectl -n kube-system exec ds/cilium -- cilium status --verbose | grep -A3 KubeProxyReplacement

# 2) サービスがeBPFマップに入ったか確認
kubectl -n kube-system exec ds/cilium -- cilium service list
kubectl -n kube-system exec ds/cilium -- cilium bpf lb list

# 3) kube-proxyが本当にないか、iptablesの痕跡を確認
kubectl -n kube-system get ds kube-proxy 2>&1 || echo "kube-proxyなし - 正常"
iptables-save | grep -c KUBE-SVC || echo "KUBE-SVCチェーンなし - 正常"

# 4) 実際の接続テスト
kubectl run probe --image=curlimages/curl --rm -it --restart=Never -- \
  curl -s -o /dev/null -w "%{http_code}\n" http://my-service.default.svc.cluster.local

特に既存クラスタからkube-proxyを削除して移行する場合、残存するiptablesルールがeBPF経路と衝突し得るため、kube-proxy DaemonSet削除後にノードの残存KUBE-*チェーンを整理する手順(ノード再起動またはiptablesフラッシュ)を計画に含めるべきです。

パフォーマンスの観点 — なぜ速くなるのか

ベンチマーク数値は環境依存性が大きいため、構造的にどこで利得が生まれるかを理解することが重要です。

  1. ルックアップ計算量: iptablesはルール数に比例した走査、eBPFはハッシュマップで定数時間。サービス/ポリシー数が増えるほど差が広がります。
  2. 経路長の短縮: bpf_redirect_peerでvethペア通過時にソフトIRQ1サイクルを節約し、ホストルーティングモードでは上位netfilterスタックへの進入自体をスキップします。
  3. 更新コスト: デプロイが頻繁なクラスタでiptablesの全面書き換えはCPUスパイクとルール適用遅延を生みますが、マップの増分更新はほぼ無コストです。
  4. XDP: ドライバレベルの処理でsk_buff割り当て前にLB/ドロップを決定し、NodePortパケットの処理量を大幅に引き上げます。

逆にコストもあります。L7ポリシーを有効にすると該当トラフィックはユーザ空間のEnvoyを経由するためレイテンシが追加され、トンネルモードはカプセル化オーバーヘッドとMTU縮小を伴います。「すべてが速くなる」のではなく「どの機能を有効にするとどのコストがかかるか」を把握して選択するのが運用の核心です。

トラブルシューティングの基礎

# リアルタイムのデータパスイベント観察 (ドロップ理由を含む)
kubectl -n kube-system exec ds/cilium -- cilium monitor --type drop
kubectl -n kube-system exec ds/cilium -- cilium monitor --type policy-verdict

# 特定エンドポイントの詳細 (ポリシー状態、identityの確認)
kubectl -n kube-system exec ds/cilium -- cilium endpoint list
kubectl -n kube-system exec ds/cilium -- cilium endpoint get 1234

# conntrack/NATマップの直接照会
kubectl -n kube-system exec ds/cilium -- cilium bpf ct list global | head
kubectl -n kube-system exec ds/cilium -- cilium bpf nat list | head

# 総合診断バンドルの収集 (イシューレポート添付用)
cilium sysdump
# またはノード単位で
kubectl -n kube-system exec ds/cilium -- cilium-bugtool

cilium monitorのドロップ理由コードが第一の手がかりです。よく見る理由は次のとおりです。

ドロップ理由よくある原因
Policy deniedポリシー未許可 — identityとポートを再確認
CT: Map insertion failedconntrackマップ満杯 — bpf-ct-global-tcp-maxを調整
Unsupported L3 protocol非IPトラフィック — 意図したものか確認
Stale or unroutable IPipcache不一致 — エージェント/ノード間の同期を点検
Missed tail callプログラムロード不一致 — エージェント再起動、バージョン混在を確認

運用上の注意点

  • バージョン互換: Ciliumのマイナーバージョンは順次アップグレードのみサポートします。1.16から1.18への飛ばしは不可です。Kubernetesバージョンのサポートマトリクスも併せて確認してください。
  • ポリシー移行: Calicoなどから移行する際、既存のNetworkPolicyはほとんどそのまま動作しますが、identityモデルの違いで挙動が微妙に変わり得る部分(特にipBlockとノードIPの扱い)はステージングで先に検証すべきです。
  • 予約identity: host、remote-node、world、kube-apiserverといった予約identityの意味を理解していないと、ノード発のトラフィックやヘルスチェックがポリシーにブロックされる事故が起きます。
  • リソース上限: 大規模クラスタではBPFマップサイズ(ct、ipcache、lbマップ)のデフォルト値を点検し、エージェントのメモリ要求をそれに合わせて調整すべきです。
  • マネージド環境の制約: GKE Dataplane V2、AKSのCiliumモードではhelm値の一部がクラウド側で固定されています。セルフインストールと同じ自由度は期待できません。

導入チェックリスト

  • ノードのカーネルバージョンが5.4以上か(WireGuardが必要なら5.6以上)
  • ルーティングモードの決定(トンネル vs ネイティブ)とその根拠を文書化したか
  • PodCIDRプールが今後3年のノード増設に耐えられるか
  • kubeProxyReplacement時にk8sServiceHost/Portを指定したか
  • トンネルモードならノード間でUDP 8472(VXLAN)または6081(Geneve)が開いているか
  • MTU計算(カプセル化/暗号化オーバーヘッドの反映)を済ませたか
  • cilium connectivity testが全項目パスするか
  • kube-proxy削除後の残存iptablesチェーン整理手順があるか
  • cilium status、BPFマップ使用率、ドロップカウントをモニタリングに接続したか
  • アップグレード時のpreflight手順がランブックに含まれているか

おわりに

Ciliumの本質は「Kubernetesネットワーキングの意味論(サービス、ラベル、ポリシー)をカーネル内のデータ構造へ直接下ろしたこと」です。iptables時代にはラベルという意味がIPルールへの翻訳の過程で失われていましたが、eBPFデータパスではidentityという形でカーネルまで保持されます。この構造を理解すれば、kube-proxy replacement、ポリシー評価、トラブルシューティングがすべて1つの絵としてつながります。次回はこの上に乗るネットワークポリシー(L3〜L7、DNS)を実践YAML中心に扱います。

参考資料