🕸️
概念 #Kubernetes #TCP/IP #CNI #Service #Ingress #NetworkPolicy #ServiceMesh 📚 TCPIPネットワーク

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通信方式パフォーマンスNetworkPolicyL7ポリシー難易度
FlannelVXLAN✗(別途Calico必要)
CalicoBGP / IPIP
CiliumeBPF◎◎
WeaveVXLAN

レイヤー④: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