deny-allのNetworkPolicyを書いたのに、外からの通信だけ普通に通ってしまった

検証環境を本番相当に近づけるため、ある名前空間に podSelector: {} の deny-all ingress ポリシーを適用した。
Pod間の通信は狙い通り遮断された。別の名前空間からの curl は案の定タイムアウトする。ところが、
Service を type: LoadBalancer で外部公開しているエンドポイントに対して、クラスタの外から同じ疎通確認をすると、
NetworkPolicyが存在しないかのように普通にレスポンスが返ってきた。
k3s networkpolicy が期待通り動いていないと分かった瞬間、まず疑うのは「K3sのデフォルトCNIであるFlannelは NetworkPolicyを無視するから」という、よく聞く説明だろう。実際、素のFlannelにはNetworkPolicyを強制する機能が ない。だが結論から言うと、**この説明はK3sには当てはまらない。**K3sには最初からNetworkPolicyを強制する 仕組みが組み込まれているからだ。ではなぜ通信が漏れたのか。原因はNetworkPolicyの実装そのものではなく、 K3s標準のLoadBalancer実装である ServiceLB の経路にあった。
「K3sはNetworkPolicyを無視する」という誤解を正す
K3s公式ドキュメントによれば、K3sはFlannelに加えて kube-routerのNetworkPolicyコントローラをライブラリとして内蔵し、デフォルトで有効化している。 kube-router自体の他の機能(BGP等)は使わず、NetworkPolicyを強制するコントローラ部分だけを取り込んでいる形だ。 つまりK3sクラスタを構築した時点で、追加のCNIを何もインストールしなくても、NetworkPolicyリソースをapplyすれば その内容が実際に反映される。「K3s = Flannelだから NetworkPolicy は無視される」という単純化は誤りで、 これはむしろ一般的なFlannel単体構成(例えばkubeadmで素のFlannelだけを入れた場合)との重要な違いだ。
この内蔵コントローラを無効化したい場合は、サーバー起動時に --disable-network-policy を明示的に指定する
必要がある。K3sのネットワークオプション公式ドキュメント
では、Calico や Cilium のような独自のポリシーエンジンを持つCNIに切り替える際は、内蔵コントローラとの
競合を避けるためにこのフラグを併用することが推奨されている。
つまり検証環境で deny-all ポリシーが部分的に効いていたという事実そのものが、 K3sの内蔵コントローラが正常に動作している証拠でもある。問題はコントローラの有無ではなく、 別のところにあった。こうした「デフォルトで何が有効になっているか」を都度ドキュメントで確認する作業自体が、 自前でK3sクラスタを運用する上での地味なコストになる。Kubo のようなマネージド K3sであれば、この種のデフォルト構成の確認・検証はプラットフォーム側の責務として吸収できる。
真犯人はServiceLB。NetworkPolicy APIには「ノード自身からの通信」を遮断できないという制約がある

Kubernetes公式ドキュメントのNetworkPolicyページ には、あまり知られていないが重要な一文がある。Podはlocalhostへのアクセスをブロックできず、 自分が乗っているノード自身からのアクセスも原理的にブロックする手段を持たない、という記述だ。 これはK3s固有の制約ではなく、Kubernetes の NetworkPolicy API そのものが持つ恒久的な仕様であり、 どのCNI・どのポリシーコントローラを使っても解消できない。
この仕様がK3sで特に顕在化しやすい理由は、K3sの標準LoadBalancer実装である ServiceLB(旧称 Klipper LB)
の動き方にある。type: LoadBalancer のServiceを作成すると、ServiceLBは各ノード上に
DaemonSetのPodをデプロイし、そのPodがノード自身のiptablesにDNATルールを書き込んで、
ホストポート宛のトラフィックをServiceのClusterIPへ転送する仕組みになっている
(What does ServiceLB (Klipper LB) actually do? · Discussion #9927、
Networking Services | K3s)。
つまりLoadBalancer経由で届いたトラフィックは、Pod側から見ると**「ノード自身が転送してきた通信」**に
分類されうる。先述の「ノード資格の通信はブロックできない」という制約と組み合わさることで、
外部からLoadBalancer経由で届いたトラフィックに対して、NetworkPolicyのingress拒否ルールが
期待通りに機能しないケースが生まれる。実際に、あるK3sユーザーが ipBlock を使った拒否ルールで
LoadBalancer Service へのアクセスを遮断しようとしたが効かなかった、という事例が
K3sのIssue #7624 に報告されている。
同じ構成をAWS EKSで試したところ意図通りブロックできたという報告もあり、
LoadBalancerの実装方式がNetworkPolicyの実効性に影響することを裏付けている。
気づくための検証手順

この種の問題は kubectl get networkpolicy を見ても異常なく表示され、apply もエラーなく通る。
静かに漏れるからこそ厄介だ。以下の手順で切り分けると気づきやすい。
Pod間通信とLoadBalancer経由通信を分けて確認する
- 同一クラスタ内の別Podから 対象Podへ直接アクセスし、NetworkPolicyで遮断されることを確認する
- クラスタ外から、LoadBalancer Service経由で 同じPodへアクセスし、遮断されるかどうかを比較する
- 後者だけ通ってしまう場合、原因はNetworkPolicyの設定ミスではなく、経路自体がポリシー評価の対象外に なっている可能性が高い
ノード上のiptablesルールを確認する
kube-routerが書き込んだNetworkPolicy関連のiptablesルールと、ServiceLBが書き込んだDNATルールは
別々のチェーンに存在する。対象ノードで iptables-save を実行し、KUBE-ROUTER プレフィックスの
チェーンと、ServiceLB(svc- プレフィックスのPodが管理する)DNATルールの両方を確認することで、
どちらの経路をトラフィックが通っているかを追跡できる。kube-router公式のトラブルシューティングガイド
によれば、NetworkPolicyによって拒否されたトラフィックは iptables の NFLOG group 100 に
記録される仕組みになっており、実際に拒否ログが記録されているか・記録されていないのに通信が
成立しているかを見比べることでも、経路の切り分けができる。
対処法 — L7での制御、または別CNIへの切り替え

選択肢1: L4のNetworkPolicyに頼らず、L7で制御する IngressコントローラやService Meshのアクセス制御機能を使えば、送信元IPやパスベースでより柔軟に 制限できる。ノード資格通信の制約を回避しつつ、外部公開エンドポイントへのアクセス制御を実現できる。
選択肢2: NetworkPolicy機能がより高度なCNIへ切り替える
CalicoのGlobalNetworkPolicyなど、より高度なポリシーエンジンを持つCNIに切り替える方法もある。
その場合は --flannel-backend=none --disable-network-policy を明示的に指定し、内蔵のkube-router
コントローラと競合しないようにする(Issue #1308)。
ただし切り替え時には注意点がある。network policy controllerを無効化しても、それまでに
kube-routerが書き込んだiptablesルールは自動的にはクリーンアップされない
(Issue #7244)。古いルールが残ったまま新しいCNIの
ルールが追加されると、意図しない通信可否が発生しうる。k3s-killall.sh の実行や iptables-save/
iptables-restore による手動クリーンアップを、切り替え手順に必ず組み込む必要がある。
kube-routerのNetworkPolicyコントローラの実装詳細は
kube-router公式ユーザーガイド、K3sにおける
NetworkPolicy運用の基本的な考え方はSUSE/Rancher公式ブログ
にもまとまっている。
こうしたCNI切り替え作業の手順そのものを個別のクラスタごとに確立し、iptablesの残骸まで含めて検証するのは 運用負荷が高い。Kubo Cloud はCaptain UIでCNI構成とNetworkPolicyの 強制状態を可視化した状態で提供しており、こうした切り替え判断を都度ゼロから検証する必要がない。
まとめ — 「デフォルトで安全」という思い込みを、経路ごとに検証する
K3sはNetworkPolicyを無視しない。それどころかkube-routerの内蔵コントローラによって、追加の設定なしで NetworkPolicyが機能する数少ないディストリビューションの一つだ。しかし「NetworkPolicyが有効」であることと 「すべての通信経路がその対象になっている」ことは別の話だ。今回のケースでは、K3s標準のServiceLBが ノードのiptables経由でトラフィックを転送する実装であることと、NetworkPolicy APIがノード資格通信を 原理的にブロックできないという恒久的な制約が重なったことで、外部公開したサービスへの通信だけが 静かに漏れていた。
こうした「経路ごとの見落とし」は、構成を自前で組み上げるほど発生しやすい。Kubo はK3sベースのマネージドKubernetesとして、CNI構成やNetworkPolicyの強制範囲を運用チームが個別に 検証しなくても済む状態で提供することを目指している。特にデータ主権やネットワーク隔離の要件が厳しい 環境では、Kubo On-Premise を使えば、 自社インフラ上でも同様に検証済みの構成をそのまま利用できる。「デフォルトで安全なはず」という前提を 経路単位で疑う習慣は、K3sに限らずKubernetes運用全般で有効な視点だ。ネットワーク構成の検証コストを 下げたい場合は、お問い合わせ から相談してみてほしい。