shop Namespace の Pod が http://api を呼び出し、リクエストがタイムアウトします。Pod は動いていて、Service も存在し、その EndpointSlice には準備完了のバックエンドが 2 つ載っています。原因は DNS、kube-proxy がノードに書いたルール、ノード間のオーバーレイ、先週誰かが適用した NetworkPolicy のどれかにあるかもしれません。それぞれ別の層で、別のコンポーネントが動かしています。
この記事では、図を使ってそれらの層を順に見ていきます。ネットワークモデル、Pod 間トラフィック、CNI プラグイン、Service、DNS、NetworkPolicy、そしてクラスターへの出入りのトラフィックです。
Kubernetes のネットワークモデル
Kubernetes のネットワークモデルには 3 つのルールがあります。各 Pod はクラスター全体で有効な固有の IP アドレスを持ち、その中のコンテナで共有します。各 Pod は、どのノード上にあっても、プロキシや NAT なしで他のすべての Pod に到達できます。そして kubelet のようなノードエージェントは、自分のノード上のすべての Pod に到達できます。
Kubernetes が API を定義し、実際の処理はインストールしたコンポーネントが行います。
| 層 | 提供するもの | 実装するもの |
|---|---|---|
| Pod ネットワーク | Pod ごとの IP、Pod 間の到達性 | CNI プラグイン |
| Service | Pod の集合に対する安定した仮想 IP と名前 | kube-proxy または eBPF による代替 |
| DNS | api.shop.svc.cluster.local のような名前 |
CoreDNS |
| NetworkPolicy | Pod トラフィックの許可ルール | CNI プラグイン |
| Ingress、Gateway API | クラスター外からのルーティング | 自分でインストールするコントローラー |
この分担を知っていると、多くの意外な挙動に説明がつきます。たとえば、API サーバーは受け付けるのに、プラグインが黙って無視する NetworkPolicy などです。
同じノード上の Pod 間通信
コンテナランタイムは Pod を起動するとき、ADD で CNI プラグインを呼び出します。多くのプラグインは veth ペアを作成し、IP を割り当てます。veth ペアは仮想ケーブルで、片端が Pod 内の eth0、もう片端がホスト側にあります。Flannel はホスト側の端を Linux ブリッジ cni0 に接続します。Calico はブリッジを使いません。各 Pod に /32 ルートを持たせ、ホストがそのルーターとして動作します。どちらの場合も、同じノード内のトラフィックにカプセル化も NAT も不要です。
ノードをまたぐ Pod 間通信:オーバーレイとネイティブルーティング
通常、各ノードは Pod 範囲の一部を持ちます。kube-controller-manager の --node-cidr-mask-size が既定の 24 なら、10.244.0.0/16 の範囲は 256 ノードにそれぞれ /24 を与えます。これは kubelet の既定である 110 Pod に対して 256 アドレスということです。ノード間では、プラグインに 2 つの選択肢があります。
| オーバーレイ(VXLAN、Geneve、IP-in-IP) | ネイティブルーティング(BGP、クラウドのルート、VPC IP) | |
|---|---|---|
| ノード間のネットワークから見えるもの | ノード IP のみ | Pod IP |
| 必要なもの | ノード間の IP 到達性 | Pod 範囲をルーティングするネットワーク、または Pod 用の VPC アドレス |
| パケットあたりのオーバーヘッド | IPv4 の VXLAN で 50 バイト、IP-in-IP で 20 バイト | なし |
| 例 | Flannel と Cilium の既定、Calico VXLAN | BGP を使う Calico、AWS VPC CNI、Azure CNI |
このオーバーヘッドは MTU の問題です。Calico の MTU ガイドによると、1,500 バイトのネットワークでは、IPv4 の VXLAN の内側にある Pod の MTU は 1,450 が必要です。ここを誤ると大きなパケットが消え、原因不明のハングのように見えます。
CNI プラグインの比較
| プラグイン | ノード間トラフィック | NetworkPolicy | 備考 |
|---|---|---|---|
| Flannel | 既定は VXLAN(UDP 8472)。共有のレイヤー 2 ネットワークでは host-gw |
Flannel 自体は適用しない。kube-network-policies か Calico を追加する | 小さくてシンプル |
| Calico | BGP、または VXLAN(UDP 4789)か IP-in-IP のオーバーレイ | フル対応。加えて Calico の Namespace 単位とグローバルのポリシー | iptables、nftables、eBPF、VPP、Windows のデータプレーン |
| Cilium | 既定は VXLAN(UDP 8472)、Geneve、またはネイティブルーティング | フル対応。加えてレイヤー 7 と DNS ベースのルール | eBPF データパス |
| AWS VPC CNI | オーバーレイなし。Pod は VPC アドレスを取得 | v1.14 以降、enableNetworkPolicy を指定 |
ノードあたりの Pod 数はインスタンスタイプで制限 |
AWS の制限は早い段階で効いてきます。既定の上限は ENI 数 × (ENI あたりの IP 数 − 1) + 2 なので、10 アドレスのインターフェースを 3 つ持つ m5.large では 3 × 9 + 2 = 29 Pod になります。プレフィックス委任でこの上限を引き上げられます。
Service:入れ替わる Pod の前に置く安定した IP
Pod の IP は、Pod が置き換わるたびに変わります。Service は Pod の集合に、安定した仮想 IP である ClusterIP と DNS 名を与えます。背後にある Pod は EndpointSlice が追跡します。
| タイプ | 得られるもの |
|---|---|
| ClusterIP(既定) | クラスター内の仮想 IP |
| NodePort | 加えて各ノードのポート。既定は 30000–32767 |
| LoadBalancer | 加えてクラウドまたはアドオンのロードバランサー |
| ExternalName | DNS エイリアス(CNAME)。プロキシはしない |
Headless(clusterIP: None) |
仮想 IP なし。DNS が Pod IP を返す |
ClusterIP を所有するマシンは 1 台もありません。各ノードで kube-proxy がカーネルを設定し、10.96.48.20:80 宛てのパケットを、クライアントのノードを出る前に 10.244.2.22:8080 のような Pod アドレスへ書き換えます(宛先 NAT)。Service はサーバーではなく、論理的なステップです。ルールはプロキシモードによって変わります。
| モード | Kubernetes 1.37 での状況 | 仕組み |
|---|---|---|
| iptables | Linux での既定 | Service とエンドポイントごとのルール。バックエンドはランダム |
| nftables | 1.33 から GA。将来の既定になる予定 | 大規模でも更新が速い。カーネル 5.13 以降が必要 |
| ipvs | 1.35 から非推奨。1.40 で既定では無効、1.43 で削除 | カーネルの IPVS テーブル、より多くの分散アルゴリズム |
| eBPF(Cilium、Calico) | kube-proxy を置き換える | eBPF プログラムでロードバランシング |
アップグレードで変わらないようにモードを明示的に設定し、IPVS からの移行を計画してください。nftables モードでは、NodePort は既定でノードのプライマリアドレスでのみ待ち受けます。
CoreDNS による DNS
CoreDNS は既定のクラスター DNS で、kubeadm は今も kube-dns という名前の Service の背後で動かします。各 Service には <service>.<namespace>.svc.cluster.local という名前が付き、これは ClusterIP に解決され、Headless Service では Pod IP に解決されます。kubelet は各 Pod の /etc/resolv.conf を書き込みます。kubeadm クラスターの shop では、通常次のようになります。
nameserver 10.96.0.10
search shop.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
落とし穴は ndots:5 です。api.stripe.com のようにドットが 5 つ未満の名前は、まずすべての検索ドメインを付けて試されるため、外部への 1 回の名前解決で失敗するクエリが 3 回発生することがあります。頻繁に使う名前には末尾にドットを付けるか、Pod の dnsConfig で ndots を下げるか、NodeLocal DNSCache を動かしてください。その後、外部の名前は DNS の仕組みで示した経路をたどります。
NetworkPolicy:既定で許可から最小権限へ
既定では、すべての Pod がどこからのトラフィックも受け付けます。いずれかの NetworkPolicy が Pod を ingress(または egress)で選択した時点から、いずれかのポリシーが許可したものだけが通ります。応答は自動的に許可されます。ポリシーは許可ルールを追加するだけなので、順序は関係ありません。各 Namespace はデフォルト拒否から始めます。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
そのうえで必要なものを開けます。ここでは web から api への TCP 8080 です。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-web
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
つまずきやすい点が 4 つあります。
- ポートは Pod のポートです。Service のポート 80 ではなく 8080 です。
- デフォルト拒否は web Pod も隔離します。そのため Gateway が web Pod に到達するには専用のルールが必要です。
- ハイフン 1 つで意味が変わります。 1 つの
fromエントリにnamespaceSelectorとpodSelectorを書くと両方に一致する必要があり、2 つのエントリに分けるとどちらか一方で足ります。 - egress のデフォルト拒否は DNS もブロックします。先に
kube-dnsへのポート 53 を許可してください。
NetworkPolicy には拒否ルールも、クラスター全体のスコープも、TLS に関する機能もありません。ワークロード間の相互 TLS については、サービスメッシュのアーキテクチャ図を参照してください。
出入りのトラフィック:Ingress、Gateway API、egress
Ingress は安定していますが凍結されており、Kubernetes プロジェクトは代わりに Gateway API を推奨しています。コミュニティの Ingress NGINX コントローラーは 2026 年 3 月に廃止され、セキュリティ修正を受け取れなくなりましたが、既存の環境は気づかれないまま動き続けています。
Gateway API は役割で仕事を分けます。プラットフォームの担当者がリスナーと証明書を持つ Gateway を運用し、アプリケーションチームが自分の Namespace から HTTPRoute を関連付けます。2023 年 10 月に GA となり、2026 年のリリースで TLSRoute、TCPRoute、UDPRoute も標準になりました。Kubernetes Ingress の図は、この経路をホップごとにたどっています。
egress は見落とされがちな方向です。クラスターから出るトラフィックは通常ノードの IP に変換されます。AWS VPC CNI も VPC 外の宛先に対してそうしています。制限するには、外部の宛先を IP 範囲としてしか扱えない egress ルール、Cilium の toFQDNs のような DNS 対応のルール、パートナーが固定の送信元 IP を必要とする場合の egress gateway を使います。
Kubernetes ネットワーク図
ダイアグラムの内容
- worker-1 · pods 10.244.1.0/24
- worker-2 · pods 10.244.2.0/24
- Users
- public-gw
- web · 10.244.1.12
- coredns · 10.244.1.5
- api · 10.244.2.21
- api · 10.244.2.22
- api · 10.96.48.20
- api-allow-web
- HTTPS 443
- HTTPRoute
- DNS: api.shop.svc.cluster.local via kube-dns 10.96.0.10
- TCP 10.96.48.20:80
- DNAT 10.244.2.21:8080
- DNAT 10.244.2.22:8080
- VXLAN overlay over UDP · node IP to node IP
図の読み方は次のとおりです。
- 2 つのワーカーノードには、それぞれ /24 の Pod 範囲がラベルとして付いています。
- 青は南北方向です: ユーザーは HTTPS 443 で
public-gwに到達し、HTTPRoute が web Pod に送ります。 - アンバーは DNS です: web は 10.96.0.10 の
kube-dns経由で CoreDNS にapi.shop.svc.cluster.localを問い合わせ、10.96.48.20 を得ます。 - ティールは Service の経路です: web は 10.96.48.20:80 に接続し、worker-1 の kube-proxy のルールがそれをポート 8080 の api Pod に書き換えます。
- グレーはオーバーレイです: パケットはノード IP 間で VXLAN の中に入って worker-2 へ渡ります。
- 赤はポリシーです:
api-allow-webはapp=webの Pod だけが api Pod に到達できるようにします。 - Markdown ブロックには、数値の背後にある既定値が載っています。
Kubernetes ネットワーク図をわかりやすく描く
- ノードには Pod 範囲、Pod には IP をラベルとして付けます。ログで検索する情報だからです。
- Service は論理的なステップとして描き、書き換えが起きる場所を注記します。
- エッジにはアドレス、ポート、プロトコルを付け、Service の後は Pod のポートに切り替えます。
- 南北と東西の色を分け、オーバーレイはノード間に 1 回だけ描きます。
- 各ポリシーは選択する Pod に接続し、許可する送信元を示します。
- 省くもの: kubelet、API サーバー、追加のレプリカ。コントロールプレーンは Kubernetes アーキテクチャ図で扱っています。
- よくある間違い: パケット経路に kube-proxy を描く(kube-proxy はルールを書くだけで、転送するのはカーネルです)、DNS がない、ポリシーを無視するプラグインにポリシーを描く。
Archboard で作る
- Google または GitHub でサインインし、ダイアグラムを作成します。無料の Kubernetes YAML をダイアグラムに変換ツールを使うと、既存のマニフェストから最初の下書きを作れます。
- 基本パックから フレームノードを 2 つ追加し、worker-1 と worker-2 として Pod 範囲のラベルを付けます。
- Kubernetes パックから、IP を付けた Pod ノードを 4 つ追加します。worker-1 に web と coredns、worker-2 に api Pod を 2 つです。
- フレームの間に Service(ClusterIP、ポート 80 から 8080)を追加し、web からと 2 つの api Pod へのエッジにラベルを付けます。
app=apiを選択する NetworkPolicy を追加し、api Pod へ点線のエッジで接続します。- ネットワークパックからインターネットノードを、HTTPS 443 のリスナーを持つ Gateway を追加し、web Pod までつなぎます。
- web から coredns へ破線の DNS エッジを、フレーム間に太い双方向の VXLAN エッジを描きます。
- 基本パックから、既定値を書いた Markdown ブロックと、短いテキストのメモを追加します。
Kubernetes のユースケースページには、さらに多くのオブジェクトが載っています。この例で使うダイアグラムノードは 11 個で、Free プランの範囲に収まります。
よくある質問
Kubernetes でノードをまたいで Pod はどう通信しますか? CNI プラグインを通じて行います。オーバーレイが Pod のパケットをノード IP 間で包むか、ネイティブルーティングが Pod のアドレスをそのまま運びます。どちらの場合も、Pod は互いの実際の IP を見ます。
Pod IP と ClusterIP の違いは何ですか? Pod IP は 1 つの Pod に属し、Pod が置き換わると変わります。ClusterIP は Service の安定した仮想 IP で、kube-proxy または eBPF による代替が、現在の Pod IP に書き換えます。
どの kube-proxy モードを使うべきですか? Kubernetes 1.37 では iptables が既定で、nftables は 1.33 から GA であり今後の既定になる予定で、IPVS は非推奨です。カーネル 5.13 以降であれば、長期的には nftables が適切な選択です。
NetworkPolicy はすべての CNI プラグインで動きますか? いいえ。API はどこでも受け付けますが、実際に適用するのは NetworkPolicy を実装したプラグインだけです。Calico、Cilium、ネットワークポリシーを有効にした AWS VPC CNI は適用します。Flannel 単体では適用しません。
Ingress と Gateway API のどちらを使うべきですか? 新しく作るなら Gateway API です。Kubernetes が推奨しており、Ingress は凍結されています。既存の Ingress リソースは動き続けますが、セキュリティ修正を受け取れなくなった Ingress NGINX からの移行を計画してください。