新卒インフラエンジニア向け「Kubernetes 実践教科書① CKAD アプリケーション開発編」の第16回です。第15回で RBAC と SecurityContext を使って fanclub-api の Backend を「誰が操作できるか」「どう制限して動かすか」の面から守りました。今回はNetworkPolicyで「どの Pod がどの Pod と通信できるか」を絞ります。既定では素通しの Pod 間通信を default-deny(全拒否)から始め、必要な経路だけを段階的に許可して、fanclub-api の通信を最小限に閉じ込めます。CKAD ドメイン D5 の要です。
動作確認バージョン:kind v0.31.0(K8s v1.35.0)/ kubectl v1.35.6 / fanclub-backend:0.3.2(再ビルドなし)/ Calico v3.32.0(NetworkPolicy をエンフォース)/ 疎通確認は Frontend Pod 内蔵の wget(busybox)(2026-07-03 実機検証時点)
今ここマップ(全 19 回中の現在地)
現在地は第 5 部「セキュリティ基礎」の第 16 回、第 5 部の締めくくりです。前回の「守り」に、通信経路の「絞り込み」を足します。
- 第 1 部 コンテナと Docker(第 1〜4 回)
- 第 2 部 Kubernetes 基礎(第 5〜6 回)
- 第 3 部 アプリリソース(第 7〜11 回)
- 第 4 部 ワークロード戦略(第 12〜14 回)
- 第 5 部 セキュリティ基礎(第 15〜16 回)← 今ここ
- 第 6 部 パッケージ管理と HTTPS 公開(第 17〜19 回)
この回のゴール
- NetworkPolicy で Pod 間通信(Ingress / Egress)をホワイトリスト制御できる。
- default-deny-all のベースラインから、必要な経路だけを段階的に許可して 3 層構成を閉じ込められる。
- Egress を絞ると DNS が壊れる落とし穴を理解し、
allow-dnsで回避できる。
NetworkPolicy とは(どの Pod と通信できるか)
NetworkPolicy は、Pod 単位で「入ってくる通信(Ingress)」と「出ていく通信(Egress)」を、送信元・宛先・ポート(L3/L4)でホワイトリスト制御する仕組みです。ポイントは 2 つです。
- ポリシーが 1 つも無い Pod は「全許可」。既定では、どの Pod もどことでも通信できます。
- ある Pod を
podSelectorで対象にするポリシーを 1 つでも適用すると、その Pod は「許可したもの以外は拒否(default-deny)」に切り替わる。許可を明示的に足していく方式です。
重要な前提として、NetworkPolicy を実際に効かせる(エンフォースする)のは CNI プラグインです。本シリーズは第5回で Calico v3.32.0 を導入済みで、これが NetworkPolicy を確実にエンフォースします(kind 既定の CNI は NetworkPolicy のエンフォースが不確実なため、本シリーズでは第5回から Calico を採用しています。CKAD 本番試験や第2巻の kubeadm 環境も NetworkPolicy をエンフォースする CNI 前提です)。
fanclub-api は Frontend(Nginx)→ Backend(Payara)→ DB(PostgreSQL)の 3 層です。目標は「隣り合う層だけが通信でき、それ以外は塞ぐ」状態です。まず現状のラベルを確認します。NetworkPolicy の podSelector はこのラベルで対象を指定します。実行コマンド:
$ kubectl get pods -L app
実行結果(例):各 Pod に app ラベル(fanclub-frontend / fanclub-backend / fanclub-db)が付いています。これを selector に使います。第11回で入れたログ収集の fanclub-logcollector も同じ default にいますが、本回は 3 層の通信制御に集中します。
NAME READY STATUS RESTARTS AGE APP
fanclub-backend-6c54b488d5-j4g6j 2/2 Running 0 9h fanclub-backend
fanclub-db-0 1/1 Running 0 5d fanclub-db
fanclub-frontend 1/1 Running 0 5d fanclub-frontend
fanclub-logcollector-km8bb 1/1 Running 0 5d fanclub-logcollector

default-deny から始める
ホワイトリストの土台として、まず「全部拒否」のベースラインを敷きます。podSelector: {}(空=Namespace 内の全 Pod が対象)で、Ingress と Egress の両方を対象にします。なお本回で作る NetworkPolicy マニフェスト(この後の np-*.yaml 計 4 ファイル)は、いずれも developer のホームに一般ユーザーで作成します(kubectl apply はクラスタへ送るだけなので root は不要)。まずは全拒否のベースライン np-default-deny.yaml です。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: default
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
ingress / egress のルールを 1 つも書かないので、「許可ゼロ=全拒否」になります。適用し、Frontend から Backend への通信を試します。Frontend Pod(Nginx)には wget(busybox 由来)が入っているので、そこから確認します。Frontend の Pod 名を変数 $FE に入れておき、以降のコマンドでも同じターミナルで使い回します。実行コマンド:
$ kubectl apply -f np-default-deny.yaml
$ FE=$(kubectl get pod -l app=fanclub-frontend -o name | head -1)
$ kubectl exec "$FE" -- wget -q -O- --timeout=5 \
http://fanclub-backend:8080/health/ready
実行結果(例):通信できず、しかもbad address(名前解決の失敗)で止まります。
wget: bad address 'fanclub-backend:8080'
ここが最初の勘所です。default-deny-all は Egress(出ていく通信)も止めたため、Frontend が DNS サーバー(CoreDNS)へ問い合わせる通信まで塞がれ、fanclub-backend という名前を IP に解決できなくなりました。「接続が拒否された」より手前の、名前解決の段階で失敗しているわけです。次の節でこれを直します。
Egress と DNS の落とし穴
Egress を絞ったときに陥りやすいのが、このDNS の落とし穴です。Pod はサービス名でアクセスするたびに DNS(kube-system の CoreDNS・53 番ポート)へ問い合わせます。Egress を default-deny にした時点で、この問い合わせも拒否されます。そこで、全 Pod に「CoreDNS 宛の 53 番だけは許可」を足します。次を np-allow-dns.yaml として作成します。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: default
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
namespaceSelector(kube-system)と podSelector(k8s-app: kube-dns)の両方を 1 つの to 要素にまとめている点に注目してください。こうすると「kube-system かつ CoreDNS の Pod」という AND 条件になります(別々の - に分けると OR になり、範囲が広がりすぎます)。なお kubernetes.io/metadata.name ラベルは Kubernetes が全 Namespace に自動付与するものなので、自分で付け直す必要はありません。CoreDNS 側の k8s-app: kube-dns ラベルは環境によって異なることがあるため、kubectl get pod -n kube-system --show-labels で実値を確認しておくと確実です(kind は k8s-app: kube-dns)。適用して、もう一度 Backend にアクセスします。実行コマンド:
$ kubectl apply -f np-allow-dns.yaml
$ kubectl exec "$FE" -- wget -q -O- --timeout=5 \
http://fanclub-backend:8080/health/ready
実行結果(例):エラーが bad address から download timed out(タイムアウト)に変わりました。
wget: download timed out
変化が意味を持ちます。名前解決は成功する(=DNS は通った)ようになり、その先の Backend への接続で止まっている状態です。DNS の落とし穴は解消し、あとは通したい経路を許可するだけになりました。

段階的に許可する(3 層ホワイトリスト)
ここが本回で最も大切な考え方です。default-deny-all は Ingress と Egress の両方を止めているので、1 つの経路を通すには「宛先側の Ingress 許可」と「送信元側の Egress 許可」の両方が要ります。片方だけでは通りません。まず Frontend → Backend を開けます。次を np-allow-backend.yaml として作成します。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-from-frontend
namespace: default
spec:
podSelector:
matchLabels:
app: fanclub-backend
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels:
app: fanclub-frontend
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-frontend-to-backend
namespace: default
spec:
podSelector:
matchLabels:
app: fanclub-frontend
policyTypes: ["Egress"]
egress:
- to:
- podSelector:
matchLabels:
app: fanclub-backend
ports:
- protocol: TCP
port: 8080
1 枚目が「Backend は Frontend からの 8080 を受け入れる(Ingress)」、2 枚目が「Frontend は Backend の 8080 へ出てよい(Egress)」です。適用して確認します。実行コマンド:
$ kubectl apply -f np-allow-backend.yaml
$ kubectl exec "$FE" -- wget -q -O- --timeout=5 \
http://fanclub-backend:8080/health/ready
実行結果(例):UP が返り、Frontend → Backend が通りました。
{"status":"UP","checks":[{"name":"fanclub-api-ready","status":"UP","data":{}}]}
補足:Service(ClusterIP)経由でも NetworkPolicy が効く理由:fanclub-backend は ClusterIP の Service なので、Frontend は Pod へ直接ではなく Service 宛てに通信します。それでも podSelector のポリシーが効くのは、NetworkPolicy が対象にするのが Service ではなく Pod(の IP)だからです。Frontend が Service 名でアクセスすると、DNS で ClusterIP を得たあと、kube-proxy が ClusterIP を実際の Backend Pod の IP に変換(DNAT)します。この変換は Calico がポリシーを判定するより手前で起きるため、Calico は変換後の Pod IPで可否を決めます。結果、Egress 許可(to podSelector: app=fanclub-backend)は変換後の Backend Pod に、Ingress 許可(from podSelector: app=fanclub-frontend)は送信元の Frontend Pod にマッチします。to/from に Service を書けず必ず Pod をラベルで選ぶのは、このためです。

同じ要領で Backend → DB(PostgreSQL・5432)も開けます。次を np-allow-db.yaml として作成します。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-db-from-backend
namespace: default
spec:
podSelector:
matchLabels:
app: fanclub-db
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels:
app: fanclub-backend
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-backend-to-db
namespace: default
spec:
podSelector:
matchLabels:
app: fanclub-backend
policyTypes: ["Egress"]
egress:
- to:
- podSelector:
matchLabels:
app: fanclub-db
ports:
- protocol: TCP
port: 5432
適用して、今度はアプリ全体(Frontend → Backend → DB)が通るかを、DB 参照を伴う /api/members で確認します。実行コマンド:
$ kubectl apply -f np-allow-db.yaml
$ kubectl exec "$FE" -- wget -q -O- --timeout=6 \
http://fanclub-backend:8080/api/members
実行結果(例):会員一覧の JSON が返り、3 層すべてが必要な経路だけで通信できています。
[{"createdAt":"2026-06-27T10:28:41.933973","email":"taro@example.com","id":1,"name":"山田太郎","plan":"premium"}, ...]
これで default-deny-all + allow-dns +(Frontend↔Backend の 2 枚)+(Backend↔DB の 2 枚)の計 6 枚で、隣接層だけが通信できる状態になりました。外部(ブラウザ)→ Frontend を通す allow-frontend-from-gateway は、Gateway API を導入する第18回で追加し、4 層のホワイトリストが完成します(今回はまだ Gateway が無いので追加しません)。
適用順序のベストプラクティス
本回では学習のため deny を先に入れて「壊れる様子」を観察しましたが、本番稼働中のクラスタでは順序が逆です。default-deny を先に入れると、許可を足し終えるまでの間、通信が止まるダウンタイムの窓が生まれます。したがって本番では次の順序が安全です。
- 先に
allow-dnsと各allow-*(必要な経路の Ingress / Egress ペア)をすべて適用する。 - 通信が維持されていることを確認する。
- 最後に
default-deny-allを適用して、許可していない通信だけを閉じる。
「許可を先に、拒否を最後に」——これでダウンタイムなしにホワイトリストへ移行できます。
やってみよう
default-deny-allを適用し、Frontend からwget http://fanclub-backend:8080/health/readyがbad addressで失敗する(DNS も止まる)ことを観察する。allow-dnsを適用し、エラーがdownload timed outに変わる(名前解決は復旧・接続は未許可)ことを確認する。allow-backend-from-frontend+allow-egress-frontend-to-backendを適用し、UPが返ることを確認する(Ingress と Egress の両方が要る点を確かめる)。allow-db-from-backend+allow-egress-backend-to-dbを適用し、/api/membersが会員 JSON を返す(3 層が通る)ことを確認する。
理解度チェック
次の各文が正しいか(○)誤りか(×)を判断してください。解答は下にまとめています。
- NetworkPolicy が 1 つも無い Namespace では、Pod 間通信はすべて許可される。
- ある Pod を
podSelectorの対象にするポリシーを 1 つ適用すると、その Pod は許可した通信以外が拒否される。 - Egress を default-deny にした状態では、宛先側に Ingress 許可があれば、送信元側に Egress 許可が無くても通信できる。
- Egress を絞ると、53 番(DNS)への許可を入れない限りサービス名での名前解決が失敗する。
podSelector: {}(空)は、その Namespace 内の全 Pod を対象にする。- NetworkPolicy を実際にエンフォースするのは CNI で、Calico は対応している。
- 本番稼働中のクラスタでは、
default-deny-allを先に適用してから許可ルールを足すのが安全な順序である。 allow-dnsの宛先はkube-systemの CoreDNS(k8s-app: kube-dns)で、ポートは 53(UDP / TCP)である。
解答
- 1. ○:ポリシー不在の Pod は全許可。
- 2. ○:対象になった時点で default-deny 化し、許可を明示した通信だけ通る。
- 3. ×:default-deny は Ingress・Egress の両方を止めるため、送信元の Egress 許可と宛先の Ingress 許可の両方が要る。
- 4. ○:DNS 問い合わせも Egress。53 番を許可しないと名前解決が落ちる。
- 5. ○:空の
podSelectorは Namespace 内の全 Pod が対象。 - 6. ○:エンフォースは CNI の役割。本シリーズは Calico(第5回導入)。
- 7. ×:逆。許可を先に入れ、default-deny は最後にするとダウンタイムを避けられる。
- 8. ○:CoreDNS(
k8s-app: kube-dns)宛の 53(UDP / TCP)を許可する。
まとめ
本記事では、NetworkPolicy で Pod 間通信をホワイトリスト制御しました。default-deny-all で全拒否のベースラインを敷き(Ingress・Egress 両方)、Egress を絞ると DNS が壊れる落とし穴を allow-dns(CoreDNS 宛 53)で回避し、1 経路=宛先 Ingress 許可+送信元 Egress 許可のペアという原則で Frontend↔Backend・Backend↔DB を段階的に開けて、3 層を必要な経路だけに閉じ込めました。本番では「許可を先に・拒否を最後に」でダウンタイムを避けます。外部→Frontend の許可は Gateway 導入の第18回で足して 4 層が完成します。
第 5 部を通じて、fanclub-api は RBAC(最小権限の ServiceAccount で「何をできるか」を絞る・第15回)・SecurityContext(非 root・読み取り専用ルートFS・capability 剥奪で「どう動くか」を固める・第15回)・NetworkPolicy(default-deny から必要な経路だけ許可して「誰と通信できるか」を絞る・第16回)の 3 層で守られました。権限・実行環境・通信をそれぞれ最小化する多層防御が、これでアプリの土台になります。次の第 6 部では、この状態のアプリをパッケージ化して公開まで進めます。これで第 5 部「セキュリティ基礎」は終わりです。
次回予告
次回(第17回)からは第 6 部「パッケージ管理と HTTPS 公開」に入ります。ここまで手で kubectl apply してきた大量のマニフェストを、Helm v4 で 1 つの chart にまとめ、install / upgrade / rollback のフルサイクルを体験します。fanclub-api を「パッケージ」として扱えるようになります。
シリーズ一覧
第1部:コンテナと Docker
- 第1回 コンテナ技術概念 + Docker 環境準備
- 第2回 Docker 基本操作
- 第3回 Dockerfile + マルチステージ + JDK 25 / Payara Micro イメージビルド
- 第4回 コンテナレジストリ + イメージタグ戦略 + Trivy スキャン
第2部:Kubernetes 基礎
第3部:アプリリソース
- 第7回 Pod + Multi-container パターン
- 第8回 Service とネットワーキング
- 第9回 ストレージ(PVC + StatefulSet)+ PostgreSQL DB 追加
- 第10回 ConfigMap + Secret + ServiceAccount 基礎
- 第11回 Job + CronJob + DaemonSet
第4部:ワークロード戦略
- 第12回 Deployment + 3 Probe + Rolling Update + Probe デバッグ実践
- 第13回 Deployment 戦略補完(Blue/Green + Canary + Recreate)
- 第14回 ResourceQuota + LimitRange + Multi-tenant Namespace
第5部:セキュリティ基礎
- 第15回 RBAC + SecurityContext + Admission Controller 概念 + CRD 利用
- 第16回 NetworkPolicy 基礎 ← 今ここ
