はじめに
Namespace はリソースを整理する仕組みですが、それだけでネットワークトラフィックを遮断することはありません。NetworkPolicy は、クラスタのネットワークプラグインが適用することを前提に、選択した Pod が接続を送受信できる条件を定義します。この実験環境では、準備済みの Calico ネットワークがその適用を担います。
まずトラフィックが開放されていることを確認し、デフォルト拒否を適用した後、承認済み Namespace に属するラベル付きクライアントだけを許可します。
この実験では、安全なポリシー適用の流れに沿って進めます。最初に動作する基準状態を確認し、トラフィックを拒否し、意図した最小限の例外だけを追加してから、許可されるケースと拒否されるケースの両方をテストします。許可されたクライアントだけをテストすると、範囲が広すぎるポリシーを見逃してしまいます。
サーバーとクライアントをデプロイする
このステップでは、アプリケーション用とクライアント用に分離した Namespace、NGINX サーバー、そして長時間実行する 2 つのクライアントを作成します。
ネットワーク実験の本質が不要な YAML に埋もれないよう、コマンドには分かりやすい命令形式を使用します。一方のクライアントには role=approved、もう一方には role=untrusted のラベルを付けます。また、クライアント Namespace には access=approved を付けます。後続のポリシーでは、これら 2 つのレベルのラベルをセレクターとして使用します。
cd /home/labex/project/network-policy
kubectl create namespace practice-apps
kubectl create namespace practice-clients
kubectl label namespace practice-clients access=approved
サーバーを作成して公開します。kubectl expose は ClusterIP Service を作成します。この Service のセレクターは、Deployment によって生成された Pod の app=protected-web ラベルと一致します。
kubectl create deployment protected-web -n practice-apps --image=nginx:1.27-alpine --replicas=1
kubectl expose deployment protected-web -n practice-apps --port=80 --target-port=80
診断用のクライアント Pod を 2 つ作成します。各 Pod のスリープループにより、kubectl exec を使ったテストを繰り返し実行できます。
kubectl run は、それぞれのスタンドアロン Pod を直接作成します。--labels はポリシーが選択する識別情報を付与し、--restart=Never は kubectl がワークロードコントローラーを作成するのを防ぎます。また、-- は kubectl のオプションと、コンテナ内で実行する sh -c コマンドを分離します。待機コマンドの --all は、practice-clients 内のすべての Pod が Ready になるまで待機することを意味します。
kubectl run approved-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=approved --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl run untrusted-client -n practice-clients --image=busybox:1.36 --image-pull-policy=IfNotPresent --labels=role=untrusted --restart=Never -- sh -c 'while true; do sleep 30; done'
kubectl wait --for=condition=Ready pod -n practice-clients --all --timeout=120s
kubectl rollout status deployment/protected-web -n practice-apps --timeout=60s
Namespace を分離しただけでは、両方のクライアントから Service への接続は引き続き許可されます。
Ready 状態を確認するコマンドでは、2 つのクライアント Pod とサーバー Deployment の両方について、次のような結果が表示されます。
pod/approved-client condition met
pod/untrusted-client condition met
deployment "protected-web" successfully rolled out
この時点では、ラベルはクライアントを識別しているだけで、通信を制限してはいません。ラベルがトラフィックルールとして機能するのは、NetworkPolicy のセレクターがそのラベルを使用した後です。
トラフィックが最初は開放されていることを確認する
このステップでは、両方のクライアントから Namespace をまたいで Service にリクエストを送信します。完全修飾名には Service 名と Namespace 名が含まれます。
同じ Namespace 内であれば、protected-web だけで十分です。しかし practice-clients からアクセスする場合は、protected-web.practice-apps によってクラスタ DNS に Service 名と Namespace の両方を伝えられます。ポリシーを適用する前に両方のクライアントをテストしておくことで、後から発生する失敗を DNS や Service の設定ではなく、ポリシーによるものだと判断できます。
ヘルスプローブの実験と同様に、wget -qO- は進捗情報を表示せず、レスポンス本文だけを出力します。--timeout=3 は拒否されたリクエストが無期限に待機するのを防ぎ、grep へのパイプによって NGINX であることを示すテキストだけを表示します。
cd /home/labex/project/network-policy
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl get networkpolicies -n practice-apps
選択対象となる NetworkPolicy が存在しない場合、Ingress は許可されます。これは、Namespace だけでネットワークセキュリティ境界になるという、よくある誤解を正します。
両方のコマンドで Welcome to nginx が一致し、kubectl get networkpolicies ではリソースが存在しないと表示されるはずです。これが、意図的に変更する前の正常な基準状態です。
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
No resources found in practice-apps namespace.
同じページのテキストが 2 回表示されることで、分離を開始する前は、両方のクライアントが同じ Service に到達できることが分かります。
Ingress をデフォルト拒否にする
このステップでは、practice-apps 内のすべての Pod を選択し、Ingress の送信元を一切許可しない設定にします。
NetworkPolicy が通信を制限するのは、そのポリシーが選択した Pod と、policyTypes に記載された通信方向に対してだけです。ここでの podSelector: {} は practice-apps 内のすべての Pod を選択し、ingress の許可ルールを記述しないことで、Ingress をデフォルト拒否にします。
cd /home/labex/project/network-policy
cat <<'EOF' > default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-ingress, namespace: practice-apps}
spec:
podSelector: {}
policyTypes: [Ingress]
EOF
kubectl apply -f default-deny.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'blocked as expected'
空の podSelector は、ポリシーが配置された Namespace 内のすべての Pod を選択します。想定どおりタイムアウトすることで、Service が消えたのではなく、Ingress が拒否されたことを確認できます。
|| echo ... は、wget が想定どおりゼロ以外の終了コードを返した場合でも、ターミナルで処理を続行できるようにします。この失敗は、制御された実験における証拠です。サーバー Pod は引き続き Ready であり、変更されたのはネットワークへの受け入れ可否だけです。
networkpolicy.networking.k8s.io/default-deny-ingress created
wget: download timed out
command terminated with exit code 1
blocked as expected
パケットが拒否されるとアプリケーションからの応答が返らないため、タイムアウトは想定された結果です。最後の行は、シェルがこの想定された失敗を意図的に読みやすい結果へ変換したことを示しています。
承認済みクライアントを 1 つだけ許可する
このステップでは、承認済み Namespace に属し、かつ承認済み Pod ラベルを持つクライアントだけを要求する許可ポリシーを追加します。
NetworkPolicy は加算的に適用されます。デフォルト拒否ポリシーはそのまま有効であり、この 2 つ目のポリシーが許可する送信元を 1 つ追加します。選択されたサーバー Pod は、送信元 Namespace と送信元 Pod の両方が一致する場合に限り、TCP ポート 80 を受け入れます。
cd /home/labex/project/network-policy
cat <<'EOF' > allow-approved.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: allow-approved-client, namespace: practice-apps}
spec:
podSelector: {matchLabels: {app: protected-web}}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector: {matchLabels: {access: approved}}
podSelector: {matchLabels: {role: approved}}
ports:
- {protocol: TCP, port: 80}
EOF
kubectl apply -f allow-approved.yaml
kubectl exec approved-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps | grep 'Welcome to nginx'
kubectl exec untrusted-client -n practice-clients -- wget -qO- --timeout=3 http://protected-web.practice-apps || echo 'untrusted client blocked'
2 つのセレクターが同じ from エントリ内にあるため、両方が一致する必要があります。エントリを分けた場合は、論理和(OR)として扱われます。
承認済みクライアントからのリクエストは再び成功し、信頼されていないクライアントからのリクエストは引き続きタイムアウトするはずです。この対になった結果によって、Ingress 全体を再開したのではなく、対象を限定して許可できていることが分かります。
networkpolicy.networking.k8s.io/allow-approved-client created
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
wget: download timed out
command terminated with exit code 1
untrusted client blocked
結果は 1 組として確認してください。まず approved-client からページ内容が返り、次に untrusted-client がタイムアウトします。成功したリクエストだけを確認しても、ルールの範囲が十分に限定されていることは証明できません。
まとめ
Namespace はデフォルトではトラフィックを分離しないことを確認し、Ingress をデフォルト拒否に設定したうえで、Namespace、Pod、プロトコル、ポートの条件を組み合わせて 1 つのクライアントだけを許可しました。


