🕸️
Kubernetesネットワーク全体図
Pod内通信からCNI・Service・Ingress・NetworkPolicy・Service Meshまで。各レイヤーの懸念点・ソリューション・トレードオフを図解。
K8sネットワークの全体マップ
外部クライアント
│
▼
[ DNS ] example.com → LBのIP
│
▼
[ クラウドLB / NodePort ] ← L4。外部トラフィックの入口
│
▼
[ Ingress Controller ] ← L7。ホスト名・パスでルーティング
│ (nginx, Envoy, Traefik等)
▼
[ Service (ClusterIP) ] ← 仮想IP + ロードバランシング
│ kube-proxy / eBPFが実装
▼
[ Pod ]
├─ [Pause Container] ← Network Namespaceを保持
├─ [App Container A] ← Pause のNSを共有
└─ [Sidecar(Service Mesh)] ← Podに注入されるプロキシ
────────────────────────────────────────────
横断的な関心事:
[ CoreDNS ] サービスディスカバリー
[ CNI Plugin ] Pod間のルーティング(Node間)
[ NetworkPolicy ] Pod間の通信制御(ファイアウォール)
レイヤー①:Pod内通信
仕組み
Pod
┌─────────────────────────────────────────────────┐
│ │
│ [Pause Container] ← Network Namespaceを保持 │
│ │ │
│ └── Network Namespace ←──── eth0 │
│ (10.244.0.2) │
│ [App Container] ─┐ │
│ [Sidecar] ─┴─── 同じNSを共有 │
│ │
└─────────────────────────────────────────────────┘
Pod内のコンテナ間通信:
localhost:ポート番号 で直接通信(NATなし・遅延なし)
→ ポート番号の衝突に注意(同一Pod内で同じポートは使えない)
Pause Container(インフラコンテナ)の役割
Podのライフサイクル中、Network Namespaceを保持し続ける器。AppコンテナやSidecarが再起動してもIPアドレスが変わらないのはこのため。
懸念点とトレードオフ
| 懸念 | 内容 | 対処 |
|---|---|---|
| ポート衝突 | 同一Pod内でポートが重複できない | コンテナごとにポートを設計段階で割り当て |
| Sidecarオーバーヘッド | ServiceMesh注入でメモリ+数十MB増加 | 不要なNamespaceではSidecar注入を無効化 |
レイヤー②:同一Node内のPod間通信
Node (192.168.1.10)
┌─────────────────────────────────────────────────────┐
│ │
│ Pod A (10.244.0.2) Pod B (10.244.0.3) │
│ ┌────────────┐ ┌────────────┐ │
│ │ eth0 │ │ eth0 │ │
│ └─────┬──────┘ └──────┬─────┘ │
│ │ veth_A │ veth_B │
│ └──────────┬──────────────┘ │
│ [ cbr0 / cni0 ] │
│ bridge: 10.244.0.1/24 │
│ │ │
│ [ eth0 ] ← 物理NIC │
└─────────────────────────────────────────────────────┘
A → B の経路:
Pod A の eth0 → veth_A → cbr0 bridge → veth_B → Pod B の eth0
(同一サブネットなのでルーティング不要、ブリッジ転送)
レイヤー③:Node間のPod通信とCNI
異なるNodeにあるPodが通信するには、物理ネットワークを越える仕組みが必要。これをCNI(Container Network Interface)プラグインが担う。
CNIプラグインの比較
Node A (192.168.1.10) Node B (192.168.1.20)
Pod A: 10.244.0.2 Pod B: 10.244.1.2
方式①:VXLAN(Flannel デフォルト、Calico VXLAN mode)
Pod A → cbr0 → VTEP(flannel.1)
[元パケット: 10.244.0.2 → 10.244.1.2]
↓ UDPで包む(カプセル化)
[外パケット: 192.168.1.10:xxxxx → 192.168.1.20:4789]
→ 物理ネットワーク → Node B
→ VTEP がデカプセル → cbr0 → Pod B
利点:物理ネットワークの設定不要
欠点:カプセル化のオーバーヘッド(CPUコスト・MTU縮小問題)
方式②:BGP(Calico デフォルト)
各NodeがBGPルーターとして動作し、PodのCIDRをネイバーに広告
Node A: 「10.244.0.0/24 は俺経由で到達可能」
Node B: 「10.244.1.0/24 は俺経由で到達可能」
Pod A → ルーティングテーブルで直接 Node B へ送信(カプセル化なし)
利点:オーバーヘッドが最小、高パフォーマンス
欠点:物理ネットワークがBGPをサポートしている必要がある
クラウド環境ではノード間のルーティングがBGPに対応していないことも
→ Calico が IPIP / VXLAN にフォールバックする設定が必要
方式③:eBPF(Cilium)
iptables をカーネルバイパス。XDP(eXpress Data Path)で
NICドライバレベルでパケットを処理
利点:最高パフォーマンス、豊富なObservability(Hubble)
L7 NetworkPolicy(HTTPパス単位でフィルタ)が可能
欠点:カーネル 4.9+ 必須、複雑な設定、デバッグが難しい
CNI比較まとめ
| CNI | 通信方式 | パフォーマンス | NetworkPolicy | L7ポリシー | 難易度 |
|---|---|---|---|---|---|
| Flannel | VXLAN | △ | ✗(別途Calico必要) | ✗ | 低 |
| Calico | BGP / IPIP | ◎ | ✓ | ✗ | 中 |
| Cilium | eBPF | ◎◎ | ✓ | ✓ | 高 |
| Weave | VXLAN | △ | ✓ | ✗ | 低 |
レイヤー④:Service
PodのIPは再起動のたびに変わる。ServiceはPodへの安定したエンドポイントを提供する。
Serviceの種類
【ClusterIP】(デフォルト)
仮想IP(VIP)をクラスター内に公開。外部からは到達不可。
Service IP: 10.96.0.100:80
↓ kube-proxyがiptables/IPVSルールで転送
Pod A: 10.244.0.2:8080
Pod B: 10.244.0.3:8080
Pod C: 10.244.1.4:8080
【NodePort】
全NodeのIPで特定ポート(30000-32767)を開放。
<NodeIP>:30080 → ClusterIP → Pod群
【LoadBalancer】
クラウドのLBを自動プロビジョニングして外部IPを割り当て。
NodePortの上位互換。
<External IP>:80 → NodePort → ClusterIP → Pod群
【ExternalName】
Service名をCNAMEとして外部DNSにマッピング。
クラスター外のDBやAPIをK8s DNSで名前解決できるようにする。
my-db.default.svc.cluster.local → CNAME → rds.amazonaws.com
【Headless Service】
ClusterIPを持たない(clusterIP: None)。
DNS解決すると各PodのIPが直接返る。
StatefulSet(DBクラスター等)での直接Pod接続に使う。
kube-proxy の実装と問題点
【iptables モード】(デフォルト)
Service IP 10.96.0.100:80 へのパケット
→ KUBE-SERVICES チェーン
→ KUBE-SVC-XXXX チェーン(確率的にPodを選択)
→ DNAT: 10.244.0.2:8080 に書き換え
問題:
Serviceが1000個、Podが10000個になると
iptablesルールが数万行 → ルール追加/削除がO(n)で遅い
更新のたびに全ルールをロックして書き換える → 遅延スパイク
【IPVS モード】
Linux カーネルの IPVS(IP Virtual Server)を使用。
ハッシュテーブルで O(1) のルックアップ。
LBアルゴリズムも豊富(rr, lc, dh, sh など)。
設定:kube-proxy --proxy-mode=ipvs
【eBPFモード(Cilium kube-proxy replacement)】
iptablesもIPVSも使わずeBPFマップでO(1)転送。
kube-proxyを完全に置き換え可能。最高パフォーマンス。
セッションアフィニティ(スティッキーセッション)
apiVersion: v1
kind: Service
spec:
sessionAffinity: ClientIP # クライアントIPで同じPodに固定
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3時間
注意:
水平スケール時に特定Podに負荷が偏る可能性
Podが削除されるとセッションが切れる
推奨:アプリをステートレスに設計してセッションアフィニティを使わない
レイヤー⑤:CoreDNS(サービスディスカバリー)
DNS解決の仕組み
Pod内の /etc/resolv.conf(K8sが自動設定):
nameserver 10.96.0.10 ← CoreDNSのClusterIP
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
「my-service」への接続 → DNS解決の流れ:
1. my-service(ndots:5なのでFQDNとみなさない)
2. my-service.default.svc.cluster.local を試みる → ヒット → 返却
FQDNの形式:
<service>.<namespace>.svc.cluster.local
<pod-ip-dashed>.<namespace>.pod.cluster.local
Headless Serviceの場合:
my-db-0.my-db.default.svc.cluster.local → Pod直接のIP(SRVレコードも返る)
ndots:5 問題(パフォーマンス上の罠)
問題:
外部ホスト「api.example.com」への接続時に
1. api.example.com.default.svc.cluster.local → NXDOMAIN
2. api.example.com.svc.cluster.local → NXDOMAIN
3. api.example.com.cluster.local → NXDOMAIN
4. api.example.com. → ヒット!(4回無駄なクエリが走る)
外部ドメインへのDNSクエリがndots:5分だけ増える
高トラフィック環境でCoreDNSがボトルネックになることがある
対策①:FQDNで指定(末尾に.)
api.example.com. → 直接外部DNSへ(search listを使わない)
対策②:ndotsを下げる(Pod spec)
spec.dnsConfig.options:
- name: ndots
value: "1"
対策③:CoreDNSのNodeLocalDNSCache
各NodeにDNSキャッシュを展開してCoreDNSへの集中を緩和
レイヤー⑥:Ingress
外部HTTPトラフィックをServiceに振り分けるL7ルーター。
Ingressリソースとコントローラーの分離
Ingress リソース(宣言) Ingress Controller(実装)
┌────────────────────────┐ ┌────────────────────────────┐
│ kind: Ingress │──────→│ nginx / Traefik / Envoy │
│ rules: │ │ ※ 別途インストールが必要 │
│ - host: api.example │ │ │
│ http: │ │ Kubernetes APIをwatchして │
│ paths: │ │ Ingressリソースを検知 │
│ /api → api-svc │ │ 自分の設定を動的に更新 │
│ / → web-svc │ └────────────────────────────┘
└────────────────────────┘
K8sはIngressの「仕様」だけを定義している。
実装はコントローラー次第(→ベンダーロックインのリスクもある)
IngressとGateway APIの比較
【Ingress(旧来)】
シンプルだが表現力が低い(L7ルーティングが基本のみ)
ベンダー固有のannotationsで拡張 → 移植性が低い
例:nginx特有の設定
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
【Gateway API(新世代・推奨)】
GatewayClass / Gateway / HTTPRoute / TCPRoute の4層構造
標準化された表現力(ヘッダーリライト、重み付きルーティング)
Ingressより移植性が高い
HTTPRoute で % ベースのトラフィック分割(カナリアリリース):
- backendRef: service-v1 weight: 90
- backendRef: service-v2 weight: 10
TLS終端とcert-manager
cert-manager を使ったTLS自動管理:
Ingressリソースにアノテーションを付けるだけで
Let's Encrypt から証明書を自動取得・更新
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
tls:
- hosts: [api.example.com]
secretName: api-tls
懸念:
cert-managerが単一障害点になる
→ HPA・複数レプリカ・Podのヘルスチェック設定が必須
Let's Encrypt のレート制限(週50証明書)に注意
レイヤー⑦:NetworkPolicy
K8sのデフォルトは「全Pod間通信が許可」。NetworkPolicyでファイアウォールルールを定義する。
【デフォルト(NetworkPolicyなし)】
Pod A → Pod B(別Namespace)✓
Pod A → Pod C(別Namespace)✓
DBへの直接接続 ✓(危険!)
【NetworkPolicy適用後】
DB Pod へのingressを「app: backend ラベルを持つPodのみ」に制限
NetworkPolicyの書き方
# DBへのアクセスをbackendのみに制限
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: production
spec:
podSelector:
matchLabels:
role: db # このPolicyが適用されるPod
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production # 同じnamespaceのみ
podSelector:
matchLabels:
app: backend # backendラベルのPodのみ
ports:
- port: 5432
NetworkPolicyの落とし穴
落とし穴①:podSelectorとnamespaceSelector の AND/OR
from:
- namespaceSelector: ... # OR条件(どちらかでOK)
- podSelector: ...
from:
- namespaceSelector: ... # AND条件(両方一致が必要)
podSelector: ...
→ ハイフンの位置でAND/ORが変わる!バグの温床。
落とし穴②:NetworkPolicyを実装するのはCNI
Flannel はNetworkPolicyを実装していない
→ FlannelにCalicoを重ねる「Canal」構成が必要
→ Cilium / Calico は標準対応
落とし穴③:デフォルト拒否ポリシーを入れないと意味が薄い
許可ルールを追加するだけではデフォルト許可のまま
Namespaceごとに「全拒否」ポリシーを先に適用してから許可を積む
# デフォルト全拒否(これを先に入れる)
kind: NetworkPolicy
spec:
podSelector: {} # 全Pod
policyTypes: [Ingress, Egress]
# ingress/egress の rules を書かない → 全拒否
レイヤー⑧:Service Mesh
サービス間通信の横断的関心事(セキュリティ・観測・制御)をアプリから分離する。
サイドカーパターン
Pod
┌─────────────────────────────────────────────────┐
│ [App Container] │
│ ↑↓ localhost:ポート番号 │
│ [Sidecar Proxy(Envoy等)] │
│ ↑↓ │
│ eth0 ← iptablesでトラフィックをSidecarにリダイレクト│
└─────────────────────────────────────────────────┘
Service A → Sidecar A ─[mTLS]─ Sidecar B → Service B
(インターセプト) (インターセプト)
主要な機能
| 機能 | 説明 |
|---|---|
| mTLS | サービス間通信を自動的に暗号化・認証(証明書はControlPlaneが自動管理) |
| Observability | 全サービス間のメトリクス・トレース・ログを追加コードなしで収集 |
| Retry / Timeout | サービスごとに再試行・タイムアウトをProxyレベルで設定 |
| Circuit Breaker | 障害サービスへのリクエストを一時遮断(連鎖障害防止) |
| Traffic Shifting | 重み付きルーティング(カナリア・Blue/Green) |
| Rate Limiting | サービス単位でのリクエスト制限 |
Istio vs Linkerd vs Cilium ServiceMesh
【Istio】
Control Plane: Istiod(Pilot + Citadel + Galley の統合)
Data Plane: Envoy Proxy(機能が非常に豊富)
利点:機能が最も豊富・広いエコシステム
欠点:複雑・サイドカーのメモリ消費が大(50〜100MB/Pod)
学習コストが高い
【Linkerd】
Control Plane: linkerd-controller
Data Plane: linkerd-proxy(Rustで実装)
利点:シンプル・軽量(サイドカー10MB程度)・Istioより簡単
欠点:Istioより機能が少ない(L7ポリシーが限定的)
【Cilium ServiceMesh(Sidecarless)】
eBPFでサイドカーなしにServiceMesh機能を実現(新世代)
利点:サイドカーのオーバーヘッドがゼロ・高パフォーマンス
欠点:カーネル 5.10+ 必須・新しく実績が少ない
トレードオフ整理
サービスメッシュを入れるべきか?
入れる理由:
✓ マイクロサービスが5つ以上ある
✓ サービス間のmTLSが必要(コンプライアンス)
✓ 統一された可観測性が欲しい
✓ カナリアリリースを簡単にやりたい
入れない理由:
✗ サービス数が少ない(3つ以下)
✗ Podのメモリが逼迫している
✗ チームにServiceMeshの運用知識がない
✗ レイテンシが極めてシビア(Sidecarで数ミリ秒の遅延追加)
全体の懸念点と対処のまとめ
┌────────────────────────┬──────────────────────────┬────────────────────────────┐
│ 懸念 │ 原因 │ ソリューション │
├────────────────────────┼──────────────────────────┼────────────────────────────┤
│ PodのIPが変わる │ Pod再起動でIP再割り当て │ Serviceで抽象化 │
│ Serviceが重い(規模) │ iptablesのO(n)更新 │ IPVS mode / Cilium eBPF │
│ DNS遅延 │ ndots:5 + 外部ドメイン │ FQDN使用 / NodeLocalDNS │
│ Node間オーバーヘッド │ VXLANカプセル化 │ BGP(Calico)/ eBPF │
│ Pod間通信が野放し │ デフォルト全許可 │ NetworkPolicy (Deny All先入れ)|
│ 証明書管理が大変 │ サービス増加で証明書増加 │ cert-manager / ServiceMesh│
│ L7可観測性がない │ アプリにコードを書く必要 │ ServiceMesh(Istio等) │
│ Ingressが移植性低い │ ベンダー固有annotation │ Gateway API への移行 │
│ ServiceMeshが重い │ Sidecarのメモリ消費 │ Cilium Sidecarless │
└────────────────────────┴──────────────────────────┴────────────────────────────┘
デバッグコマンド早見表
# Service のエンドポイント(Podに届いているか)
kubectl get endpoints <service-name>
kubectl describe service <service-name>
# DNS解決のデバッグ
kubectl run -it --rm debug --image=busybox --restart=Never -- sh
nslookup my-service.default.svc.cluster.local
nslookup api.example.com
# NetworkPolicy が効いているか(疎通テスト)
kubectl exec -it pod-a -- curl http://pod-b-ip:8080
kubectl exec -it pod-a -- nc -zv pod-b-ip 5432
# iptables でService転送ルールを確認
iptables -t nat -L KUBE-SERVICES -n --line-numbers
iptables -t nat -L KUBE-SVC-<hash> -n
# Cilium の接続性デバッグ
cilium connectivity test
cilium endpoint list
# CoreDNS のログ
kubectl logs -n kube-system -l k8s-app=kube-dns
# Pod のネットワーク経路確認
kubectl exec -it <pod> -- ip route
kubectl exec -it <pod> -- ss -tn - 1. 📡TCP/IPパケット通信の全過程
- 2. 🔌物理層・データリンク層詳解
- 3. 🌐ネットワーク層(IP)詳解
- 4. 🔄TCPの全機能詳解
- 5. ⚡UDP詳解とユースケース
- 6. 🌍アプリケーション層詳解(DNS・HTTP・TLS)
- 7. 🔧ソケットAPIとI/Oモデル詳解
- 8. ☁️コンテナ・クラウドネットワーキング詳解
- 9. ⚖️ロードバランサー・リバースプロキシ詳解
- 10. 🕸️Kubernetesネットワーク全体図