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

Kubernetes応用編 第06回

Kubernetes応用編 第06回
Pod間の通信制御 — NetworkPolicy

6.1 はじめに

前回(第5回)では、Gateway APIを導入してTaskBoardの外部アクセスを整備しました。NodePortの直接公開から、GatewayClass → Gateway → HTTPRoute の3層構造によるL7ルーティングへ移行し、/ → Nginx、/api → TaskBoard API のパスベース振り分けを実現しました。

現在のTaskBoardの状態を確認しておきましょう。

[app Namespace]
  Nginx (Deployment, replicas: 2) + Service (ClusterIP)
  TaskBoard API (Deployment, replicas: 2, MySQL接続版) + Service (ClusterIP)
  Gateway (taskboard-gateway) + HTTPRoute (taskboard-route)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[db Namespace]
  MySQL (StatefulSet, replicas: 1) + Headless Service + PVC
  + DB初期化Job(Completed)
  + DBバックアップCronJob(稼働中)
  + Secret(MySQL認証情報)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[monitoring Namespace]
  ログ収集DaemonSet(全Worker Nodeに配置)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[envoy-gateway-system Namespace]
  Envoy Gatewayコントローラー
  Envoy Proxy データプレーン

[クラスタ全体]
  GatewayClass (eg)
  Metrics Server 稼働中
  マルチノード構成(CP 1 + Worker 3)

入口は整いました。外部からのアクセスはGateway APIで一本化されています。しかし、クラスタの「内側」に目を向けてみてください。Kubernetesのデフォルトでは、すべてのPodがすべてのPodと自由に通信できます。Nginx PodからMySQLに直接アクセスできますし、monitoring NamespaceのPodからdb NamespaceのPodにも到達できます。

これは、VMの世界でいえば「全サーバーをファイアウォールなしの同一ネットワークに置いている」のと同じ状態です。本番環境では許容できません。

BeforeAfter
クラスタ内は全Pod間が通信し放題。何も制限していないNetworkPolicyで必要最小限の通信のみを許可する設計ができる

今回は、NetworkPolicyを使って「誰が誰と通信できるか」を制御します。具体的には、以下を達成します。

  • NetworkPolicyに対応したCNI(Calico)を導入する
  • NetworkPolicyなしの状態で、不要な通信が通ることを確認する
  • db Namespaceに「デフォルト拒否 + API Podからのみ許可」のポリシーを適用する
  • app Namespaceに「デフォルト拒否 + Gateway経由のIngressのみ許可」のポリシーを適用する
  • TaskBoard全体の通信マトリクスをBefore/Afterで確認する

今回から3回にわたる「ネットワークとセキュリティ」セクションの第2弾です。前回が「入口の設計」なら、今回は「内部の通信制御」です。

6.2 VMの世界との対比 — NSX分散ファイアウォールとセキュリティグループ

6.2.1 VMの世界での通信制御

VMware環境でサーバー間の通信を制御する場合、NSX分散ファイアウォール(DFW)を使います。DFWの特徴を思い出してみましょう。

NSX DFWでは、まず「セキュリティグループ」を作成してVMを分類します。たとえば「Web層」「AP層」「DB層」のようなグループを作り、各VMをグループに所属させます。次に、グループ間の通信ルールを定義します。「Web層 → AP層: 8080/tcp を許可」「AP層 → DB層: 3306/tcp を許可」「Web層 → DB層: 拒否」のようにです。

NSX 分散ファイアウォール ルール設計例:

  送信元             宛先            ポート       アクション
  ─────────────────────────────────────────────────────
  Web層グループ   →  AP層グループ    8080/tcp     許可
  AP層グループ    →  DB層グループ    3306/tcp     許可
  Web層グループ   →  DB層グループ    任意         拒否
  その他          →  DB層グループ    任意         拒否
  ─────────────────────────────────────────────────────
  ※ デフォルトルール: 拒否(ホワイトリスト方式)

NSX DFWの重要な特徴は「分散」であることです。物理的なファイアウォールアプライアンスにトラフィックを集約するのではなく、各ESXiホストのカーネルレベルでフィルタリングが行われます。つまり、同一ホスト上のVM同士の通信であっても、ルールが適用されます。

もうひとつ重要なのが「マイクロセグメンテーション」の考え方です。従来のファイアウォールがネットワーク境界(北-南方向)でトラフィックを制御するのに対し、NSX DFWはサーバー間(東-西方向)の通信を細かく制御できます。同じネットワークセグメントにいるVM同士でも、ルールで通信を制限できるのです。

6.2.2 K8sではNetworkPolicyがその役割を担う

KubernetesのNetworkPolicyは、まさにNSX分散ファイアウォールと同じ役割を担います。VMの世界の概念と対応させてみましょう。

VMの世界(NSX DFW)Kubernetes(NetworkPolicy)
セキュリティグループ(VMの分類)ラベル(Podの分類)+ Namespace
ファイアウォールルール(送信元 → 宛先: ポート)NetworkPolicyの ingress / egress ルール
デフォルトルール(許可 or 拒否)デフォルト許可 → NetworkPolicyで拒否に変更可能
NSX Manager(ルール管理)kubectl apply(マニフェストで宣言的に管理)
分散ファイアウォール(カーネルレベル実装)CNIプラグイン(Calico / Cilium等がノードレベルで実装)

NSX DFWでは「セキュリティグループ」でVMを分類しました。Kubernetesでは「ラベル」と「Namespace」がその役割を担います。第1回で設計した app: taskboard, component: frontend のようなラベル体系が、ここで活きてくるのです。

NSX DFWのルール適用がESXiカーネルレベルで行われるように、KubernetesのNetworkPolicyもCNI(Container Network Interface)プラグインが各ノードレベルで実装します。ただし、すべてのCNIプラグインがNetworkPolicyをサポートしているわけではありません。この点が、今回最初に対処すべき課題です。

6.3 NetworkPolicyに対応したCNIを導入する

6.3.1 なぜCNIの変更が必要か — kindnetの制約

kindのデフォルトCNIは「kindnet」です。kindnetはシンプルなCNIプラグインで、Pod間の基本的なネットワーク接続を提供しますが、NetworkPolicyをサポートしていません。つまり、いくらNetworkPolicyのマニフェストをapplyしても、kindnetは何もフィルタリングしません。

VMの世界に例えるなら、「ファイアウォールのルールは書いたが、ファイアウォール自体がインストールされていない」状態です。ルールが存在しても、実行するエンジンがなければ意味がありません。

そこで、NetworkPolicyに対応したCNIプラグインを導入する必要があります。本シリーズではCalicoを採用します。Calicoは最も広く使われているCNIプラグインのひとつで、NetworkPolicyのフルサポートに加え、独自の拡張ポリシーも提供しています。

本番環境(マネージドK8s)では: EKS(Amazon VPC CNI + Calico)、GKE(Dataplane V2 / Calico)、AKS(Azure CNI + Calico)など、マネージドKubernetesではCalicoやCiliumが標準またはオプションで利用可能な場合がほとんどです。kind環境でのCNI導入は学習環境ならではの手順ですが、NetworkPolicyのマニフェスト自体は本番でもそのまま使えます。

kindnetを後からCalicoに差し替えることも技術的には可能ですが、CNIの共存・移行には想定外の問題が起きやすいです。確実に動作する方法として、kindクラスタを再作成します。

6.3.2 Calicoを導入する

まず、現在のクラスタを削除します。

[Execution User: developer]

kind delete cluster --name k8s-applied
Deleting cluster "k8s-applied" ...
Deleted nodes: ["k8s-applied-control-plane" "k8s-applied-worker" "k8s-applied-worker2" "k8s-applied-worker3"]

次に、Calico用のkindクラスタ定義を作成します。第1回で使った kind-applied.yaml をベースに、networking.disableDefaultCNI: truenetworking.podSubnet を追加します。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/kind-applied-calico.yaml
# kind-applied-calico.yaml — Calico対応クラスタ定義
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true   # kindnet を無効化
  podSubnet: "192.168.0.0/16"  # Calico のデフォルトCIDR
nodes:
  - role: control-plane
  - role: worker
  - role: worker
  - role: worker
EOF

ポイントは2つです。disableDefaultCNI: true でkindnetの自動インストールを無効化します。podSubnet: "192.168.0.0/16" はCalicoのデフォルトIPプールと合わせています。これにより、Calicoインストール時のIP設定を変更する必要がありません。

クラスタを作成します。

[Execution User: developer]

kind create cluster --name k8s-applied --config ~/k8s-applied/kind-applied-calico.yaml
Creating cluster "k8s-applied" ...
 ✓ Ensuring node image (kindest/node:v1.32.0) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing StorageClass 💾
 ✓ Joining worker nodes 🚜
Set kubectl context to "kind-k8s-applied"

クラスタは作成されましたが、CNIがインストールされていないため、ノードのステータスは NotReady です。

[Execution User: developer]

kubectl get nodes
NAME                          STATUS     ROLES           AGE   VERSION
k8s-applied-control-plane     NotReady   control-plane   60s   v1.32.0
k8s-applied-worker            NotReady   <none>          30s   v1.32.0
k8s-applied-worker2           NotReady   <none>          30s   v1.32.0
k8s-applied-worker3           NotReady   <none>          30s   v1.32.0

Calicoをインストールします。Calico公式のオペレーターとカスタムリソースを使います。

[Execution User: developer]

# Calico オペレーターをインストール
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml

[Execution User: developer]

# Calico カスタムリソースを適用(デフォルト設定で十分)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml

Calicoのインストールには数分かかります。calico-system Namespaceの全PodがRunningになるのを待ちます。

[Execution User: developer]

kubectl wait --timeout=5m -n calico-system deployment/calico-kube-controllers --for=condition=Available
kubectl wait --timeout=5m -n calico-system daemonset/calico-node --for=jsonpath='{.status.numberReady}'=4

ノードのステータスを確認します。

[Execution User: developer]

kubectl get nodes
NAME                          STATUS   ROLES           AGE     VERSION
k8s-applied-control-plane     Ready    control-plane   3m30s   v1.32.0
k8s-applied-worker            Ready    <none>          3m      v1.32.0
k8s-applied-worker2           Ready    <none>          3m      v1.32.0
k8s-applied-worker3           Ready    <none>          3m      v1.32.0

全ノードが Ready になりました。Calicoが正常にインストールされ、Pod間ネットワークが構築されています。CNIの導入はこれで完了です。次のセクションでTaskBoard環境を復元します。

6.3.3 TaskBoard環境を復元する

クラスタを再作成したため、第1回〜第5回で構築したリソースをすべて再適用する必要があります。本回の本題はNetworkPolicyなので、復元手順は簡潔にまとめます。以下の手順を順に実行してください。

手順1: Metrics Serverの導入

[Execution User: developer]

# Metrics Server(第1回で導入済みのものと同じ)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# kind環境用のパッチ(kubelet証明書検証をスキップ)
kubectl patch deployment metrics-server -n kube-system \
  --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

手順2: Namespaceの作成

[Execution User: developer]

kubectl apply -f ~/k8s-applied/namespace-app.yaml
kubectl apply -f ~/k8s-applied/namespace-db.yaml
kubectl apply -f ~/k8s-applied/namespace-monitoring.yaml

手順3: ResourceQuota / LimitRange / RBACの適用

[Execution User: developer]

# ResourceQuota と LimitRange(第1回)
kubectl apply -f ~/k8s-applied/resourcequota-app.yaml
kubectl apply -f ~/k8s-applied/resourcequota-db.yaml
kubectl apply -f ~/k8s-applied/resourcequota-monitoring.yaml
kubectl apply -f ~/k8s-applied/limitrange-app.yaml
kubectl apply -f ~/k8s-applied/limitrange-db.yaml
kubectl apply -f ~/k8s-applied/limitrange-monitoring.yaml

# RBAC(第2回)
kubectl apply -f ~/k8s-applied/sa-developer-app.yaml
kubectl apply -f ~/k8s-applied/sa-developer-db.yaml
kubectl apply -f ~/k8s-applied/sa-operator-app.yaml
kubectl apply -f ~/k8s-applied/sa-operator-db.yaml
kubectl apply -f ~/k8s-applied/role-developer-app.yaml
kubectl apply -f ~/k8s-applied/role-developer-db.yaml
kubectl apply -f ~/k8s-applied/role-operator-app.yaml
kubectl apply -f ~/k8s-applied/role-operator-db.yaml
kubectl apply -f ~/k8s-applied/rolebinding-developer-app.yaml
kubectl apply -f ~/k8s-applied/rolebinding-developer-db.yaml
kubectl apply -f ~/k8s-applied/rolebinding-operator-app.yaml
kubectl apply -f ~/k8s-applied/rolebinding-operator-db.yaml

手順4: TaskBoard APIイメージの再ロード

[Execution User: developer]

# 第3回でビルドしたMySQL接続版のイメージを再ロード
kind load docker-image taskboard-api:1.0 --name k8s-applied

手順5: db Namespaceのリソースを適用

[Execution User: developer]

# Secret → Headless Service → StatefulSet の順に適用
kubectl apply -f ~/k8s-applied/mysql-secret.yaml
kubectl apply -f ~/k8s-applied/mysql-headless-service.yaml
kubectl apply -f ~/k8s-applied/mysql-statefulset.yaml

# StatefulSetの起動を待つ
kubectl wait --timeout=3m -n db pod/mysql-0 --for=condition=Ready

手順6: DB初期化Jobの実行

[Execution User: developer]

kubectl apply -f ~/k8s-applied/db-init-job.yaml
kubectl wait --timeout=2m -n db job/db-init --for=condition=Complete

手順7: app Namespaceのリソースを適用

[Execution User: developer]

# Deployment と Service
kubectl apply -f ~/k8s-applied/nginx-deployment.yaml
kubectl apply -f ~/k8s-applied/nginx-service.yaml
kubectl apply -f ~/k8s-applied/mysql-secret-app.yaml
kubectl apply -f ~/k8s-applied/taskboard-api-deployment-v2.yaml
kubectl apply -f ~/k8s-applied/taskboard-api-service.yaml

# 起動を待つ
kubectl wait --timeout=3m -n app deployment/nginx --for=condition=Available
kubectl wait --timeout=3m -n app deployment/taskboard-api --for=condition=Available

手順8: CronJob / DaemonSetの適用

[Execution User: developer]

# DBバックアップCronJob(第4回)
kubectl apply -f ~/k8s-applied/db-backup-cronjob.yaml

# ログ収集DaemonSet(第4回)
kubectl apply -f ~/k8s-applied/log-collector-daemonset.yaml

手順9: Gateway APIの導入と設定

[Execution User: developer]

# Envoy Gateway のインストール(第5回)
kubectl apply --server-side -f https://github.com/envoyproxy/gateway/releases/download/v1.6.3/install.yaml
kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available

# Gateway と HTTPRoute
kubectl apply -f ~/k8s-applied/gateway.yaml
kubectl apply -f ~/k8s-applied/httproute-taskboard.yaml

手順10: 復元の確認

[Execution User: developer]

# 全Namespaceのリソースを一括確認
kubectl get all -n app
kubectl get all -n db
kubectl get all -n monitoring
kubectl get gateway,httproute -n app

すべてのPodがRunningであること、GatewayのPROGRAMMEDがTrueであることを確認してください。問題があれば、各Podのkubectl describekubectl logsで原因を調べます。

Gateway API経由の動作確認も行っておきましょう。

[Execution User: developer]

# Gateway の Service名を取得してport-forward
export GATEWAY_SVC=$(kubectl get svc -n envoy-gateway-system \
  -l gateway.envoyproxy.io/owning-gateway-name=taskboard-gateway \
  -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward -n envoy-gateway-system svc/$GATEWAY_SVC 8080:80 &

# フロントエンドの確認
curl -s http://localhost:8080/ | head -5

# APIの確認
curl -s http://localhost:8080/api/tasks | head -3

# port-forwardを停止
kill %1 2>/dev/null; echo "port-forward stopped"

フロントエンドのHTMLとAPIのJSONレスポンスが返ってくれば、TaskBoard環境の復元は完了です。これで、NetworkPolicyのハンズオンを始める準備が整いました。

6.4 NetworkPolicyなしの状態を確認する — 全Pod間通信テスト

NetworkPolicyを適用する前に、まず「何も制限していない状態」を確認します。Before/Afterの対比を実感するために、このステップは重要です。

6.4.1 通信テスト用Podを使って現状を可視化する

通信テストには kubectl run で一時的なPodを作成し、そこからServiceへの到達性を確認します。テスト用Podには busybox を使います。wget コマンドでHTTP接続、nc(netcat)コマンドでTCP接続を確認できます。

TaskBoardの主要な通信経路を整理しておきます。

想定される正しい通信経路:
  外部クライアント → Gateway → Nginx (app)      : 正当(フロントエンド配信)
  外部クライアント → Gateway → TaskBoard API (app): 正当(API呼び出し)
  TaskBoard API (app) → MySQL (db)                : 正当(データベースアクセス)

不要・危険な通信経路:
  Nginx (app) → MySQL (db)                        : 不要(フロントからDBに直接アクセスする理由がない)
  monitoring → MySQL (db)                          : 不要(ログ収集にDB接続は不要)
  任意のPod → MySQL (db)                           : 危険(DBへの直接アクセスはAPIのみに制限すべき)

6.4.2 フロント → DB:通る(本来は不要な通信)

app Namespace内のNginx Podから、db NamespaceのMySQL(3306ポート)に直接アクセスできるか確認します。

[Execution User: developer]

# app Namespaceにテスト用Podを作成し、db NamespaceのMySQLに接続を試みる
kubectl run test-frontend -n app --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
mysql-headless.db.svc.cluster.local (192.168.x.x:3306) open
pod "test-frontend" deleted

open と表示されました。NginxのPodからMySQLのポートに直接到達できる状態です。フロントエンドがデータベースに直接アクセスする理由はありません。これは「不要な通信」です。

6.4.3 API → DB:通る(必要な通信)

次に、TaskBoard API PodからMySQLへの通信を確認します。これはアプリケーションの正常動作に必要な通信です。

[Execution User: developer]

# app Namespaceからテスト(APIと同じNamespace)
kubectl run test-api -n app --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
mysql-headless.db.svc.cluster.local (192.168.x.x:3306) open
pod "test-api" deleted

こちらも通ります。この通信はNetworkPolicy適用後も維持する必要があります。

6.4.4 外部 → DB:通る(危険な通信)

最後に、monitoring NamespaceのPodからMySQLへの通信を確認します。ログ収集DaemonSetがデータベースにアクセスする理由はありません。

[Execution User: developer]

# monitoring NamespaceからMySQLへの接続を試みる
kubectl run test-monitoring -n monitoring --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
mysql-headless.db.svc.cluster.local (192.168.x.x:3306) open
pod "test-monitoring" deleted

monitoring Namespaceからもデータベースに到達できます。現在の通信マトリクスを表で整理しておきましょう。

送信元宛先ポート現状あるべき姿
Gateway (envoy-gateway-system)Nginx (app)80✅ 通る✅ 許可
Gateway (envoy-gateway-system)TaskBoard API (app)8080✅ 通る✅ 許可
TaskBoard API (app)MySQL (db)3306✅ 通る✅ 許可
Nginx (app)MySQL (db)3306✅ 通る❌ 遮断すべき
monitoringMySQL (db)3306✅ 通る❌ 遮断すべき
任意のPodMySQL (db)3306✅ 通る❌ 遮断すべき

「現状」と「あるべき姿」にギャップがあることが確認できました。このギャップをNetworkPolicyで埋めていきます。

6.5 NetworkPolicyの構造を理解する

6.5.1 NetworkPolicyの基本構造 — podSelector / ingress / egress

NetworkPolicyのマニフェストは、3つの主要な部分で構成されています。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: example-policy
  namespace: target-namespace     # このポリシーが適用されるNamespace
spec:
  podSelector:                    # (1) 対象Pod — どのPodにポリシーを適用するか
    matchLabels:
      app: example
  policyTypes:                    # (2) 方向 — Ingress(着信)/ Egress(発信)
    - Ingress
    - Egress
  ingress:                        # (3-a) Ingressルール — 着信を許可する条件
    - from:
        - namespaceSelector:      #   送信元Namespaceの条件
            matchLabels:
              layer: application
          podSelector:            #   送信元Podの条件
            matchLabels:
              component: api
      ports:
        - port: 3306              #   許可するポート
  egress:                         # (3-b) Egressルール — 発信を許可する条件
    - to:
        - namespaceSelector:
            matchLabels:
              layer: database
      ports:
        - port: 3306

(1) podSelector — 「このポリシーの対象となるPod」を選択します。NSX DFWでいえば「ルールを適用するセキュリティグループ」です。空の podSelector: {} を指定すると、そのNamespace内の全Podが対象になります。

(2) policyTypes — ポリシーが制御する方向を指定します。Ingress(着信)とEgress(発信)のどちらか、または両方を指定します。

(3) ingress / egress — 許可する通信の条件を定義します。from(Ingress)または to(Egress)で送信元・宛先を、portsで許可するポートを指定します。

重要な点が2つあります。namespaceSelectorpodSelector を同一の from/to ブロック内に書くと「AND条件」(両方を満たす)になります。別々の from/to ブロックに書くと「OR条件」(どちらかを満たす)になります。この違いはNetworkPolicyで最もよく起こる設計ミスの原因なので、注意してください。

# AND条件 — 同一ブロック内に記述
# 「layer: applicationのNamespace」かつ「component: apiのPod」からの通信を許可
ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            layer: application
        podSelector:            # namespaceSelector と同じインデントレベル = AND
          matchLabels:
            component: api

# OR条件 — 別々のブロックに記述
# 「layer: applicationのNamespace」または「component: apiのPod」からの通信を許可
ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            layer: application
      - podSelector:            # 別の配列要素 = OR
          matchLabels:
            component: api

6.5.2 「デフォルト許可」と「デフォルト拒否」の考え方

Kubernetesのデフォルトでは、NetworkPolicyが1つも存在しないNamespaceの全通信は許可されています。これが「デフォルト許可」です。先ほどの通信テストで確認した通り、何もない状態では全Pod間が自由に通信できます。

ネットワークセキュリティの基本原則は「デフォルト拒否 + ホワイトリスト(必要な通信のみ明示的に許可)」です。NSX DFWでも、デフォルトルールを「拒否」に設定し、許可するルールだけを個別に定義するのが一般的です。

Kubernetesで「デフォルト拒否」を実現するには、以下のNetworkPolicyを適用します。

# デフォルト拒否ポリシーのテンプレート
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: target-namespace
spec:
  podSelector: {}              # 全Podが対象
  policyTypes:
    - Ingress                  # 着信を拒否
    - Egress                   # 発信を拒否

podSelector: {} でNamespace内の全Podを対象にし、policyTypes にIngressとEgressを指定しますが、ingress/egress ルールを書きません。ルールが存在しない = 何も許可しない = すべて拒否です。

このデフォルト拒否ポリシーを「土台」として適用した後、必要な通信だけを個別のNetworkPolicyで許可していく。これが「デフォルト拒否 + ホワイトリスト」パターンです。NSX DFWの設計と同じ考え方です。

NetworkPolicyの「加算ルール」: 複数のNetworkPolicyが同じPodに適用される場合、ルールは「加算(union)」されます。つまり、いずれかのNetworkPolicyが許可する通信はすべて許可されます。「デフォルト拒否」ポリシーが存在しても、別のNetworkPolicyで許可ルールを追加すれば、その通信は通ります。NSX DFWの「優先度ベースの上書き」とは異なり、拒否ルールで特定の通信を個別にブロックすることはできません。許可しなければ自動的にブロックされる仕組みです。

6.6 db NamespaceにNetworkPolicyを適用する

まず、最も保護すべきデータベース層から着手します。db NamespaceにNetworkPolicyを適用し、API Pod以外からのアクセスを遮断します。

6.6.1 デフォルト拒否ポリシーを作成する

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-db-default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: db
spec:
  # 全Podが対象
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
EOF

db Namespace内の全Pod(MySQLを含む)に対して、すべての着信・発信を拒否するポリシーです。これを適用した瞬間、MySQLは完全に孤立します。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-db-default-deny.yaml
networkpolicy.networking.k8s.io/default-deny-all created

ここで通信テストを行うと、app NamespaceからMySQLに到達できなくなっていることを確認できます。

[Execution User: developer]

# タイムアウト5秒を設定(遮断時に無限に待たないため)
kubectl run test-deny -n app --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-deny" deleted

Connection timed out。デフォルト拒否ポリシーが効いています。ただし、この状態ではTaskBoard APIからのMySQL接続も遮断されてしまいます。TaskBoardは正常に動作しません。次に、必要な通信だけを許可するポリシーを追加します。

6.6.2 API Podからの通信のみ許可するポリシーを作成する

db NamespaceのMySQL Podに対して、app Namespaceの TaskBoard API Pod(component: api)からの3306ポートへのIngressのみを許可します。さらに、MySQL PodからのDNSクエリ(kube-dnsへのアクセス)も許可する必要があります。Egress を完全に遮断するとDNS解決ができなくなり、想定外の問題が発生するためです。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-db-allow-api.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-mysql
  namespace: db
spec:
  # 対象: MySQL Pod
  podSelector:
    matchLabels:
      app: taskboard
      component: db
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # app Namespace の API Pod からの 3306/tcp のみ許可
    - from:
        - namespaceSelector:
            matchLabels:
              layer: application       # app Namespace のラベル
          podSelector:
            matchLabels:
              app: taskboard
              component: api           # TaskBoard API Pod のラベル
      ports:
        - protocol: TCP
          port: 3306
  egress:
    # DNS(kube-dns)への発信を許可(53/udp, 53/tcp)
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

このマニフェストのポイントを整理します。

  • podSelectorapp: taskboard, component: db のPod(MySQL)を対象にしています
  • ingress.from では namespaceSelectorpodSelector同一ブロック内に記述(AND条件)しています。「layer: application ラベルを持つNamespace内の、component: api ラベルを持つPod」からのIngressのみを許可します
  • ingress.ports で3306/tcpのみに限定しています
  • egress でkube-system NamespaceへのDNSクエリ(53番ポート)を許可しています。これがないと、MySQLのログやヘルスチェックでDNS解決が必要な場合に問題が生じます

DNS許可を忘れると何が起きるか: Egress を完全に遮断すると、Pod内からのDNSクエリ(kube-dns.kube-system.svc.cluster.local:53)も遮断されます。Service名による名前解決ができなくなり、一見ネットワーク障害のような症状が出ます。Egress拒否を設定する場合は、DNS(53/udp, 53/tcp)の許可を忘れないでください。これはNetworkPolicyのデバッグで最も多いハマりポイントです。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-db-allow-api.yaml
networkpolicy.networking.k8s.io/allow-api-to-mysql created

6.6.3 CronJobバックアップの通信も許可する

ここで思い出してください。第4回でdb NamespaceにはDBバックアップのCronJobを配置しました。CronJobが生成するPodは component: db-backup ラベルを持ち、MySQLに接続してmysqldumpを実行します。デフォルト拒否ポリシーの下では、このCronJobのPodもMySQLに接続できなくなっています。

「NetworkPolicyを設計する際は、アプリケーションの通信経路だけでなく、運用ツール(バックアップ、監視、メンテナンス)の通信も忘れずに考慮する」——これは実務で最もよく見落とされるポイントです。

CronJobバックアップPodからMySQLへの通信を許可するポリシーを追加します。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-db-allow-backup.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backup-to-mysql
  namespace: db
spec:
  # 対象: MySQL Pod
  podSelector:
    matchLabels:
      app: taskboard
      component: db
  policyTypes:
    - Ingress
  ingress:
    # 同一Namespace(db)内のバックアップPodからの 3306/tcp を許可
    - from:
        - podSelector:
            matchLabels:
              app: taskboard
              component: db-backup
      ports:
        - protocol: TCP
          port: 3306
EOF

CronJobバックアップPod自身にもEgress(MySQL + DNS)が必要です。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-db-allow-backup-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backup-egress
  namespace: db
spec:
  # 対象: バックアップPod
  podSelector:
    matchLabels:
      app: taskboard
      component: db-backup
  policyTypes:
    - Egress
  egress:
    # 同一Namespace内のMySQLへの発信を許可
    - to:
        - podSelector:
            matchLabels:
              app: taskboard
              component: db
      ports:
        - protocol: TCP
          port: 3306
    # DNS への発信を許可
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

fromnamespaceSelector を省略し podSelector のみを指定しているのがポイントです。namespaceSelector がない場合、NetworkPolicyが属するNamespace(この場合 db)内のPodのみにマッチします。つまり「db Namespace内の component: db-backup Podから」という意味になります。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-db-allow-backup.yaml
kubectl apply -f ~/k8s-applied/netpol-db-allow-backup-egress.yaml
networkpolicy.networking.k8s.io/allow-backup-to-mysql created
networkpolicy.networking.k8s.io/allow-backup-egress created

6.6.4 通信テスト — フロント → DBが遮断されたことを確認する

db Namespaceに4つのNetworkPolicy(デフォルト拒否 + API許可 + バックアップIngress許可 + バックアップEgress許可)が適用された状態で、通信テストを行います。

テスト1: API Pod(component: api)からMySQL — 通るはず

TaskBoard APIと同じラベルを持つテスト用Podで確認します。

[Execution User: developer]

# API Pod と同じラベルを付与してテスト
kubectl run test-api-labeled -n app --rm -it --restart=Never \
  --labels="app=taskboard,component=api" \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
mysql-headless.db.svc.cluster.local (192.168.x.x:3306) open
pod "test-api-labeled" deleted

API Podのラベルを持つPodからの通信は許可されています。

テスト2: Nginx Pod(component: frontend)からMySQL — 遮断されるはず

[Execution User: developer]

# フロントエンドのラベルを付与してテスト
kubectl run test-frontend-labeled -n app --rm -it --restart=Never \
  --labels="app=taskboard,component=frontend" \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-frontend-labeled" deleted

Connection timed out。フロントエンドからデータベースへの直接通信が遮断されました。

テスト3: monitoring NamespaceからMySQL — 遮断されるはず

[Execution User: developer]

kubectl run test-monitoring -n monitoring --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-monitoring" deleted

monitoring Namespaceからも遮断されています。

テスト4: バックアップPod(component: db-backup)からMySQL — 通るはず

[Execution User: developer]

# バックアップPodと同じラベルを付与してテスト
kubectl run test-backup-labeled -n db --rm -it --restart=Never \
  --labels="app=taskboard,component=db-backup" \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
mysql-headless.db.svc.cluster.local (192.168.x.x:3306) open
pod "test-backup-labeled" deleted

バックアップPodからMySQLへの通信は維持されています。Before/Afterを比較してみましょう。

送信元宛先Before(6.4節)After(6.6節)
API Pod (app, component: api)MySQL (db)✅ 通る✅ 通る
バックアップPod (db, component: db-backup)MySQL (db)✅ 通る✅ 通る
Nginx Pod (app, component: frontend)MySQL (db)✅ 通る❌ 遮断
monitoringMySQL (db)✅ 通る❌ 遮断

db Namespaceのセキュリティが確保されました。次に、app NamespaceにもNetworkPolicyを適用します。

6.7 app NamespaceにNetworkPolicyを適用する

app Namespaceでは、以下の通信を制御します。

  • Ingressの制御: Gateway(Envoy Proxy)からのHTTPトラフィックのみを許可する
  • Egressの制御: API Podからdb NamespaceのMySQL(3306/tcp)への通信と、DNS(53)への通信を許可する

6.7.1 デフォルト拒否ポリシーを作成する

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-app-default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: app
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
EOF

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-app-default-deny.yaml
networkpolicy.networking.k8s.io/default-deny-all created

この時点で、app Namespace内の全Podはすべての通信が遮断されます。Gateway経由のアクセスも、API PodからMySQLへの接続もすべて止まります。TaskBoardは完全に機能停止します。ここから必要な通信だけを復活させていきます。

6.7.2 Gateway経由のIngressのみ許可するポリシーを作成する

app Namespaceに対して、以下の通信を許可するNetworkPolicyを作成します。

  • Envoy Gatewayデータプレーン(envoy-gateway-system Namespace)からのIngressを許可
  • 全PodからDNS(kube-system, 53番ポート)へのEgressを許可

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-app-allow-gateway.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-gateway-ingress
  namespace: app
spec:
  # 対象: app Namespace の全Pod
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # envoy-gateway-system Namespace からの着信を許可
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: envoy-gateway-system
      ports:
        - protocol: TCP
          port: 80     # Nginx
        - protocol: TCP
          port: 8080   # TaskBoard API
  egress:
    # DNS(kube-dns)への発信を許可
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

Envoy Gatewayのデータプレーン(実際にトラフィックを処理するEnvoy Proxy Pod)は envoy-gateway-system Namespaceに配置されています(第5回で確認しました)。namespaceSelectorkubernetes.io/metadata.name: envoy-gateway-system を指定することで、このNamespace内のPodからのIngressを許可しています。

kubernetes.io/metadata.name ラベルについて: Kubernetes 1.22以降、すべてのNamespaceにはNamespace名と同じ値を持つ kubernetes.io/metadata.name ラベルが自動付与されます。カスタムラベルを事前に定義していないNamespace(envoy-gateway-systemkube-system など)を namespaceSelector で指定する際に便利です。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-app-allow-gateway.yaml
networkpolicy.networking.k8s.io/allow-gateway-ingress created

続いて、API PodからMySQLへのEgressを許可するポリシーを追加します。先ほどのポリシーではDNSのみが許可されているため、API PodからMySQLへの3306/tcp通信はまだ遮断されています。

[Execution User: developer]

cat <<'EOF' > ~/k8s-applied/netpol-app-allow-api-to-db.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: app
spec:
  # 対象: TaskBoard API Pod のみ
  podSelector:
    matchLabels:
      app: taskboard
      component: api
  policyTypes:
    - Egress
  egress:
    # db Namespace の MySQL への発信を許可
    - to:
        - namespaceSelector:
            matchLabels:
              layer: database          # db Namespace のラベル
          podSelector:
            matchLabels:
              app: taskboard
              component: db            # MySQL Pod のラベル
      ports:
        - protocol: TCP
          port: 3306
    # DNS への発信も許可(重複だが明示的に)
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

このポリシーは podSelectorcomponent: api のPod(TaskBoard API)のみを対象にしています。Nginx Podにはこのポリシーは適用されないため、Nginx PodからMySQLへのEgressは引き続き遮断されたままです。

[Execution User: developer]

kubectl apply -f ~/k8s-applied/netpol-app-allow-api-to-db.yaml
networkpolicy.networking.k8s.io/allow-api-to-db created

6.7.3 通信テスト — 全体の通信マトリクスを確認する

app Namespaceとdb Namespaceの両方にNetworkPolicyが適用された状態で、TaskBoard全体の通信マトリクスを確認します。

テスト1: Gateway → Nginx — 通るはず

[Execution User: developer]

# Gateway のport-forward を再開
export GATEWAY_SVC=$(kubectl get svc -n envoy-gateway-system \
  -l gateway.envoyproxy.io/owning-gateway-name=taskboard-gateway \
  -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward -n envoy-gateway-system svc/$GATEWAY_SVC 8080:80 &
sleep 2

# フロントエンドの確認
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
200

HTTPステータス 200 が返りました。Gateway経由のフロントエンドへのアクセスは正常です。

テスト2: Gateway → API → MySQL — 通るはず

[Execution User: developer]

curl -s http://localhost:8080/api/tasks | head -3
[{"id":1,"title":"サーバー構成図の更新","completed":false}, ...]

APIもMySQLからデータを取得できています。Gateway → API → MySQL の正規の通信経路は維持されています。

テスト3: フロント → DB直接 — 遮断されるはず

[Execution User: developer]

kubectl run test-final-frontend -n app --rm -it --restart=Never \
  --labels="app=taskboard,component=frontend" \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-final-frontend" deleted

テスト4: monitoring → DB直接 — 遮断されるはず

[Execution User: developer]

kubectl run test-final-monitoring -n monitoring --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-final-monitoring" deleted

テスト5: 外部(default Namespace)→ DB直接 — 遮断されるはず

[Execution User: developer]

kubectl run test-final-external -n default --rm -it --restart=Never \
  --image=busybox:1.36 -- \
  nc -zv -w 5 mysql-headless.db.svc.cluster.local 3306
nc: mysql-headless.db.svc.cluster.local (192.168.x.x:3306): Connection timed out
pod "test-final-external" deleted

port-forwardを停止しておきます。

[Execution User: developer]

kill %1 2>/dev/null; echo "port-forward stopped"

最終的な通信マトリクスを確認しましょう。

送信元宛先ポートBefore(6.4節)After(最終)判定
Gateway (envoy-gateway-system)Nginx (app)80✅ 通る✅ 通る正常
Gateway (envoy-gateway-system)TaskBoard API (app)8080✅ 通る✅ 通る正常
TaskBoard API (app)MySQL (db)3306✅ 通る✅ 通る正常
バックアップPod (db)MySQL (db)3306✅ 通る✅ 通る正常
Nginx (app)MySQL (db)3306✅ 通る❌ 遮断正常
monitoringMySQL (db)3306✅ 通る❌ 遮断正常
default NamespaceMySQL (db)3306✅ 通る❌ 遮断正常

すべてが「あるべき姿」と一致しています。不要な通信は遮断され、正規の通信経路のみが維持されています。

6.8 NetworkPolicyのデバッグ

6.8.1 意図通りに遮断されているかの確認方法

NetworkPolicyが期待通りに動作しているか確認するためのデバッグ手法を整理します。

方法1: 適用されているNetworkPolicyの一覧確認

[Execution User: developer]

# 各Namespaceの NetworkPolicy を確認
kubectl get networkpolicy -n db
kubectl get networkpolicy -n app
# db Namespace
NAME                    POD-SELECTOR                          AGE
default-deny-all        <none>                                10m
allow-api-to-mysql      app=taskboard,component=db            8m
allow-backup-to-mysql   app=taskboard,component=db            7m
allow-backup-egress     app=taskboard,component=db-backup     7m

# app Namespace
NAME                    POD-SELECTOR                          AGE
default-deny-all        <none>                                5m
allow-gateway-ingress   <none>                                4m
allow-api-to-db         app=taskboard,component=api           3m

方法2: NetworkPolicyの詳細確認

[Execution User: developer]

kubectl describe networkpolicy allow-api-to-mysql -n db

describe の出力で、Allowing ingress traffic セクションに期待するルール(送信元のNamespaceとPodラベル、許可ポート)が表示されていることを確認します。ここに意図しないルールが含まれていたり、必要なルールが欠けていたりすれば、マニフェストを修正します。

方法3: テスト用Podによる到達性テスト

今回のハンズオンで行ったように、kubectl run でラベルを指定した一時Podを作成し、nc -zv -w 5 で到達性を確認する方法が最も確実です。-w 5(5秒タイムアウト)を忘れないでください。遮断されている通信をタイムアウトなしでテストすると、長時間待たされることになります。

方法4: 実際のアプリケーションの動作確認

最終的には、TaskBoardの正常動作が確認できればOKです。Gateway経由でフロントエンドが表示され、APIがMySQLからデータを取得できていれば、正規の通信経路は維持されています。

6.8.2 よくあるトラブルと対処法

症状原因対処法
デフォルト拒否を適用したら全通信が止まった許可ポリシーをまだ適用していない許可ポリシーを順に適用する。デフォルト拒否は「土台」であり、単独では全遮断になるのが正常
NetworkPolicyを適用したが遮断されないCNIがNetworkPolicyをサポートしていない(kindnetのまま等)kubectl get pods -n calico-system でCalicoが稼働しているか確認
DNS解決ができなくなった(名前解決タイムアウト)Egress拒否でDNS(53番ポート)も遮断されているkube-system NamespaceへのEgress(53/udp, 53/tcp)を許可するルールを追加
namespaceSelector で指定したNamespaceからの通信が遮断されるNamespaceのラベルが想定と異なるkubectl get ns --show-labels で実際のラベルを確認。kubernetes.io/metadata.name は自動付与される組み込みラベル
AND条件のつもりがOR条件になっているYAML のインデントミスで別配列要素になっているnamespaceSelectorpodSelector が同一ブロック内(同じ - の下)にあるか確認
特定のPodだけ通信できないpodSelector のラベルが実際のPodラベルと一致していないkubectl get pods -n <ns> --show-labels で実際のラベルを確認
CronJobやJobが失敗するようになったデフォルト拒否適用後、運用ツール(バックアップ、監視等)の通信を許可していないアプリケーション通信だけでなく、運用系Pod(CronJob, Job, DaemonSet)の通信経路も洗い出してポリシーを追加

6.9 この回のまとめ

6.9.1 TaskBoardの現在地

今回の作業で、TaskBoardに以下が追加されました。

[app Namespace]
  Nginx (Deployment, replicas: 2) + Service (ClusterIP)
  TaskBoard API (Deployment, replicas: 2, MySQL接続版) + Service (ClusterIP)
  Gateway (taskboard-gateway) + HTTPRoute (taskboard-route)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み
  + NetworkPolicy適用済み ← 今回追加
    - default-deny-all(デフォルト拒否)
    - allow-gateway-ingress(Gateway経由のIngressのみ許可)
    - allow-api-to-db(API → MySQL のEgressのみ許可)

[db Namespace]
  MySQL (StatefulSet, replicas: 1) + Headless Service + PVC
  + DB初期化Job(Completed)
  + DBバックアップCronJob(稼働中)
  + Secret(MySQL認証情報)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み
  + NetworkPolicy適用済み ← 今回追加
    - default-deny-all(デフォルト拒否)
    - allow-api-to-mysql(API Podからの3306/tcpのみ許可)
    - allow-backup-to-mysql(バックアップPodからの3306/tcpのみ許可)
    - allow-backup-egress(バックアップPodのEgress許可)

[monitoring Namespace]
  ログ収集DaemonSet(全Worker Nodeに配置)
  + ResourceQuota / LimitRange 適用済み
  + RBAC適用済み

[envoy-gateway-system Namespace]
  Envoy Gatewayコントローラー
  Envoy Proxy データプレーン

[calico-system Namespace] ← 今回追加
  Calico CNI

[クラスタ全体]
  GatewayClass (eg)
  Metrics Server 稼働中
  マルチノード構成(CP 1 + Worker 3)

Pod間通信がNetworkPolicyで最小権限化されました。データベースへのアクセスはAPI Podからのみ、app Namespaceへの着信はGateway経由のみに制限されています。

6.9.2 NetworkPolicy設計の判断基準 — いつ使う / いつ使わない

判断ケース
使うべき本番環境のマルチテナント・マルチNamespace構成。DB層への直接アクセスを防ぎたい場合。コンプライアンス要件でネットワーク分離が求められる場合。PCI DSS等のセキュリティ基準への準拠が必要な場合
使わなくてよい開発者個人の学習・テスト環境。単一Namespace・単一アプリの簡易構成。NetworkPolicyの管理コスト(ルール設計・テスト・メンテナンス)がセキュリティ上のメリットを上回る場合
注意が必要CNIがNetworkPolicyをサポートしているか事前に確認すること。デフォルト拒否を適用する場合は、DNS許可を忘れないこと。マイクロサービスが多い環境ではポリシーの数が増えるため、命名規則とラベル設計が重要

実務での推奨パターンは「デフォルト拒否 + ホワイトリスト」です。新しいNamespaceを作成したら、まずデフォルト拒否ポリシーを適用する。その後、必要な通信経路を1つずつ許可していく。この手順を徹底すれば、「許可し忘れ」は気づきやすく、「遮断し忘れ」は発生しません。NSX DFWの設計と同じ原則です。

6.9.3 実践編への橋渡し

今回学んだNetworkPolicyの知識は、実践編で以下の場面で使います。

  • 実践編 第3回(詳細設計): NetworkPolicyのルールを「なぜこの設計にしたか」という根拠とともに設計書に落とし込みます。通信マトリクス表は設計書の必須項目です
  • 実践編 第6回(ネットワーク構築と結合テスト): 今回のNetworkPolicy設定を本番品質で再適用し、E2Eテストで全通信経路の疎通を検証します
  • 実践編 第9回(障害対応): 「NetworkPolicyの誤設定による通信断」が障害シナリオの1つとして登場します。今回学んだデバッグ手法が、障害の切り分けに直結します

6.9.4 次回予告

ネットワークのセキュリティが整いました。次回(第7回)では、コンテナ自体のセキュリティに取り組みます。SecurityContextとPod Security Standardsを使って、TaskBoardの各コンテナを「最小権限の実行環境」に強化します。Payara Micro(すでに非rootで動作するpayaraユーザー)とNginx(rootで動作している)の違いを対比しながら、コンテナセキュリティのベストプラクティスを体験します。

AIコラム — NetworkPolicyの通信マトリクスをAIに整理させる

NetworkPolicyの設計では「どのPodがどのPodと通信できるか」を表で整理する作業が発生します。コンポーネント数が増えると、この通信マトリクスを手作業で管理するのは負担です。

AIに「TaskBoardの構成を説明し、必要な通信経路を洗い出してNetworkPolicyのマニフェストを生成してほしい」と依頼すると、通信マトリクスの初版とマニフェストのドラフトを短時間で得られます。ただし、AIが生成したNetworkPolicyをそのまま適用するのは危険です。以下のチェックポイントを必ず自分で確認してください。

  • namespaceSelectorpodSelector の AND/OR 条件は意図通りか(YAMLインデントのミスで意味が変わる)
  • DNS(53番ポート)のEgressは許可されているか
  • Namespaceのラベルは実際にクラスタ上で付与されているものと一致しているか
  • ポート番号はServiceの定義と合っているか

実践編第6回では、AIにNetworkPolicyの設定を生成させ、読者と一緒に批判的にレビューする場面があります。今回の学びが、AIの出力を検証する力の土台になります。