Contents
NetworkPolicyの基本概念と前提条件
NetworkPolicyの基本動作と検証時の前提をまとめます。podSelectorやnamespaceSelector、policyTypesの挙動とCNI依存性、hostNetwork/HostPortの注意点を実務視点で解説します。
基本概念(Pod/Namespaceセレクタ、ポート、プロトコル、ingress/egress)
NetworkPolicyはL3/L4でPod間通信を制御するリソースです。podSelectorは同一Namespaceで評価され、空にするとそのNamespace内の全Podを選択します。namespaceSelectorはPeerとして別Namespaceを指定する際に使います。portsはTCP/UDP/SCTPを指定でき、標準のNetworkPolicyはL7(HTTPパス等)やICMPを直接扱いません。
デフォルト挙動と前提条件(policyTypes、CNIサポート)
NetworkPolicyはAPI上の宣言であり、実際の遮断はCNIプラグインが実装します。Podがどれか一つでもPolicyに選択されると、policyTypesに含まれる方向で明示的に許可されていない通信は拒否されます。policyTypesは明示的に指定することを推奨します。hostNetwork/HostPortの挙動やDaemonSetのノードローカル性はCNI実装に左右されます。
API互換性とKubernetesバージョン
マニフェストで使うapiVersionはクラスタごとに差があります。まずはクラスタで networking.k8s.io/v1 が利用可能か確認してください。下記コマンドで確認できます。
|
1 2 3 |
kubectl api-versions | grep networking.k8s.io kubectl explain networkpolicy --api-version=networking.k8s.io/v1 |
多くの近年のクラスタは networking.k8s.io/v1 をサポートしますが、古いクラスタでは beta 系の API や別の group/version の可能性があります。古いクラスタで差異がある場合は manifest の apiVersion を合わせる、あるいはクラスタのリリースノートで deprecation 情報を確認してください。
検証準備:クラスタとCNIの状態確認
検証前にクラスタとCNIの状態を確実に把握します。ここで事前に問題を潰しておくと、後の切り分けが早くなります。
クラスタとCNIの状態確認コマンド例
まずは利用可能なAPIとCNIの存在を確認します。以下は堅牢に情報を取得するための例です。各コマンドの出力を保存して調査に使ってください。
- Kubernetes バージョンとノード情報を取得するコマンド:
- kubectl version --short
-
kubectl get nodes -o wide
-
networking.k8s.io API の有無を確認:
-
kubectl api-versions | grep networking.k8s.io
-
kube-system の DaemonSet / Pod を列挙して CNI を特定する例:
- kubectl get ds,deploy,po -n kube-system -o wide
-
kubectl get daemonset -n kube-system -o custom-columns=NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image
-
特定のCNI Podをラベルで探す(環境依存なのでラベル名は要調整):
- kubectl get pods -n kube-system -l k8s-app=calico-node -o wide
-
kubectl get pods -n kube-system -l k8s-app=cilium -o wide
-
CNI Podのログ確認:
-
kubectl logs -n kube-system
-c -
ノード上のCNI設定ファイル(ノードSSHが可能な場合):
- sudo ls /etc/cni/net.d
CNI名やラベルは環境差が大きいので、単純な文字列マッチよりDaemonSetやPodのイメージ名、ownerReferencesで特定する方法が堅牢です。
権限・必須ツールと事前チェック
実行に必要な権限と検証用ツールを揃えてください。主な項目は次の通りです。
- RBAC: NetworkPolicyやPodの作成、exec の権限(例: kubectl auth can-i create networkpolicy --namespace=
) - kubectl(kubeconfig が設定済み)
- CNI固有ツール(必要に応じて): calicoctl、cilium CLI/hubble
- デバッグ用イメージ: nicolaka/netshoot、curlimages/curl、infoblox/dnsutils、iperf3
- ノードツール(読み取り専用推奨): iptables/nft、conntrack、bpftool、tcpdump(ノードSSH権限が必要)
- クラウド環境固有の確認:GKE等はアドオンやCalicoの導入状況で機能が変わるため、クラウドのドキュメントとリリースノートを参照してください(例: https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy)。
ノードやCNIに対する操作は影響範囲が大きいので、事前承認とバックアウト手順を用意してください。
検証マトリクスとテストPodによる接続確認
検証は再現性と判定の明確さが重要です。ここでは網羅的にケースを設計し、DNS周りやService経由とPodIP直叩きの差も検証します。
検証マトリクスの設計(Ingress/Egress/ポート/プロトコル/名前空間等)
検証マトリクスは期待結果を明確にするために作成します。DNS(UDP/TCP 53)やFQDNベースの検証はEgress制限で失敗することがある点に注意してください。
| ケース | 方向 | 送信元 | 宛先 | ポート | プロトコル | 期待結果 |
|---|---|---|---|---|---|---|
| 1 | Ingress | 同一NamespaceのPod | 試験Pod | 80 | TCP | Allow |
| 2 | Ingress | 別NamespaceのPod | 試験Pod | 80 | TCP | Allow/Denied(Policy次第) |
| 3 | Egress | 試験Pod | 外部IP | 443 | TCP | Allow/Denied |
| 4 | Ingress | Service ClusterIP経由 | バックエンドPod | 80 | TCP | PodレベルのPolicyが適用されるか確認 |
| 5 | Egress | 試験Pod | クラスタDNS (UDP/TCP 53) | 53 | UDP/TCP | DNSがブロックされるとFQDN検証は失敗 |
| 6 | Egress | 試験Pod | バックエンド PodIP | 80 | TCP | PodIP直叩きでパスが通るか確認 |
この表を基に自動化スクリプトを作成してください。
テストPodの作り方と接続確認の具体コマンド例
テストはephemeral Podや既存のデバッグPodで行うと便利です。FQDNベースのテストはDNSが通ることが前提です。EgressルールでDNSがブロックされると名前解決ができず失敗するため、DNS(UDP/TCP 53)の許可ルール例か、Pod IPによる検証を用意してください。
- FQDNでのHTTPアクセス確認(ephemeral Podを使う例)
- kubectl run --rm --image=curlimages/curl --restart=Never curl-test -n np-a -- sh -c 'curl -sS -m 5 http://backend.np-b.svc.cluster.local:80'
- Pod IP での確認
- PODIP=$(kubectl get pod -n np-b -l app=backend -o jsonpath='{.items[0].status.podIP}')
- kubectl run --rm --image=curlimages/curl --restart=Never curl-test -n np-a -- sh -c "curl -sS -m 5 http://$PODIP:80"
- DNS の確認(dig/nslookup が使える場合)
- kubectl run --rm --image=nicolaka/netshoot --restart=Never -n np-a -- dig +short backend.np-b.svc.cluster.local
- Pod/Namespace のラベル確認
- kubectl get pods -n
--show-labels - kubectl get ns --show-labels
DNSブロックでFQDNが解決できない時は、まず DNS (UDP/TCP 53) を許可するPolicyを追加するか、PodIP直接での検証を行ってください。
代表的NetworkPolicyマニフェスト例と適用→検証フロー
よく使う代表パターンを示します。例では namespaceSelector のラベル依存に注意し、必要なら Namespace にラベル付与を行う手順を含めます。
テスト用リソース(Namespace/Deployment/Service/Pod)
以下は検証用の最小構成です。Namespaceには明示的にラベルを付けています。実運用ではラベル名を組織ルールに合わせてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
apiVersion: v1 kind: Namespace metadata: name: np-a labels: name: np-a --- apiVersion: v1 kind: Namespace metadata: name: np-b labels: name: np-b --- apiVersion: apps/v1 kind: Deployment metadata: name: backend namespace: np-b spec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: nginx:stable ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: backend namespace: np-b spec: selector: app: backend ports: - port: 80 targetPort: 80 --- apiVersion: v1 kind: Pod metadata: name: debug-client namespace: np-a labels: app: debug-client spec: containers: - name: netshoot image: nicolaka/netshoot command: ["/bin/sleep", "3600"] |
NamespaceSelectorでラベルを参照するPolicyを使う場合、対象Namespaceにラベルが付与されていることを確認してください。ラベルがない場合は次のように付与します。
|
1 2 3 4 |
kubectl label namespace np-a name=np-a --overwrite kubectl label namespace np-b name=np-b --overwrite kubectl label namespace kube-system name=kube-system --overwrite |
deny-all(完全隔離)マニフェスト
対象Namespace内の全Podを両方向で遮断する例です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: deny-all namespace: np-b spec: podSelector: {} policyTypes: - Ingress - Egress ingress: [] egress: [] |
名前空間間許可(np-a から np-b の backend への許可)
namespaceSelector を使う場合、Namespaceラベルに依存します。ラベル付与を忘れないでください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-from-ns-a namespace: np-b spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: np-a ports: - protocol: TCP port: 80 |
クライアント側でDNSを許可する例(np-a)
Egress を制限する場合はDNS(UDP/TCP 53)も許可する必要があります。以下は np-a の Pod が kube-system の DNS に接続できるようにする例です。事前に kube-system にラベルを付けてください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: np-a spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 53 |
適用から検証、期待結果までのワークフロー
まずはリソースをデプロイしてからPolicyを適用し、マトリクスに従ってテストします。一般的な手順は次の通りです。
- テスト用NamespaceとPodを作成する。
- 必要なNamespaceラベルを付与する(namespaceSelector を使う場合)。
- マニフェストを適用する。
- Podの起動待ちを行い、FQDN と PodIP 両方で接続を確認する。
- 期待と異なる場合は CNI ログ、iptables/BPF、tcpdump の順で原因を切り分ける。
- 修正後に同一マトリクスで再確認する。
CNI別デバッグと低レイヤ診断(実務的な切り分け手順)
CNIごとに実装や可視化手段が異なります。標準NetworkPolicyはL3/L4のみのため、L7制御やFQDN制御が必要な場合はCNIの拡張機能を利用する必要があります。
CNI別のチェックポイントと専用ツール(Calico、Cilium、その他)
各CNIの主要ポイントと代表的コマンドを示します。
- Calico
- カーネルレベルのルールはFelixが管理します。ログは calico-node コンテナで確認してください。
- calicoctl が使える環境ならポリシーや状態の照会が便利です。
-
Calico の FQDN ベースの拡張やエンタープライズ機能はバージョン依存です。ドキュメントで確認してください。
-
Cilium
- cilium CLI と Hubble によるフロー可視化が強力です(cilium status, cilium endpoint list, cilium monitor, hubble observe)。
-
Cilium の拡張ポリシー(CiliumNetworkPolicy)は L7 制御をサポートします。BPF レベルでの可視化や sysdump が取得できます。
-
その他(Weave、Flannel、kube-router など)
- 実装は多様で、NetworkPolicy 未対応または限定的な場合があります。daemon のログとドキュメントを確認してください。
標準 NetworkPolicy は L7 をサポートしません。L7/FQDN の制御が必要な場合は、Cilium や Calico の拡張を検討してください。各CNIで実装方法や制約が異なるため、必ずベンダーのドキュメントを参照してください。
低レイヤ診断(iptables/nftables、BPFマップ、conntrack、tc、tcpdump等)
低レイヤは「読み取り中心」で切り分けを行います。危険な変更は避けてください。ここでは安全に行える確認コマンドと禁止事項を示します。
- 読み取り用コマンド例:
- sudo iptables-save | grep -E 'cali|CILIUM|cilium|CALICO'
- sudo iptables -L -n -v
- sudo nft list ruleset
- sudo conntrack -L | grep
- sudo bpftool map show
- sudo bpftool prog list
-
sudo tcpdump -n -i any host
and port -
ipvs / kube-proxy の確認:
- sudo ipvsadm -Ln
-
kubectl -n kube-system get daemonset kube-proxy -o yaml
-
禁止・注意事項(明確に守ること)
- ノード全体に影響する破壊的操作を行わない(例: iptables -F, iptables -X, nft delete ruleset, conntrack -F)。
- 影響範囲が限定される操作でも、事前に影響試算と承認を得ること。
- 必要であれば特定フローだけを削除する(例: sudo conntrack -D -s
)等の限定的操作を検討する。
低レイヤ診断はCNI固有の構造を理解してから行うと効率的です。ログとキャプチャを保存して解析に使ってください。
Service経由とPodIP直叩きの切り分け(kube-proxy iptables / ipvs の違い)
Service経由とPodIP直叩きでルーティング経路が異なることがあります。特に kube-proxy のモード(iptables / ipvs)で動作が変わります。
- モード確認:
- kubectl -n kube-system get ds kube-proxy -o yaml で起動オプションを確認
-
ノード上で sudo ipvsadm -Ln を実行し、IPVS テーブルが存在すれば ipvs モード
-
典型的な差:
- iptables モードでは ClusterIP へは DNAT がかかり、iptables チェーンにルールが現れます。
- ipvs モードでは ipvs テーブルで仮想サービスが管理され、パケットの扱いが異なるため、Policy の適用箇所やログの出どころが変わることがあります。
切り分けは、ノードの tcpdump と Pod 内の tcpdump を比較し、DNAT の有無やパスの差を確認してください。
特殊ケース:hostNetwork / HostPort / ノードローカルDaemonSet の扱いと検証方法
hostNetwork を使う Pod はノードのネットワーク名前空間で動くため、NetworkPolicy の適用が期待どおりにならない場合があります。HostPort やノードローカルなDaemonSetも同様に挙動がCNI依存です。検証のポイントは次の通りです。
- hostNetwork Pod のトラフィックがどの時点でフィルタされるかを、ノード上の tcpdump と Pod内の tcpdump で比較する。
- HostPort はノードのポートを使うため、ノードのファイアウォールや kube-proxy の設定が影響する。
- 回避策としては、アプリをCluster-IPやNodePort越しにアクセスさせる、あるいはクラスタ内プロキシを経由させる方法がある。
CI自動化・運用ベストプラクティス、よくある誤り
運用では自動化と明確な前提確認が重要です。ここでは優先的な切り分けフローとよくある誤りの実例を示します。
CI組込みと運用フロー(テストの自動化、監視、段階的導入)
検証をCIに組み込む際の高レベルな流れは次の通りです。
- テスト用クラスタ/Namespace を用意する(kind/k3d 等で短命環境)
- マニフェストをデプロイし、前提チェックを実行する(API・CNI・ラベル等)
- 検証スクリプトでマトリクス全ケースを実行し、明示的に成功/失敗を判定する
- 結果をアーティファクトとして保存し、問題発生時はログ・pcapを参照する
- 段階的に本番に導入し、監視(CNIのドロップメトリクス等)を設置する
よくある誤り・実例のトラブルシュート
代表的な誤りと対応例を示します。
- ラベルミスでPolicyがマッチしない
- 再現: NamespaceBのServiceにアクセス不可
- 原因: podSelector/namespaceSelectorのラベル名がPodやNamespaceと一致していない
-
対応: kubectl get pods -n ns-b --show-labels で確認し、ラベルまたはPolicyを修正する
-
Egress制限でFQDNが失敗する(DNSブロック)
- 再現: FQDNでアクセスできないが PodIP 直叩きは通る
- 原因: クライアント側のEgressで UDP/TCP 53 が許可されていないため名前解決が失敗している
-
対応: DNS を許可するPolicyを追加するか、検証は PodIP で行う
-
testスクリプトで待ちの扱いが甘い
- 再現: kubectl wait に '|| true' を付けているため準備不足でも先に進む
- 対応: 待ち処理は明示的なタイムアウトとリトライで実装し、失敗時は明確に異常終了させる
付録:コピーして使えるテスト用マニフェストと堅牢な検証スクリプト例
ここでは前節のマニフェストと、DNSの落とし穴に対応した堅牢なスクリプト例を示します。実行前に内容を確認し、環境に合わせて調整してください。
マニフェスト一覧(主なもの)
- test-resources.yaml(前出)
- deny-all.yaml(np-b 全隔離、前出)
- allow-from-ns-a.yaml(np-b の backend に対して np-a からの TCP:80 を許可、前出)
- allow-dns.yaml(np-a から kube-system の DNS に対する UDP/TCP:53 を許可、前出)
- client-restrict-egress.yaml(np-a から np-b:80 のみを許可する例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# client-restrict-egress.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: restrict-egress-to-ns-b namespace: np-a spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: name: np-b ports: - protocol: TCP port: 80 |
このPolicyは np-a から np-b の TCP:80 のみを許可します。DNS が許可されていないため FQDN による検証は失敗します。この例で DNS の重要性を確認してください。
test-script.sh(堅牢版の例)
検証スクリプトは待ち・判定・エラーハンドリングを明確にします。以下は一例です。実行前に実行権限を付与してください(chmod +x test-script.sh)。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#!/usr/bin/env bash set -euo pipefail NAMESPACE_A=np-a NAMESPACE_B=np-b TIMEOUT=120 info() { echo "[INFO] $*"; } err() { echo "[ERROR] $*" >&2; } wait_for_deployment() { ns="$1"; deploy="$2"; timeout="$3" info "Waiting for deployment ${deploy} in ${ns} (timeout ${timeout}s)" end=$((SECONDS + timeout)) while [ $SECONDS -lt $end ]; do if kubectl -n "$ns" get deploy "$deploy" >/dev/null 2>&1; then desired=$(kubectl -n "$ns" get deploy "$deploy" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo 1) available=$(kubectl -n "$ns" get deploy "$deploy" -o jsonpath='{.status.availableReplicas}' 2>/dev/null || echo 0) if [ -n "$desired" ] && [ "$available" = "$desired" ]; then info "Deployment ${deploy} is ready" return 0 fi fi sleep 2 done err "Timeout waiting for deployment ${deploy}" return 1 } run_curl() { ns="$1"; url="$2" # ephemeral pod to run curl; capture exit code set +e kubectl run --rm --image=curlimages/curl --restart=Never curl-test -n "$ns" -- sh -c "curl -sS -m 5 '$url'" rc=$? set -e return $rc } expect_ok() { ns="$1"; url="$2" if run_curl "$ns" "$url"; then info "OK: $url" else err "EXPECTED OK but failed: $url" exit 2 fi } expect_fail() { ns="$1"; url="$2" if run_curl "$ns" "$url"; then err "UNEXPECTED OK: $url" exit 3 else info "Expected fail: $url" fi } info "1. Apply test resources" kubectl apply -f test-resources.yaml info "2. Ensure namespace labels exist for namespaceSelector usage" kubectl label namespace "$NAMESPACE_A" name="$NAMESPACE_A" --overwrite kubectl label namespace "$NAMESPACE_B" name="$NAMESPACE_B" --overwrite kubectl label namespace kube-system name=kube-system --overwrite || true info "3. Wait for backend ready" wait_for_deployment "$NAMESPACE_B" backend "$TIMEOUT" info "4. Smoke test (FQDN) before any policy (should succeed)" expect_ok "$NAMESPACE_A" "http://backend.${NAMESPACE_B}.svc.cluster.local:80" info "5. Apply client egress restriction (np-a -> np-b:80 only, DNS blocked)" kubectl apply -f client-restrict-egress.yaml info "6. Test FQDN (should fail due to DNS being blocked)" expect_fail "$NAMESPACE_A" "http://backend.${NAMESPACE_B}.svc.cluster.local:80" info "7. Test Pod IP directly (should succeed because egress to np-b:80 is allowed)" BACKEND_IP=$(kubectl get pod -n "$NAMESPACE_B" -l app=backend -o jsonpath='{.items[0].status.podIP}') if [ -z "$BACKEND_IP" ]; then err "Cannot find backend pod IP" exit 4 fi expect_ok "$NAMESPACE_A" "http://$BACKEND_IP:80" info "8. Allow DNS from np-a to kube-system and retest FQDN" kubectl apply -f allow-dns.yaml expect_ok "$NAMESPACE_A" "http://backend.${NAMESPACE_B}.svc.cluster.local:80" info "9. (Optional) Apply deny-all on np-b to verify isolation" kubectl apply -f deny-all.yaml expect_fail "$NAMESPACE_A" "http://backend.${NAMESPACE_B}.svc.cluster.local:80" info "Cleanup: resources left for inspection. Delete manually when ready." # kubectl delete -f test-resources.yaml -f client-restrict-egress.yaml -f allow-dns.yaml -f deny-all.yaml || true info "Script completed" |
上記スクリプトは失敗時に明確な終了コードを返します。kubectl wait を単に '|| true' で逃すような実装は避けてください。
まとめ
- NetworkPolicy は L3/L4 の制御であり、実効は CNI に依存します。CNI の実装差を把握してください。
- Egress を制限する Policy を導入すると DNS(UDP/TCP 53)がブロックされ、FQDN ベースの検証は失敗することがあります。DNS 許可または PodIP 直接検証を併用してください。
- networking.k8s.io/v1 の利用可否はクラスタによります。kubectl api-versions や kubectl explain で確認してください。
- L7 制御や FQDN 制御は標準 NetworkPolicy では扱えません。Cilium や Calico の拡張機能を利用する場合はベンダードキュメントで機能と制約を確認してください。
- 低レイヤ診断は読み取り中心で行い、破壊的な操作(iptables 全消去、conntrack 全削除など)は原則実施しないでください。
- 検証は前提チェック(API/CNI/ラベル/RBAC)→マトリクス設計→堅牢なスクリプトで自動化→低レイヤ切り分けの順で行ってください。