本記事には広告(アフィリエイトリンク)が含まれます。

NetworkPolicy・Cilium比較【CKA第10回】

広告

新卒インフラエンジニア向け Kubernetes 実践教科書(第2巻 CKA 編)の第10回です。動作確認バージョン: AlmaLinux 10.1 / K8s v1.35.5 / Calico v3.32.0 / kubeadm v1.35.5(2026-05-24 時点)

広告

今ここマップ(第10回 / 全16回 / 第3部)

今ここ: 第10回 / 全16回(第3部:ネットワーク)
▓▓▓▓▓▓▓▓▓▓░░░░░░  63%

第1部(クラスタ構築):       ■■■■■ 5/5 回(完了)
第2部(ワークロード管理):   ■■■   3/3 回(完了)
第3部(ネットワーク):       ■■□   2/3 回 ← 今ここ
第4部(ストレージ):         □     0/1 回
第5部(監視・運用):         □□    0/2 回
第6部(トラブルシュート):   □□    0/2 回

第9回では MetalLB を導入し、LoadBalancer type Service に外部 IP が払い出されることを実機で確認しました。第10回からはネットワークポリシー制御に踏み込みます。CKA D3(Services and Networking・20%)の中核テーマです。

第10回のキャッチコピー: 「default-deny で始め、必要な通信だけを開ける」

第10回終了時の達成状態:

  • NetworkPolicy の podSelector / namespaceSelector / ipBlock を組み合わせてポリシーを記述できる
  • policyTypes の明示が必要な理由と、省略した場合の挙動差異を説明できる
  • default-deny-all から段階的に許可ルールを追加する手順を実機で実施できた
  • CoreDNS への Egress を UDP 53 + TCP 53 の両方で許可しなければならない理由を説明できる
  • NetworkPolicy 起因の名前解決失敗を「症状確認 → IP 直アクセスで切り分け → ポリシー確認 → 修正」の手順で診断できる
  • Calico(L4 ポリシー)と Cilium(L7 ポリシー・Hubble・Pod 間 mTLS)の主要な差分を説明できる

第10回のスコープと設計 — np-demo Namespace で安全に演習する

本セクションでは、第10回で扱うことと扱わないことを明確にし、演習 Namespace の設計判断を説明します。

第10回で「やること」と「やらないこと」

やることやらないこと
NetworkPolicy spec 構造(全フィールド解説)Cilium 実機導入(第3巻第6回以降)
default-deny 4 パターン(deny-ingress / allow-ingress / deny-egress / deny-all)BGP ネットワークポリシー
段階許可演習(np-demo Namespace)GlobalNetworkPolicy(Calico 固有 API)
CoreDNS Egress 遮断ミニトラブルシュートCilium L7 Policy の実機設定
Calico vs Cilium 比較表(概念理解)WireGuard 暗号化の設定

演習 Namespace の設計

Namespace役割削除タイミング
np-demo段階許可演習(nginx Deployment + nginx-svc Service)演習①完了後に削除
ts-demoCoreDNS Egress 遮断トラブルシュート演習演習②完了後に削除
fanclub(既存)fanclub-api 稼働中・変更なし変更なし

設計判断の背景

判断① 演習 Namespace は np-demo・終了時削除

NetworkPolicy 演習は np-demo Namespace を新規作成して行います。演習終了後に kubectl delete namespace np-demo で削除し、NetworkPolicy・Pod・Service をまとめてクリーンアップします。fanclub-api(fanclub Namespace)は稼働中であり、誤った NetworkPolicy を適用しても影響がゼロになります。

判断② Cilium は比較表のみ・実機演習なし

Cilium の実機インストールは行いません。現クラスタの CNI は Calico(第2回で導入済み)であり、Cilium に切り替えると既存 fanclub-api の通信設定が変わり、第11回以降の演習に影響します。CKA 試験では「使用中の CNI の NetworkPolicy 操作」が出題されるため、Calico による実機演習を優先します。Cilium の実機導入は第3巻(CKS 編)第6回以降で扱います。

判断③ ミニトラブルシュートは CoreDNS Egress 遮断の 1 シナリオ集中型

「default-deny-all 適用後に kube-dns(CoreDNS)への Egress が遮断され、全 Pod の名前解決が失敗するパターン」に絞ります。1 シナリオを掘り下げることで「原因特定 → 修正 → 確認」の診断ループを完結体験できます。複数シナリオは第16回(総合トラブルシュート回)に集約します。

判断④ default-deny-all から始める段階許可フロー

最も厳格な default-deny-all から始め、段階的に許可ルールを追加していく設計を採用します。CKA 試験でもこの「まず全遮断、次に必要な通信だけ許可」というアプローチが試験問題の前提となっています。

NetworkPolicy の spec 構造を理解する

NetworkPolicy は Kubernetes のネイティブリソースです。CNI プラグイン(本シリーズでは Calico)が NetworkPolicy の内容を解釈し、各ノードの iptables / eBPF ルールに変換して通信を制御します。

NetworkPolicy の基本構造

以下は全フィールドを含む NetworkPolicy の完全な YAML 例です。各フィールドの役割を把握してください。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sample-policy
  namespace: np-demo
spec:
  podSelector:           # 対象 Pod(空 = Namespace 全体)
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: prod
          podSelector:             # AND 結合(同一ブロック内)
            matchLabels:
              role: client
      ports:
        - protocol: TCP
          port: 80
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.0.1.0/24
      ports:
        - protocol: TCP
          port: 443

podSelector・namespaceSelector・ipBlock の使い分け

from / to の中に指定できるセレクタは 3 種類です。

セレクタ選択対象典型的なユースケース
podSelector同一 Namespace の Podアプリ間通信制御(frontend → backend)
namespaceSelector指定ラベルを持つ Namespace 全体の PodNamespace 間通信の許可(prod → monitoring)
namespaceSelector + podSelector(AND)指定 Namespace 内の指定 Pod のみ厳格な跨 Namespace 制御
ipBlock外部 CIDR(Kubernetes ノード外)外部 API・データベースへの Egress

AND / OR 結合ルール — インデントで決まる最頻出ミス

namespaceSelectorpodSelector を組み合わせる際、インデントの位置によって AND(積集合)か OR(和集合)かが変わります。これは CKA 試験でも現場でも頻出のミスポイントです。

同一ブロック内に並べる → AND(積集合)

from:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: prod
    podSelector:
      matchLabels:
        app: frontend

上記は「prod Namespace の中の app=frontend Pod」のみ許可します。namespaceSelectorpodSelector が同一の - エントリ(同一インデントブロック)に属しているため AND 評価になります。

別ブロックに分ける → OR(和集合)

from:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: prod
  - podSelector:
      matchLabels:
        app: frontend

上記は「prod Namespace の任意の Pod」OR「同 Namespace の app=frontend Pod」のいずれかを許可します。各 - エントリが独立したブロックになっているため OR 評価になります。

インデントの - 位置が 1 段ずれるだけでセキュリティポリシーが反転します。YAML を書く際は「このセレクタは同じ - エントリか、別の - エントリか」を必ず確認してください。

policyTypes の明示が重要な理由

NetworkPolicy 双方向要件 — Egress 許可と Ingress 許可の両方が必要
NetworkPolicy 双方向要件 — Egress 許可と Ingress 許可の両方が必要

policyTypes を省略すると、次の挙動になります。

  • Ingress: 常にポリシーが適用される(ingress フィールドの有無に関わらず)
  • Egress: egress フィールドが存在する場合のみ適用される

この非対称な挙動が原因で「Egress を制御したつもりが制御されていない」という状況が発生します。policyTypes は必ず明示する習慣をつけてください。

また、Pod が複数の NetworkPolicy に選択された場合、すべてのポリシーの和集合(OR)が適用されます。1 つのポリシーで通信を許可すれば、他のポリシーで遮断していても通信は通ります。

default-deny パターン 4 種類

NetworkPolicy の「default-deny」パターンには状況に応じた 4 種類があります。用途と特性を把握して使い分けてください。

パターンpolicyTypesingress / egress ルール適用場面
① deny-ingress["Ingress"]ingress フィールドなし外部からの流入を防ぎつつ、外への通信は自由に許容する
② allow-ingress["Ingress"]ingress: [{}](全許可エントリ)明示的に全 Ingress を許可する(ポリシー適用対象にしつつ全通し)
③ deny-egress["Egress"]egress フィールドなし外部への流出を防ぐ(データ漏洩防止・スキャン遮断)
④ deny-all["Ingress","Egress"]両フィールドなし最も厳格・本番 Namespace の初期設定に採用

各パターンの完全な YAML マニフェストを以下に示します。

パターン①: default-deny-ingress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress

パターン②: default-allow-ingress(明示的全許可)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-allow-ingress
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - {}

パターン③: default-deny-egress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Egress

パターン④: default-deny-all(最も厳格)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

本番 Namespace の NetworkPolicy 設計では「パターン④ deny-all を最初に適用し、その後に必要な通信だけを追加許可する」というホワイトリスト方式が基本です。

Calico v3.32 と Cilium を比較する

第2回で導入した Calico と、第3巻(CKS 編)で導入予定の Cilium について、主要な差分を整理します。どちらを選ぶかは運用要件によって判断します。

Calico のアーキテクチャと特徴

Calico v3.32.0 はマルチデータプレーン構成を採用しています。

  • データプレーン: iptables(デフォルト)/ eBPF(オプション・kernel 5.8+ 推奨)/ VPP の 3 択
  • Kubernetes NetworkPolicy 準拠: 標準 NetworkPolicy API を完全サポート
  • Calico 固有ポリシー: GlobalNetworkPolicy(クラスタ全体・order 指定可能)/ NetworkPolicy(Namespace スコープ)
  • WireGuard 暗号化: ノード間のトラフィックを暗号化可能
  • インストール: kubectl apply -f 一発で完了。kubeadm + CKA 試験向けに最適
  • eBPF モード: v3.32 では LRU ハッシュマップ採用でコネクショントラッキングエントリの自動蒸発に対応

主要コンポーネントは以下のとおりです(CKA D3 必須知識)。

  • Felix: 各ノードに常駐する policy engine。NetworkPolicy / Service ルートを iptables(または eBPF / VPP)に反映する
  • BIRD: BGP daemon。ノード間で Pod CIDR の経路情報を交換し、オーバーレイなしで Pod-to-Pod 通信を成立させる
  • typha: kube-apiserver と Felix の間に立つ datastore proxy。大規模クラスタで apiserver への watch 接続を集約し負荷を緩和する
  • calico-node DaemonSet: 全ノードで Felix + BIRD を 1 Pod として起動する実体
  • calico-kube-controllers Deployment: Namespace / NetworkPolicy / Profile などのリソースを kube-apiserver と Calico datastore の間で同期する

Calico vs Cilium 比較表

比較軸Calico v3.32Cilium v1.17+
アーキテクチャマルチデータプレーン(iptables デフォルト / eBPF オプション)eBPF ネイティブ(カーネルレベル一本)
L4 NetworkPolicy標準サポート標準サポート
L7 Policy(HTTP / gRPC / Kafka)非対応対応(method / path / gRPC call でフィルタ可能)
可観測性syslog ベースフローログHubble(eBPF ベース・リアルタイム L3-L7 フロー可視化)
mTLS / ワークロード認証WireGuard(ノード間)SPIFFE アイデンティティ + IPSec / WireGuard(Pod 間・サイドカーレス)
パフォーマンス(pod-to-svc スループット)iptables モードはやや劣る・eBPF モードで差縮小(公開ベンチ要再確認)同条件で Calico iptables より高速(同上)
ポリシースケーラビリティiptables モードは線形増加(1,000 ポリシー以上で顕著)eBPF マップは対数的スケール(1,000+ ポリシーで差顕著)
インストール難易度シンプル(kubectl apply 一発)やや複雑(Helm + CiliumConfig)
本シリーズでの扱い第2巻で実機演習(本回)第3巻第6回から実機導入(CKS 必須)

第3巻への橋渡し

Cilium を第3巻で導入する主な理由は、CKS(Certified Kubernetes Security Specialist)の試験ドメインと直結しているためです。CKS では mTLS による Pod 間通信の暗号化と L7 レベルのポリシー制御が求められます。Cilium の SPIFFE アイデンティティによるサイドカーレス mTLS と、Hubble による L7 可視化はこの要件を満たす中核技術です。本回では Calico を使った L4 NetworkPolicy 操作を習得し、第3巻で Cilium に切り替えた際に「何が変わったのか」を対比できる土台を作ります。

やってみよう①:default-deny から段階許可でポリシーを育てる

NetworkPolicy の「非分離 → 全遮断 → 許可追加」の流れを実機で体験します。演習 Namespace は np-demo、演習終了後に削除します。

Step 1: np-demo 環境構築

まず Namespace を作成し、nginx Deployment と ClusterIP Service を用意します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: np-demo
EOF

実行結果:

namespace/np-demo created

nginx Deployment(3 Pod)を作成します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: np-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: 192.168.1.123:5000/nginx:1.27
          ports:
            - containerPort: 80
EOF

実行結果:

deployment.apps/nginx-deploy created

ClusterIP Service を作成します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: np-demo
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
EOF

実行結果:

service/nginx-svc created

Pod が Running になったことを確認します。

実行コマンド:

$ kubectl get pods -n np-demo

実行結果:

NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-7d5d5c6b9f-2xkqp   1/1     Running   0          20s
nginx-deploy-7d5d5c6b9f-8htzm   1/1     Running   0          20s
nginx-deploy-7d5d5c6b9f-vfn9r   1/1     Running   0          20s

NetworkPolicy を一切作成していない状態(非分離状態)では、busybox から nginx-svc への通信が通ります。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n np-demo --rm -it --restart=Never -- wget -O- http://nginx-svc --timeout=3

実行結果:

Connecting to nginx-svc (10.107.x.x:80)
writing to stdout
<!DOCTYPE html>
...
-                    100% |********************|   615  0:00:00 ETA
written to stdout
pod "busybox" deleted

Step 2: default-deny-all 適用(全通信遮断)

default-deny-all ポリシーを適用して全通信を遮断します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
EOF

実行結果:

networkpolicy.networking.k8s.io/default-deny-all created

busybox から接続を試みます。タイムアウトで遮断を確認します。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n np-demo --rm -it --restart=Never -- wget -O- http://nginx-svc --timeout=3

実行結果:

wget: bad address 'nginx-svc'
pod "busybox" deleted

DNS(Egress UDP/TCP 53)も遮断されたため、Service 名の名前解決自体が失敗しています。これが CoreDNS Egress 遮断の典型的な症状です。

Step 3: 同一 Namespace 内 Ingress を許可

np-demo Namespace 内の Pod からの Ingress を許可するポリシーを追加します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-from-np-demo
  namespace: np-demo
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector: {}
      ports:
        - protocol: TCP
          port: 80
EOF

実行結果:

networkpolicy.networking.k8s.io/allow-ingress-from-np-demo created

ここで重要なポイントがあります。default-deny-all は Egress も遮断しているため、テストクライアントである busybox 自身の送信(Egress)もまだ許可されていません。DNS 解決ができず Service 名は使えないので IP を直接指定して試しますが、それでも通信は成立しないことを確認します。

実行コマンド:

$ NGINX_IP=$(kubectl get svc nginx-svc -n np-demo -o jsonpath='{.spec.clusterIP}')
$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n np-demo --rm -it --restart=Never -- wget -O- http://${NGINX_IP}:80 --timeout=3

実行結果(Ingress を許可しても、クライアントの Egress が遮断されているため通らない):

Connecting to 10.107.x.x:80
wget: download timed out
pod "busybox" deleted

nginx 側の Ingress は許可しましたが、busybox 側の Egress が default-deny-all で遮断されたままのため、IP を直接指定しても download timed out になります。これが NetworkPolicy の重要な性質です。通信が成立するには「送信元 Pod の Egress 許可」と「宛先 Pod の Ingress 許可」の両方が揃う必要があります。次の Step 5 でクライアント側の Egress を許可し、通信を成立させます。

Step 4: 特定 Namespace からの Ingress 許可(AND 結合の実例)

monitoring Namespace の app=prometheus Pod からの Ingress だけを追加で許可します。AND 結合(namespaceSelector + podSelector 同一ブロック)の実例です。

まず monitoring Namespace を作成し、Kubernetes が自動付与するメタデータラベルを確認します。

実行コマンド:

$ kubectl create namespace monitoring

実行結果:

namespace/monitoring created

実行コマンド:

$ kubectl get namespace monitoring --show-labels

実行結果:

NAME         STATUS   AGE   LABELS
monitoring   Active   5s    kubernetes.io/metadata.name=monitoring

Kubernetes v1.21 で beta 導入、v1.22 で GA。全 Namespace に kubernetes.io/metadata.name=<Namespace 名> ラベルが自動付与されます。NetworkPolicy の namespaceSelector でこのラベルを使うと、ラベルの手動付与が不要になります。

monitoring Namespace の prometheus Pod からの Ingress を AND 結合で許可します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-from-prometheus
  namespace: np-demo
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring
          podSelector:
            matchLabels:
              app: prometheus
      ports:
        - protocol: TCP
          port: 80
EOF

実行結果:

networkpolicy.networking.k8s.io/allow-ingress-from-prometheus created

現在の NetworkPolicy 一覧を確認します。

実行コマンド:

$ kubectl get networkpolicy -n np-demo

実行結果:

NAME                           POD-SELECTOR   AGE
allow-ingress-from-np-demo     app=nginx      2m
allow-ingress-from-prometheus  app=nginx      10s
default-deny-all               <none>         3m

Step 5: Egress 許可(DNS + HTTP)

np-demo の全 Pod から、CoreDNS(名前解決)・外部 HTTP/HTTPS・同一 Namespace 内の nginx(TCP 80)へ出る Egress を許可するポリシーを追加します。ここでは podSelector: {} として、テストクライアントである busybox を含む Namespace 内の全 Pod を対象にします(Step 3 で確認したとおり、クライアントの Egress が許可されないと通信は成立しません)。DNS は UDP 53 と TCP 53 の両方を許可します。3 つ目のルールは、クラスタ内の nginx(Pod IP は 10.244.0.0/16 で、2 つ目のルールの except: 10.0.0.0/8 に除外される)へ到達するために必要です。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-egress-http
  namespace: np-demo
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 10.0.0.0/8
      ports:
        - protocol: TCP
          port: 80
        - protocol: TCP
          port: 443
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: np-demo
      ports:
        - protocol: TCP
          port: 80
EOF

実行結果:

networkpolicy.networking.k8s.io/allow-egress-http created

Egress が許可されたため、busybox から Service 名で接続できるようになります。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n np-demo --rm -it --restart=Never -- wget -O- http://nginx-svc --timeout=5

実行結果:

Connecting to nginx-svc (10.107.x.x:80)
writing to stdout
<!DOCTYPE html>
...
written to stdout
pod "busybox" deleted

Service 名(nginx-svc)で接続できました。クライアント側の Egress 許可(DNS の UDP 53 + TCP 53、および同一 Namespace 内 TCP 80)と、nginx 側の Ingress 許可が揃ったことで、名前解決 → 通信が成立しています。Step 3 で確認した「送信元の Egress と宛先の Ingress の両方が必要」という性質のとおりです。

Step 6: クリーンアップ

np-demo Namespace を削除し、演習環境をクリーンアップします。monitoring Namespace も不要であれば削除します。

実行コマンド:

$ kubectl delete namespace np-demo monitoring

実行結果:

namespace "np-demo" deleted
namespace "monitoring" deleted

Namespace 配下の NetworkPolicy・Deployment・Service・Pod はすべてまとめて削除されました。

やってみよう②:CoreDNS Egress 遮断ミニトラブルシュート

NetworkPolicy 起因の DNS 障害を「症状確認 → IP 直アクセスで切り分け → ポリシー確認 → 修正 → 検証」の診断ループで解決します。CKA 試験の頻出シナリオです。

シナリオ設定

以下の状態を用意します。

  • ts-demo Namespace に nginx Deployment + nginx-svc Service が存在する
  • default-deny-all と Ingress のみを許可するポリシー(allow-ingress-same-ns)が適用済み
  • Egress ポリシーは存在しない(DNS クエリが kube-system に届かない)

シナリオ環境を構築します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: ts-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: ts-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: 192.168.1.123:5000/nginx:1.27
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: ts-demo
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: ts-demo
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-ingress-same-ns
  namespace: ts-demo
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector: {}
      ports:
        - protocol: TCP
          port: 80
EOF

実行結果:

namespace/ts-demo created
deployment.apps/nginx-deploy created
service/nginx-svc created
networkpolicy.networking.k8s.io/default-deny-all created
networkpolicy.networking.k8s.io/allow-ingress-same-ns created

診断 Step 1: 症状確認

busybox から nginx-svc への接続を試みます。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n ts-demo --rm -it --restart=Never -- nslookup nginx-svc

実行結果:

Server:    10.96.0.10
Address 1: 10.96.0.10

nslookup: can't resolve 'nginx-svc'
pod "busybox" deleted

名前解決が失敗しています。

診断 Step 2: IP 直アクセスで Egress の遮断範囲を切り分ける

ClusterIP を取得し、Service 名ではなく IP で直接アクセスして、名前解決以外の通信が通るかを確認します。

実行コマンド:

$ kubectl get svc nginx-svc -n ts-demo

実行結果:

NAME        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx-svc   ClusterIP   10.108.x.x     <none>        80/TCP    30s

実行コマンド:

$ CLUSTERIP=$(kubectl get svc nginx-svc -n ts-demo -o jsonpath='{.spec.clusterIP}')
$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n ts-demo --rm -it --restart=Never -- wget -O- http://${CLUSTERIP}:80 --timeout=3

実行結果:

Connecting to 10.108.x.x:80
wget: download timed out
pod "busybox" deleted

IP 直アクセスも download timed out になりました。default-deny-all は Egress も遮断しているため、busybox から nginx への送信(Egress)自体がブロックされており、名前解決だけでなく HTTP 通信も通りません。この時点で「DNS 単独の問題」ではなく「Egress 全体が遮断されている」可能性が高いと判断できます。やってみよう①の Step 3 で確認したとおり、通信の成立には送信元 Pod の Egress 許可が必要です。次の Step 3 で NetworkPolicy の Egress 設定を確認します。

診断 Step 3: NetworkPolicy の確認

Egress の設定を確認します。

実行コマンド:

$ kubectl get networkpolicy -n ts-demo

実行結果:

NAME                    POD-SELECTOR   AGE
allow-ingress-same-ns   app=nginx      1m
default-deny-all        <none>         1m

実行コマンド:

$ kubectl describe networkpolicy default-deny-all -n ts-demo

実行結果:

Name:         default-deny-all
Namespace:    ts-demo
Created on:   2026-05-24 10:00:00 +0000 UTC
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     <none> (Allowing the specific traffic to all pods in this namespace)
  Allowing ingress traffic:
    <none> (Selected pods are isolated for ingress connectivity)
  Allowing egress traffic:
    <none> (Selected pods are isolated for egress connectivity)
  Policy Types: Ingress, Egress

Egress が全ブロック(<none>)です。Egress 許可ポリシーが存在しないため、CoreDNS への UDP/TCP 53 が届いていません。

診断 Step 4: CoreDNS への疎通確認

kube-dns Service の ClusterIP を確認します。

実行コマンド:

$ kubectl get svc kube-dns -n kube-system

実行結果:

NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   XX d

kube-dns は UDP 53 と TCP 53 の両方を公開しています。busybox から UDP 53 の疎通を確認します。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n ts-demo --rm -it --restart=Never -- nc -zvu 10.96.0.10 53

実行結果:

nc: 10.96.0.10 (10.96.0.10:53): Operation timed out
pod "busybox" deleted

タイムアウトで遮断が確認できました。Egress で UDP 53 が遮断されているためです。

修正: DNS Egress 許可ポリシーを追加

UDP 53 と TCP 53 の両方を許可する Egress ポリシーを追加します。

実行コマンド:

$ kubectl apply -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: ts-demo
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

実行結果:

networkpolicy.networking.k8s.io/allow-dns-egress created

確認: 名前解決の成功を確認

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n ts-demo --rm -it --restart=Never -- nslookup nginx-svc

実行結果:

Server:    10.96.0.10
Address:   10.96.0.10:53

Name:      nginx-svc.ts-demo.svc.cluster.local
Address:   10.108.x.x
pod "busybox" deleted

名前解決が成功しました。本シナリオの症状(CoreDNS への Egress 遮断による名前解決の失敗)は解消です。ここで 1 点注意があります。追加した allow-dns-egress は DNS(ポート 53)への Egress だけを許可しています。Service 名で HTTP 接続まで試すとどうなるかを確認します。

実行コマンド:

$ kubectl run busybox --image=192.168.1.123:5000/busybox:1.36 -n ts-demo --rm -it --restart=Never -- wget -O- http://nginx-svc --timeout=5

実行結果(名前解決は成功するが、HTTP の Egress が未許可のため接続自体は通らない):

Connecting to nginx-svc (10.108.x.x:80)
wget: download timed out
pod "busybox" deleted

名前解決は復旧しましたが、HTTP の通信そのものは nginx Pod(TCP 80)へのクライアント Egress がまだ許可されていないため通りません。やってみよう①で確認したとおり、完全な疎通には宛先への Egress 許可も必要です。本シナリオの目的は「CoreDNS への Egress を許可して名前解決を復旧する」ことであり、それは nslookup の成功で達成できています。診断 → 修正 → 確認のループが完了しました。

クリーンアップ

実行コマンド:

$ kubectl delete namespace ts-demo

実行結果:

namespace "ts-demo" deleted

まとめ・現場ヒヤリハット・理解度チェック

第10回のまとめ

  • NetworkPolicy を 1 つも作成していない Pod は全通信許可(non-isolated 状態)。ポリシーが 1 つでも適用されると、許可されていない通信はすべてブロックされる(isolated 状態)
  • policyTypes は必ず明示する。省略すると Egress 制御が意図どおりに動かないケースがある
  • namespaceSelector + podSelector を同一ブロックに書くと AND(積集合)、別ブロックに分けると OR(和集合)になる。インデントの差でポリシーの意味が反転する
  • default-deny-all を適用してから必要な通信だけを許可するホワイトリスト方式が本番 Namespace の設計の基本である
  • CoreDNS への Egress は UDP 53 + TCP 53 の両方を許可しなければならない。DNS レスポンスが 512 バイトを超えると自動的に TCP にフォールバックするため、UDP のみでは断続的に障害が起きる
  • Calico は kubeadm 標準 CNI として L4 NetworkPolicy を完全サポートする。L7 Policy・Hubble・Pod 間 mTLS が必要な場合は第3巻(CKS 編)で Cilium を導入する

次回予告

第11回では Gateway API + CoreDNS カスタマイズ + Ingress Controller(Traefik v39.x)+ cert-manager v1.20.0 による TLS 終端を扱います。第10回で遮断した通信の「どこを通過させるか」をより高度に制御する回です。

現場ヒヤリハット:default-deny 適用後に CoreDNS Egress を忘れて全 Pod の名前解決が壊れた

状況: 本番 Namespace にセキュリティ強化の一環で default-deny-all NetworkPolicy を適用しました。適用直後、Service 名を使った内部通信が全件失敗。ログには connection timed outcould not resolve host が大量出力されました。アプリチームから「サービスが全部落ちた」と連絡が来ました。

原因:

  • DNS クエリは UDP 53 で kube-system Namespace の CoreDNS へ送信される。Egress を全ブロックしたため CoreDNS に到達できなくなった
  • さらに「UDP 53 だけ許可して TCP 53 を忘れた」ため、DNS レスポンスが 512 バイトを超えるケース(SRV レコード等)でも断続的に失敗した
  • IP 直接アクセスは成功していたため「NetworkPolicy は問題ない」と判断してしまい、原因特定に時間がかかった

修正した YAML(DNS Egress 許可):

egress:
  - to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
        podSelector:
          matchLabels:
            k8s-app: kube-dns
    ports:
      - protocol: UDP
        port: 53
      - protocol: TCP
        port: 53

教訓: default-deny-all を適用する際は「DNS Egress 許可テンプレート」をセットで適用するプロセスを必ず守ります。本番 Namespace の変更は手順書に DNS Egress 許可を明示的に含めてください。また「IP で届く、Service 名で届かない」という症状が出たらまず NetworkPolicy の Egress と DNS を疑います。

理解度チェック(○×形式・7 問)

各問に対して○か×で答え、解説を確認してください。

#問題文正解解説
Q1NetworkPolicy を 1 つも作成していない Pod は、同 Namespace の任意の Pod からの通信をすべて受け入れるnon-isolated 状態。ポリシーが存在しない Pod は全通信許可
Q2policyTypes: ["Ingress"] を指定すると、Egress トラフィックもブロックされる×policyTypes に Egress が含まれない場合、Egress は非制限のまま
Q3from 配列内の同一ブロックに namespaceSelectorpodSelector を並べると、OR(和集合)として評価される×同一ブロック内は AND(積集合)。OR にするには別ブロックに分ける
Q4CoreDNS への Egress 許可では、UDP 53 だけでなく TCP 53 も許可する必要があるDNS レスポンスが 512 バイトを超える場合 TCP にフォールバックするため
Q5Calico v3.32 は HTTP メソッドやパスでフィルタする L7 NetworkPolicy をサポートしている×L7 Policy は Cilium の標準機能。Calico OSS v3.32(本シリーズで使用)は L4 まで。Calico Enterprise(商用版)には L7 ポリシーがある
Q6Cilium の Hubble は eBPF を使い、サイドカーなしで L7 レベルのトラフィックを可視化できるHubble は eBPF データパスに乗った L3-L7 リアルタイム可視化ツール。サイドカー不要
Q7podSelector: {} を指定した NetworkPolicy は、同じ Namespace 内の全 Pod に適用されるpodSelector が空({})の場合は Namespace 全体の Pod が対象になる

シリーズ一覧

第1部:クラスタ構築

第2部:ワークロード管理

第3部:ネットワーク

第4部:ストレージ

第5部:監視・運用

第6部:トラブルシュート

広告
kubernetes
スポンサーリンク