Kubernetes 解体新書Part 2 ネットワーク

Docker と Kubernetes のネットワークは何が違うのか

別々のノードで動く Pod 同士は、どう通信するのでしょうか。ノード、Pod、Service の IP の役割を整理します。

  • L1 Web ターミナル
  • 更新 2026年10月6日

Web アプリとデータベースが別々のノードに置かれても、通信は届いてほしい。 Kubernetes では、Pod がどのノードで動くかにかかわらず、接続できるネットワークが必要です。

前の章までは、1 台のホストの中の配線を見ました。 今度はノード間をつなぎ、Pod の IP アドレスまで通信が届く経路を追います。 ノード、Pod、Service に付く IP の役割の違いも、ここで整理します。

GOAL この章のゴール

  • ノードをまたぐ Pod 同士の通信を図で追える
  • パケットを包んで運ぶ方法と、経路を知らせる方法を区別できる
  • ノード、Pod、Service の IP の役割を見分けられる

Docker のブリッジが 1 台で閉じる理由

第 1 章で見たとおり、docker0 はホストのカーネルの中にあるソフトウェアのスイッチで、コンテナの veth はそこにつながっています。 Docker のドキュメントも、ユーザー定義のブリッジは同じ Docker ホスト上のコンテナ同士をつなぐもので、別のホストのコンテナと通信するには overlay ネットワーク (Swarm) を使う、と説明しています。

冒頭の 2 台の例で、1 台目の web から 172.17.0.3 に送るとします。 1 台目のカーネルは、その宛先が自分の docker0 につながっていると判断して、1 台目の中で配送を終えます。 2 台目にそのアドレスのコンテナが居ても、パケットはホストの外に出ません。 2 台目のコンテナに届けるには、-p 8080:80 で 2 台目のホストの IP とポートを公開し、そこ宛てに送るしかありません。 この宛先はコンテナが入れ替わるたびに変わります。

シングルホストの Docker と、複数ノードをまたぐ Kubernetes左は 1 台のホストの中で docker0 に閉じた 172.17.0.0/16。右は 3 台のノードをまたいで 1 つの Pod ネットワーク 10.244.0.0/16 が張られ、どのノードの Pod とも直接届く。Docker: 1 台で閉じる172.17.0.0/16web.2db.3docker0隣のホストのコンテナとは話せないKubernetes: ノードをまたいで 1 つの Pod ネットワーク10.244.0.0/16Node 1web10.244.1.5Node 2api10.244.2.3Node 3db10.244.3.9どのノードで動いても同じように届く (CNI の仕事)
図 1Docker はホストの中に閉じた 172.17.0.0/16。Kubernetes はノードをまたいで 1 つの Pod ネットワークが張られる。

Kubernetes が Pod のネットワークに求める条件

Kubernetes では、scheduler が Pod をどのノードに置くかを決め、ノードが壊れれば別のノードで作り直します。 置き場所によって通信の仕方が変わると、アプリはノードを意識して書くことになります。 そこで公式ドキュメント「The Kubernetes network model」は、ネットワークに次の条件を課しています。

  • Pod ごとにクラスタ内で一意の IP を持つ:Pod は専用の network namespace を 1 つ持ち、Pod 内のコンテナはそれを共有して localhost で通信できる。
  • Pod 同士は NAT 無しで直接届く:同じノードでも別のノードでも、プロキシやアドレス変換を介さずに通信できる。受け取る側から見た送信元は、送った Pod の IP のまま。
  • ノード上のエージェントは、そのノードの全 Pod に届く:kubelet などが Pod にヘルスチェックを送れる。
  • Service は安定した IP とホスト名を与える:Pod が入れ替わっても変わらない入口になる (次の節で扱います)。

同じドキュメントは、Pod はポートの割り当て、名前付け、サービスディスカバリ、負荷分散の観点で VM や物理ホストのように扱える、とも書いています。 Docker のブリッジは、冒頭の例のように 1 つ目の条件がホストをまたぐと崩れます。 ホストを越えるときは -p による NAT が入るので、2 つ目の条件も満たせません。

ノード間でどう届けるか

条件を満たすには、Pod の IP の範囲 (Pod CIDR) をノードごとに重ならないように分けて配り、ノードをまたぐときも Pod の IP のままパケットを届ける必要があります。 例として、Node 1 (10.0.1.11) が 10.244.0.0/24、Node 2 (10.0.1.12) が 10.244.1.0/24 を持つとします。 Node 1 の Pod 10.244.0.23 が、Node 2 の Pod 10.244.1.5 へ送るパケットを考えます。 ノードの間にあるスイッチやルーターが知っているのはノードの IP (10.0.1.x) だけで、10.244.1.5 をどこへ運べばよいか分かりません。 解決は 2 通りあります。

1 つ目は オーバーレイネットワークオーバーレイネットワークPod のパケットをそのままノード IP 宛ての UDP パケットの中身にして運び、相手ノードで取り出す方式。物理ネットワークからは、ノード同士が UDP で話しているようにしか見えない。用語集で見る です。 Node 1 のカーネルは「10.244.1.0/24 は Node 2 へのトンネルの先」という経路を持っていて、10.244.0.23 → 10.244.1.5 のパケットに VXLAN のヘッダを付け、その外側に 10.0.1.11 → 10.0.1.12、UDP 宛先ポート 8472 の IP ヘッダを付けて送り出します。 途中のネットワークから見えるのは外側のヘッダだけなので、ノード同士の普通の UDP 通信として通ります。 Node 2 は外側のヘッダを外し、中の元のパケットをそのまま Pod 10.244.1.5 に渡します。 中身の送信元と宛先は書き換えられていないので、NAT 無しという条件を満たします。 代償は、外側のヘッダ分だけ 1 パケットに入るデータ量 (MTU) が減ることで、Cilium のドキュメントは VXLAN で 1 パケットあたり 50 バイトと書いています。

2 つ目は、パケットを包まず、ノード間のネットワークに経路を教える方法です。 Node 1 の経路表に「10.244.1.0/24 は 10.0.1.12 へ」と書いておけば、Pod のパケットは Pod の IP のまま Node 2 に届きます。 この経路をルーターに伝える手段が、各ノードが BGP でルーターに知らせる方式と、クラウドの VPC のルートテーブルに「10.244.1.0/24 → Node 2」を書く方式です。 包まないぶん速く、MTU も減りませんが、ノード間のネットワークが Pod CIDR を運べる必要があります。 Cilium のドキュメントは、カプセル化は「ノード同士が IP/UDP で届きさえすればよく、ノード間のネットワークが Pod CIDR を知る必要が無い」、ネイティブルーティングは「ノードをつなぐネットワークが Pod CIDR をルーティングできる必要がある」と対比しています。

どちらの方法でも、ノードごとに Pod CIDR を割り当て、Pod の起動時に IP と経路を設定する仕事が要ります。 公式ドキュメントによれば、この Pod ネットワークを管理するのは Kubernetes 本体の外にある「Pod ネットワークの実装」で、Linux ではコンテナランタイムが CNI という取り決めでそれを呼ぶので、実装は CNI プラグインと呼ばれます (第 4 章)。

観点DockerKubernetes
前提シングルホスト複数ノードの分散システム
コンテナの IPホスト内だけで有効 (172.17.0.0/16 など)クラスタ内のどこからでも届く Pod IP
外から入るホストのポートに NAT (-p 80:80)Service (NodePort / LoadBalancer)、Gateway
名前解決dockerd 内蔵 DNS (127.0.0.11)クラスタの DNS (CoreDNS)、svc.cluster.local
ノード間の疎通無い (overlay は Swarm 向け)CNI プラグインの仕事
内部ロードバランサー無いService の ClusterIP

3 つのネットワーク

条件を満たした結果、Kubernetes のクラスタには IP の世界が 3 つ重なります。 公式ドキュメント「Cluster Networking」も、Pod、Service、Node のそれぞれに重ならない IP の範囲を割り当てる必要がある、と書いています。

3 つのネットワーク: Node / Pod / Serviceノードが持つ Node ネットワーク、ノードをまたいで Pod が持つ Pod ネットワーク、どの NIC にも付いていない IP の Service ネットワークの 3 層。Service ネットワーク 10.96.0.0/12この IP を持つ NIC は無い。各ノードの書き換え表 (kube-proxy / eBPF) が宛先を Pod IP に変える。web 10.96.12.34:80Pod ネットワーク 10.244.0.0/16 (CNI がノード間を繋ぐ)Node A10.0.1.10web10.244.1.5nginxweb10.244.1.6nginxNode B10.0.1.11web10.244.2.3nginxapi10.244.2.4appVXLAN / BGP / VPC routeEndpointSliceNode ネットワーク 10.0.1.0/24 (物理 / VPC)LB / ルーター
図 2Node / Pod / Service の 3 層。Pod ネットワークはノードをまたぎ、Service ネットワークの IP を持つ NIC はどこにも無い。
  1. Node ネットワーク:物理 (またはクラウドの VPC) の IP。普通のサーバーが繋がるネットワークで、kubectl get nodes -o wide の INTERNAL-IP がこれです。クラスタを作る前から存在し、Kubernetes はこれをそのまま使います。
  2. Pod ネットワーク:Pod 1 つに 1 つ付く IP の範囲 (kubectl get pods -o wide の IP 列)。ノードをまたいでも直接届くように、CNI プラグインが、ノード間の通り道 (VXLAN のトンネル、BGP で配る経路、クラウドのルートテーブル) を用意します。
  3. Service ネットワーク:Service に付く IP (ClusterIP) の範囲。10.96.0.10 のような IP ですが、この IP を持つ NIC はどのノードにも Pod にもありません。公式ドキュメントは「Pod の IP が固定の宛先に届くのと違い、Service の IP はどのホストも応答しない」と書いています。それでも Pod から curl http://10.96.0.10/ が届くのは、パケットがノードの Linux カーネルを通るときに、宛先が Pod の IP に書き換えられているからです。その書き換えルールを各ノードに入れる仕組み (kube-proxy や Cilium) は第 5 章で扱います。書き換えの対象は Service に書かれたプロトコルとポート (TCP や UDP) だけなので、ICMP の ping は書き換えられず、応答する NIC も無いため返りません。

3 つのうち、Kubernetes 側のコンポーネントが作るのは Pod ネットワークと Service ネットワークの 2 つです。 前者は CNI プラグイン、後者は書き換えルールを入れる仕組みの担当で、それぞれ第 4 章と第 5 章で扱います。

何が変わって、何が変わらないか

3 層に分かれている理由は、変わるものと変わらないものを分離するためです。 Pod は使い捨てで、入れ替わるたびに IP が変わります。 Node が障害を起こせば、その上の Pod は別の Node で作り直され、やはり IP が変わります。 Service の IP だけが変わりません。

下の図で、Pod を入れ替えたり Node B を落としたりしてみてください。

Service ネットワークどの NIC にも付いていない IP。Pod が何回入れ替わっても変わらないweb 10.96.12.34:80endpoints: 4 個Pod ネットワーク 10.244.0.0/16Node A10.0.1.10Node B 10.0.1.11web-110.244.1.5web-210.244.1.6web-310.244.2.3web-410.244.2.4Node ネットワーク 10.0.1.0/24 (物理 / VPC)
  1. Service web は 4 つの Pod を向いています

Pod ネットワークの IP は使い捨て、Service ネットワークの IP は安定、Node ネットワークはその土台。3 層の役割分担がこれです。

Service が「向き先」を持ち続けてくれるので、呼ぶ側は Pod の IP を知らなくて済みます。 Docker の内蔵 DNS がやっていた簡易的なサービスディスカバリを、Kubernetes では Service とクラスタの DNS が分散システムの規模でやっている、と捉えると繋がります。

3 つの IP を並べて見る
kubectl get nodes -o wide | awk '{print $1, $6}'
kubectl get pods -A -o wide | awk 'NR>1 {print $2, $7, $8}' | head
kubectl get svc -A | head

出力例

NAME INTERNAL-IP
tenant-abc-control-plane-xxxx 10.0.1.11
coredns-7b5c4 10.244.0.5 tenant-abc-control-plane-xxxx
kubernetes   ClusterIP   10.96.0.1    <none>   443/TCP

数字を見るだけで、10.0.1.x は Node、10.244.x.x は Pod、10.96.x.x は Service と分かるようになれば、この章は合格です (CIDR はクラスタごとに違います。自分のクラスタの値は kubectl cluster-info dump | grep -m1 cluster-cidr などで確認できます)。

本物でも「Pod を入れ替えても Service は変わらない」を見ておきます。 CoreDNS の Pod を 1 つ消して、Pod IP と Service IP の前後を比べます。

Pod を消しても Service の IP は変わらない
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide | awk '{print $1, $6}'
kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}{"\n"}'
kubectl -n kube-system delete pod -l k8s-app=kube-dns --wait=false
sleep 10
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide | awk '{print $1, $6}'
kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIP}{"\n"}'

出力例

coredns-7b5c4d9c8-abcde 10.244.0.5
10.96.0.10
coredns-7b5c4d9c8-f9k2x 10.244.0.31
10.96.0.10

docker port に何も出ない謎

Docker がコンテナランタイムだった時代のクラスタで、こういう経験をした人がいます。 type: LoadBalancer の Service で公開した Web UI がブラウザで開けるのに、ノードで docker port や docker ps を見ても、そのポートを公開しているコンテナが居ない。 Docker の感覚だと、80 番で開けるなら 0.0.0.0:80->80/tcp のコンテナがあるはずです。

Pod の NIC は CNI プラグインが作った veth で、docker0 には繋がっていません。 公式ドキュメントの言い方では、Pod の network namespace を用意するのはコンテナランタイム (CRI の実装) で、その中の NIC と IP を用意するのは Pod ネットワークの実装 (CNI プラグイン) です。 Docker のポート公開 (-p) はこの経路のどこにも登場しません。

docker port に何も出ないのに繋がる: Pod の NIC は docker0 に居ないLoadBalancer からノードの NodePort に入った通信は、ノード上の書き換え表で宛先を Pod IP に書き換えられ、CNI が作った veth を通って Pod に届く。docker0 もホストで listen するプロセスも経由しない。LoadBalancer:80Node10.0.1.11eth0 :30080NodePort (listen 無し)書き換え表kube-proxy / eBPFveth (CNI)web10.244.2.7nginxdocker0 / docker port(使われていない)DNAT → 10.244.2.7:80ss -ltnp に :30080 は出ない。listen するプロセスが無いから。
図 3LB → NodePort → ノードのカーネルで宛先を書き換え → veth → Pod。docker0 もホストで listen するプロセスも通らない。

ss -ltnp でも同じことが起きます。 ノードに入った NodePort (たとえば 30080 番) 宛てのパケットは、ノードのカーネルで宛先が Pod の IP とポートに書き換わり、そのまま Pod へ転送されます。 ホストのポートで listen するプロセスが無いので、ss -ltnp に 30080 番は出ません。

ふりかえり

QClusterIP に ping が通らない理由は?

ClusterIP を持つ NIC はどこにもありません。TCP / UDP の宛先としては、ノード上のルール (kube-proxy のルール、または Cilium の eBPF) が Pod の IP に書き換えて届けますが、ICMP echo は書き換えの対象になく、応答する NIC もありません。

QPod ネットワークをノードをまたいで繋ぐのは誰の仕事?

CNI プラグインが Pod に NIC を生やし、IP を配り、ノード間の経路を作ります。kube-proxy は Service の宛先の書き換え、CoreDNS は名前解決です。

QNode が障害を起こして Pod が別の Node で作り直されたとき、変わらないものは?

Pod は新しく作られるので名前も IP も変わります。Service の ClusterIP は Service オブジェクトが消えない限り変わらず、向き先の一覧だけが新しい Pod に更新されます。

参考