← 一覧へ戻る

Claude が書いた記事

ネットワーク屋が降りる Kubernetes:Pod IP は物理 LAN にどう乗っているか

Pod IP / cni0 / veth / VXLAN を、物理 LAN との対比で順に解剖した学習ログ。「Pod は IP を持つが、その IP は物理 LAN には存在しない」を起点に、CNI が物理ネットワークの上にオーバーレイをどう重ねているかを腹落ちさせるまでの記録。

俺の最初の疑問

前回 HBM の記事で LLM 推論サーバーの「サーバー内」を解剖した。次は「そのサーバーの上で動いている Kubernetes」が気になる。

特に俺はネットワーク屋なので、引っかかったのはここ。

Pod には IP がついているらしい。10.244.x.x みたいな。でもうちの社内 LAN は 10.0.x.x やぞ? その Pod IP はどこに居るんや?物理 LAN とどう繋がっとるんや?

腹落ちするまで、k8s の論理階層を一旦上から下まで降りて、ネットワーク側に登り直す経路を取った。

まず一言でいうと

Pod ネットワークは、物理 LAN の上に重ねた「オーバーレイ」

Pod IP は物理 LAN には存在しない、クラスタ内だけで通用する別レイヤの IP。物理 LAN は、その Pod ネットワークを「運ぶ運送網」として下で動いている。

VPN や VLAN を作ってその上に IP を振っている、と思うと近い。違いは「k8s 用にカスタムされた専用オーバーレイ規格」を使うことと、Pod が短命なので 動的に IP が払い出されては消える こと。

何と比べるとわかるか

普通の物理ネットワークと、k8s の Pod ネットワークは別レイヤに居る:

物理 LANPod ネットワーク
IP プール10.0.x.x (社内 VLAN 等)10.244.x.x (CNI が払う)
持ち主Node (VM のホスト)Pod
ルータ物理ルータ / クラウドの VPCLinux kernel + CNI プラグイン
Node 境界の越え方普通のルーティングVXLAN カプセル化 or BGP 広告
寿命半永久数秒〜数日 (Pod の寿命次第)

物理ネットワークの上に、k8s 用のもう一段のネットワークを乗せている、という二層構造として見ると整理がつく。

何が問題なのか

Pod は 短命で何百〜何千個立つ。それぞれに物理 LAN の IP を払うのは無理がある:

  • 物理 LAN の IP プールが枯渇する
  • Pod が死ぬ・生まれる頻度が高すぎて、DHCP では追いつかない
  • Pod が Node を跨いで移動する場合、物理ルートを毎回書き換える必要が出る
  • 物理ネットワーク管理者と k8s 運用者の責任境界が曖昧になる

「クラスタ内専用の IP プールを別に作って、CNI が動的に払う」 という解決策。物理 LAN とは独立した名前空間で IP を回すことで、上の問題を全部回避する。

図で見る

まず k8s の全体像。1 つのクラスタが複数の Node からなり、各 Node の中で Pod が動いている:

Cluster = 複数の Node を 1 つの論理単位として扱う仕組み。Control Plane Node に kube-apiserver / etcd / scheduler / controller-manager が Pod として乗り、Worker Node にアプリの Pod が乗る。各 Node は VM + Hypervisor + 物理サーバーの上に立つ。Node 同士は kube-apiserver を中心としたネットワークで繋がっている。

次に 1 つの Node の中、Pod がネットワーク的にどう繋がっているか:

Node の root NET namespace に eth0 / cni0 / iptables の 3 兄弟。各 Pod は独自の NET namespace を持ち、veth pair で cni0 に繋がる。各 Pod の eth0 には Pod CIDR から払われた IP (10.244.1.7 等)。中の Container はこの ns を共有する。

最後に、Pod 同士の通信が実際にどう流れるか (同 Node / 異 Node):

同 Node なら veth → cni0 (L2 で MAC を見るだけ) → veth で完結。異 Node なら veth → cni0 → routing / iptables → eth0 → 物理 LAN → 反対側 Node の eth0 → routing → cni0 → veth、と長くなる。Pod IP は最後まで変わらず NAT もしない、というのが k8s ネットワークの鉄則。

混乱しやすいポイント

① IP を持つのは Pod、Container ではない

NET namespace は Pod 単位で分離されている (Container 単位ではない)。同じ Pod 内の Container たちは同じ NET ns に join しているので:

  • 全員が同じ Pod IP を持つ
  • 互いに localhost で通信できる
  • 同じポート番号は Pod 内で 1 個しか使えない

② cni0 は L2 ブリッジだが、自身に IP を持って「L3 入口」も兼ねる

ここが一番引っかかった。「L2 と言ったのに、ゲートウェイ IP を持つって矛盾やん」と。

メンタルモデルは Layer 3 スイッチ

  • 普通の L2 スイッチは MAC を見てフレームを転送するだけ
  • L3 スイッチは VLAN ごとに SVI (Switch Virtual Interface) を持ち、SVI に IP を振る
  • すると VLAN 内は L2 で完結し、VLAN を跨ぐ通信は SVI 経由で L3 ルーティングできる

cni0 もこれと同じ二役構造:

  • L2 機能: 同 Node の Pod 同士の通信を MAC で転送
  • L3 入口: cni0 IF に IP (10.244.1.1 等) が振ってあり、その IP 宛のパケットは kernel の routing に渡す

実際の L3 ルーティングは kernel が行うので、「cni0 がルーティングする」というよりは「cni0 はゲートウェイ IP の持ち主、ルーティングは kernel」が正確。

③ Pod の default gateway = cni0 の IP

Pod 内の routing table:

10.244.1.0/24 dev eth0   proto kernel scope link src 10.244.1.7
default via 10.244.1.1 dev eth0

普通の Linux ホストと同じ。Pod から見れば「ゲートウェイは 10.244.1.1 (cni0)」というだけ。ARP で MAC 引いて L2 で送る、いつもの動き。

④ Pod IP は最後まで変わらない (NAT 禁止)

k8s ネットワークの仕様として 「Pod 同士の通信で NAT をしてはいけない」 という鉄則がある。Src=10.244.1.7, Dst=10.244.2.5 のまま、Node を跨いでも IP は変わらない。

これがあるおかげで Pod 側はネットワーク構成を意識せず、ただの IP 通信として書ける。代わりに Node 境界をどう越えるかの責任が CNI 側に乗る

⑤ Node 越えは CNI ごとに別の戦略

NAT 禁止の制約下で「Pod IP のまま物理 LAN を渡す」方法は複数ある:

CNI戦略
Flannel (VXLAN)Pod パケットを UDP / 4789 にカプセル化して Node 間で運ぶ。物理 LAN は外側だけ見える
Calico (BGP)各 Node が自分の Pod CIDR を BGP で物理ルータに広告。物理 LAN がそのまま Pod IP をルーティング
Cilium (eBPF)カーネル内 eBPF プログラムが直接転送。cni0 や iptables を bypass する高速経路を持つ

⑥ flannel.1 は kernel の中の仮想 IF、flanneld は userspace

Flannel を例にすると、Node 越えで使われる flannel.1 という IF は:

  • kernel の中にある net_device 構造体
  • vxlan ドライバが挙動を提供 (パケットの encap / decap を実行)
  • Pod の NET ns には存在しない、Node の root NET ns に居る

一方、flanneld という userspace デーモンは:

  • k8s API を見て各 Node の Pod CIDR を学習
  • kernel に「ここを通って」と routing / FDB を入れるだけ
  • 毎パケットには介在しない (介在したら遅すぎる)

これが 「ユーザー空間 = 制御平面、kernel = データ平面」 の典型パターン。iptables や conntrack や eBPF も同じ構造。

たとえ話

物流業のコンテナ船で考える。

  • 物理 LAN = 海 (世界中の物流網)
  • Node = 港 (コンテナを積み下ろしする拠点)
  • Pod = 港の倉庫の内線住所 (その港の中でだけ通用するアドレス)
  • cni0 = 港の管理事務所兼仕分け場 (同じ港の倉庫同士はここで完結。外への荷物はここから出す)
  • Pod IP = 倉庫の内線番号
  • 物理ホスト IP = 港の住所

Pod から見ると「他の港の倉庫宛に荷物を送る」のは「宛先 = 別港の倉庫の内線番号 で送るだけ」。実際の運搬方法は知らない。

CNI が裏で:

  • Flannel = 港でコンテナ船 (VXLAN) に荷物を積む → 反対側の港で下ろす → 中身の住所は変わらない
  • Calico = コンテナ船を使わず、荷物に「あの港のこの倉庫宛」とそのまま書いて海に流す。海の管制官 (物理 LAN のルータ) に「あの港にはこの倉庫番号が居る」と事前に教えてある (= BGP 広告)

どっちも宛先の倉庫の内線番号 (Pod IP) は変わらない。違うのは「港の外をどう運ぶか」の戦略だけ。

ニュースを読むための変換表

ニュース / ドキュメントの言葉つまり何の話
PodContainer 1 + 個を束ねた k8s の最小実行単位。NET ns を共有する
Nodekubelet が動いている OS (VM or ベアメタル)。クラスタの構成員 1 つ
ClusterNode を束ねた 1 つの論理単位
Pod IP / Pod CIDRクラスタ内専用の Pod 用 IP プール
Service IP / ClusterIPk8s が払う仮想 IP (Pod IP の上にさらに重なる別レイヤ)
CNIk8s と物理ネットワークを繋ぐプラグイン規格
Flannel / Calico / Cilium代表的な CNI プラグイン (3 強)
VXLANUDP / 4789 上で L2 フレームを運ぶオーバーレイ規格 (RFC 7348)
iptables / IPVSkube-proxy が裏で使うパケット転送ルール
veth pair仮想 NIC の表裏ペア。Pod を Node の bridge に繋ぐケーブル
containerd / runck8s ノードで動くコンテナランタイム (Docker daemon の後継)
深掘りメニュー 次におすすめのトピック

この記事の続きとして、未来の自分が次に掘るといいトピック。

  • Service IP と kube-proxy ── Pod IP の上にもう一段ある仮想 IP (10.96.x.x) と、iptables / IPVS でのロードバランス。ネットワークの次の一段。
  • Ingress / LoadBalancer ── 外から Pod へ入る側とクラウド LB 連携。今回の「中の配線」の外向き版。
  • CNI の中身比較 (Calico の BGP / Cilium の eBPF) ── プラグインで配線がどう変わるか。物理ネットワーク側への要求の違いが見える。
  • DevicePlugin と nvidia.com/gpu ── GPU 8 枚が k8s からどう見えるか。LLM サーバーの記事と直結する入口。
  • ネットワーク管理者から見たクラスタ運用 ── BGP オペレータとして何を見るか。物理 LAN 側からの視点。