新卒インフラエンジニア向け 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-demo | CoreDNS 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 全体の Pod | Namespace 間通信の許可(prod → monitoring) |
namespaceSelector + podSelector(AND) | 指定 Namespace 内の指定 Pod のみ | 厳格な跨 Namespace 制御 |
ipBlock | 外部 CIDR(Kubernetes ノード外) | 外部 API・データベースへの Egress |
AND / OR 結合ルール — インデントで決まる最頻出ミス
namespaceSelector と podSelector を組み合わせる際、インデントの位置によって AND(積集合)か OR(和集合)かが変わります。これは CKA 試験でも現場でも頻出のミスポイントです。
同一ブロック内に並べる → AND(積集合)
from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: prod
podSelector:
matchLabels:
app: frontend
上記は「prod Namespace の中の app=frontend Pod」のみ許可します。namespaceSelector と podSelector が同一の - エントリ(同一インデントブロック)に属しているため 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 の明示が重要な理由

policyTypes を省略すると、次の挙動になります。
- Ingress: 常にポリシーが適用される(
ingressフィールドの有無に関わらず) - Egress:
egressフィールドが存在する場合のみ適用される
この非対称な挙動が原因で「Egress を制御したつもりが制御されていない」という状況が発生します。policyTypes は必ず明示する習慣をつけてください。
また、Pod が複数の NetworkPolicy に選択された場合、すべてのポリシーの和集合(OR)が適用されます。1 つのポリシーで通信を許可すれば、他のポリシーで遮断していても通信は通ります。
default-deny パターン 4 種類
NetworkPolicy の「default-deny」パターンには状況に応じた 4 種類があります。用途と特性を把握して使い分けてください。
| パターン | policyTypes | ingress / 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.32 | Cilium 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-demoNamespace に 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 out や could 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 問)
各問に対して○か×で答え、解説を確認してください。
| # | 問題文 | 正解 | 解説 |
|---|---|---|---|
| Q1 | NetworkPolicy を 1 つも作成していない Pod は、同 Namespace の任意の Pod からの通信をすべて受け入れる | ○ | non-isolated 状態。ポリシーが存在しない Pod は全通信許可 |
| Q2 | policyTypes: ["Ingress"] を指定すると、Egress トラフィックもブロックされる | × | policyTypes に Egress が含まれない場合、Egress は非制限のまま |
| Q3 | from 配列内の同一ブロックに namespaceSelector と podSelector を並べると、OR(和集合)として評価される | × | 同一ブロック内は AND(積集合)。OR にするには別ブロックに分ける |
| Q4 | CoreDNS への Egress 許可では、UDP 53 だけでなく TCP 53 も許可する必要がある | ○ | DNS レスポンスが 512 バイトを超える場合 TCP にフォールバックするため |
| Q5 | Calico v3.32 は HTTP メソッドやパスでフィルタする L7 NetworkPolicy をサポートしている | × | L7 Policy は Cilium の標準機能。Calico OSS v3.32(本シリーズで使用)は L4 まで。Calico Enterprise(商用版)には L7 ポリシーがある |
| Q6 | Cilium の Hubble は eBPF を使い、サイドカーなしで L7 レベルのトラフィックを可視化できる | ○ | Hubble は eBPF データパスに乗った L3-L7 リアルタイム可視化ツール。サイドカー不要 |
| Q7 | podSelector: {} を指定した NetworkPolicy は、同じ Namespace 内の全 Pod に適用される | ○ | podSelector が空({})の場合は Namespace 全体の Pod が対象になる |
シリーズ一覧
第1部:クラスタ構築
- 第1回 第2巻スコープ + CKA 試験形式紹介 + kubeadm 概要 + kubeadm vs RKE2/k0s/OKD 概観
- 第2回 kubeadm シングルノード起動(pkgs.k8s.io + kubeadm init + Calico CNI)+ alma-proxy whitelist 構築
- 第3回 kubeadm HA 設計 + HAProxy LB(API Server LB 構成)
- 第4回 kubeadm HA クラスタ構築(CP×3 + WL×2)+ fanclub-api HA 移行
- 第5回 etcd backup/restore(etcdutl)+ ノード drain/uncordon
第2部:ワークロード管理
- 第6回 kubeadm upgrade + ノード drain/cordon + ミニトラブルシュート演習
- 第7回 Pod スケジューリング(taint/toleration/affinity/anti-affinity/PriorityClass)
- 第8回 HPA + ResourceQuota + LimitRange
第3部:ネットワーク
- 第9回 Service 詳細 + MetalLB(LoadBalancer 動作確認)
- 第10回 NetworkPolicy 詳細 + Calico/Cilium 比較 + ミニトラブルシュート演習 ← 今ここ
- 第11回 Gateway API + Traefik + cert-manager + CoreDNS(HTTPS 公開完成)
第4部:ストレージ
第5部:監視・運用
- 第13回 Prometheus + Grafana + Loki + Fluent Bit + fanclub-api 監視ダッシュボード
- 第14回 Helm + Kustomize でクラスタコンポーネント install + ArgoCD GitOps + Velero backup
