NetworkPolicy を動かして理解する

v1.37 時点(2026年10月26日 確認)

Kubernetes 基本仕様第25回 / 全45回 ・ 第4部 ネットワーク
目次

この記事では Pod の通信を IP アドレスやポートの単位で制限する NetworkPolicy の仕組みを、図で説明します。どの Pod がポリシーの対象になり、どの通信が許されるのかがわかります。NetworkPolicy は Kubernetes の API で作りますが、通信を実際に止めるのはネットワークプラグインだということもわかります。後半では kind のクラスタで、決まったラベルを持つ Pod からの通信だけを nginx に通すポリシーを作り、ポリシーの前後で届き方が変わることを確かめる手順を示します。

Service の cluster IP を通して Pod に届くことを見た第21回の内容を前提にしています。まだの方は先にこちらをどうぞ。

基本仕様 ・ 第21回 Service を動かして理解するPod の集まりに変わらない入り口を与える Service の仕組みを図で説明します。ラベルセレクタで Pod を選ぶこと、cluster IP、転送先の一覧を持つ EndpointSlice、ノードで通信を Pod へ届ける kube-proxy を押さえ、kind のクラスタで Pod を増減させたときに転送先が自動で変わる様子を確かめる手順を紹介します。

以下の図は、後半の「手元で試す」で作る access-nginx という NetworkPolicy と、通信元の 2 つの Pod、nginx の Pod の関係を表しています。

NetworkPolicy access-nginx を作ったあとの、nginx の Pod と通信元の 2 つの Pod

NetworkPolicy access-nginx はラベル app=nginx を持つ Pod を選び、その Pod に入ってくる通信を、ラベル access=true を持つ Pod からのものだけに絞ります。そのため、busybox-access からの通信は nginx の Pod に届き、ラベルの無い busybox からの通信は届かなくなります。公式のタスク(Declare Network Policy)でも、正しいラベルを持たない Pod からの問い合わせはタイムアウトし、ラベルを付けた Pod からの問い合わせは届いています。access=true は kubectl の表示や本文での書き方で、マニフェストでは access: "true" と書きます。どちらも同じラベルです。

参考:Declare Network Policy | Kubernetes、Declare Network Policy | Kubernetes、Declare Network Policy | Kubernetes

後半の手順では、通信元の Pod は nginx の Service の cluster IP に接続します(第21回)。図では Service を省き、通信が最後に届く nginx の Pod の側だけを描いています。NetworkPolicy から枠への線は、通信ではなく、ラベルで Pod を選ぶ関係を表します。赤い線は、通信が届かないことを表します。ただし、この図のとおりに通信が止まるのは、ネットワークプラグインが NetworkPolicy を実装している場合だけです(「通信を実際に止めるのはネットワークプラグイン」で説明します)。

NetworkPolicy とは

NetworkPolicy は、IP アドレスやポートの単位で、通信の流れの規則を決める仕組み です。公式ドキュメントはこの単位を、OSI 参照モデルの第 3 層と第 4 層(OSI layer 3 or 4)と説明しています。OSI 参照モデルは一般に、ネットワークの働きを 7 つの層に分けて整理した考え方で、第 3 層は IP アドレス、第 4 層は TCP や UDP のポートを扱う層です。NetworkPolicy で決められるのは、クラスタの中の通信の規則と、Pod とクラスタの外とのあいだの通信の規則です。クラスタは NetworkPolicy を実際に効かせられるネットワークプラグインを使う必要があります。

参考:Network Policies | Kubernetes

公式ドキュメントは NetworkPolicy を、アプリケーションを中心にした仕組み(application-centric construct)と説明しています。NetworkPolicy は、Pod がネットワーク上のさまざまな「相手」(原文は entity)とどう通信してよいかを決めるものです。NetworkPolicy が当てはまるのは、片方か両方の端が Pod である接続で、それ以外の接続には関係しません。対象のプロトコルは TCP、UDP、SCTP です。SCTP は一般に、TCP や UDP と同じ第 4 層のプロトコルの 1 つです。

参考:Network Policies | Kubernetes

Pod が通信できる相手は、次の 3 つの組み合わせで決めます。それぞれに、公式ドキュメントが挙げる例外があります。

相手の決め方例外
許可するほかの PodPod は自分自身への通信を止められない
許可する Namespace公式ドキュメントに例外の記載なし
許可する IP アドレスの範囲(IP ブロック)Pod が動いているノードとのあいだの通信は、Pod やノードの IP アドレスにかかわらず、いつも許される

参考:Network Policies | Kubernetes

ノードとのあいだの通信としては、第20回で見た、ノードで動く kubelet などのエージェントと Pod との通信が考えられます。公式ドキュメントのネットワークモデルも、ノードのエージェント(システムのデーモンや kubelet など)がそのノードのすべての Pod と通信できることを求めています。

参考:Services, Load Balancing, and Networking | Kubernetes

Pod や Namespace で相手を決めるときは、ラベルセレクタ(第11回)を使います。IP アドレスで決めるときは、IP アドレスの範囲(CIDR の形。172.17.0.0/16 のような書き方。第20回)を使います。Namespace は第3回で紹介した、1 つのクラスタの中でリソースをグループに分ける仕組みで、くわしくは第37回で扱います。

参考:Network Policies | Kubernetes、Namespaces | Kubernetes

NetworkPolicy は Kubernetes に組み込まれた API で、Pod どうしの通信や、Pod とクラスタの外とのあいだの通信を制御できます。apiVersion は networking.k8s.io/v1 です。NetworkPolicy は Namespace に属するオブジェクトで、ポリシーが選ぶ Pod は、ポリシーと同じ Namespace の Pod です。NetworkPolicy の名前は DNS のサブドメイン名(第5回)の形にする必要があります。

参考:Services, Load Balancing, and Networking | Kubernetes、NetworkPolicy | Kubernetes API Reference、Declare Network Policy | Kubernetes、Object Names and IDs | Kubernetes

この回に出てくる言葉を、公式ドキュメントでの意味に沿って次の表にまとめます。

言葉指すもの
ingressPod に入ってくる通信。NetworkPolicy の ingress には、入ってくる通信を許す規則を書く
egressPod から出ていく通信。NetworkPolicy の egress には、出ていく通信を許す規則を書く
隔離(isolation)ある向きの通信が「許可したものだけを通す」方式に切り替わった状態(完全な遮断ではない)。許可を何も書かなければその向きの通信は通らず、書けばそれだけが通る(入ってくる通信では、その Pod のノードからの接続も通る)
隔離されていない(non-isolated)ある向きの通信に、制限が何もかかっていない状態
ネットワークプラグインPod のネットワークを用意するソフトウェア(第20回)。NetworkPolicy も、ふつうはネットワークプラグインが実装する

参考:Network Policies | Kubernetes、Services, Load Balancing, and Networking | Kubernetes

表の ingress は、Ingress(クラスタの外から Service へ届ける API。第24回)とは別のものです。この回で「ingress」と書くときは、Pod に入ってくる通信の向きを指します。

なぜ NetworkPolicy が必要なのか

第20回で見たとおり、Kubernetes のネットワークモデルでは、意図してネットワークを区切っていない限り、すべての Pod がほかのすべての Pod と通信できます。NetworkPolicy のページも、ある Namespace にポリシーが 1 つも無ければ、その Namespace の Pod に入ってくる通信も、Pod から出ていく通信も、すべて許されると説明しています。

参考:Services, Load Balancing, and Networking | Kubernetes、Network Policies | Kubernetes

第21回の Service も相手を限りません。ClusterIP の Service にはクラスタの中から届くので、Service の cluster IP がわかれば、クラスタの中のどの Pod からでも接続を試みられると考えられます。第21回の手順でも、Service のセレクタとは関係の無い client の Pod から、cluster IP で nginx に届くことを確かめる手順を示しました。

参考:Service | Kubernetes

ここで「データベースの Pod には、フロントエンドの Pod からだけ接続させたい」といった要望が出てきます。NetworkPolicy のページの最初の例も、ラベル role=db の Pod への TCP の 6379 番のポートの接続を、ラベル role=frontend の Pod などからだけ許すものです(「マニフェストに書くフィールド」で見ます)。公式ドキュメントは TCP、UDP、SCTP の通信の流れを IP アドレスやポートの単位で制御したいなら、クラスタの中の特定のアプリケーションに NetworkPolicy を使うことを検討できる(might consider)としています。

参考:Network Policies | Kubernetes、Network Policies | Kubernetes

一方で、第20回で見たとおり、ネットワークモデルのうち Kubernetes 自身が実装している部分はわずかです。NetworkPolicy も、Kubernetes が API を決め、実際の働きはふつうネットワークプラグインが受け持ちます。公式ドキュメントによると、簡単なネットワークプラグインの中には NetworkPolicy を実装していないものがあり、管理者が NetworkPolicy に対応しない設定にすることもあります。その場合も API はありますが、効果はありません。この回では、この関係を中心に見ていきます。

参考:Services, Load Balancing, and Networking | Kubernetes

NetworkPolicy の仕組み

マニフェストに書くフィールド

以下は公式ドキュメントにある NetworkPolicy のマニフェストです(そのまま引用しています)。

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

参考:Network Policies | Kubernetes

公式ドキュメントは、このポリシーがすることを次の 3 つにまとめています。

  • default の Namespace のラベル role=db の Pod を、入ってくる通信と出ていく通信の両方の向きで隔離する
  • その Pod の TCP の 6379 番のポートへの接続を、次の相手から許す(ingress の規則)
  • その Pod から 10.0.0.0/24 の TCP の 5978 番のポートへの接続を許す(egress の規則)

参考:Network Policies | Kubernetes

ingress の規則で許す相手は、次の 3 つです。どれか 1 つに当てはまれば許されます。

相手書き方
default の Namespace の、ラベル role=frontend の PodpodSelector
ラベル project=myproject の Namespace の、すべての PodnamespaceSelector
172.17.0.0/16 のうち、172.17.1.0/24 を除いた範囲の IP アドレスipBlock の cidr と except

参考:Network Policies | Kubernetes

この例のマニフェストをクラスタに渡しても、使っているネットワークの仕組みが NetworkPolicy に対応していなければ、効果はありません。公式ドキュメントは例のすぐ後でこのことを注意しています。

参考:Network Policies | Kubernetes

spec に書く主なフィールドを、v1.37 時点の API リファレンスと公式ドキュメントに沿って次の表にまとめます。

フィールド内容書かないとき
spec.podSelectorポリシーを当てる Pod を、ポリシーと同じ Namespace の中からラベルセレクタで選ぶ空のセレクタになり、Namespace のすべての Pod を選ぶ
spec.policyTypesポリシーが関係する向き。Ingress、Egress、またはその両方Ingress はいつも入り、Egress は egress の規則があるときだけ入る
spec.ingress[].from入ってくる通信を許す相手の一覧。どれか 1 つに当てはまればよいすべての相手に当てはまる
spec.ingress[].ports入ってくる通信を許すポートの一覧。どれか 1 つに当てはまればよいすべてのポートに当てはまる
spec.egress[].to出ていく通信を許す相手の一覧。どれか 1 つに当てはまればよいすべての相手に当てはまる
spec.egress[].ports出ていく通信を許すポートの一覧。どれか 1 つに当てはまればよいすべてのポートに当てはまる
ports[].protocolプロトコル。TCP、UDP、SCTP のどれかTCP
ports[].portポートの番号か、Pod のポートに付けた名前すべてのポートに当てはまる

参考:NetworkPolicy | Kubernetes API Reference、Network Policies | Kubernetes

1 つの規則は from(egress では to)と ports の両方に当てはまる通信を許します。上の例の ingress の規則は「3 つの相手のどれか」から「TCP の 6379 番のポート」への通信を許す、という 1 つの規則です。

参考:Network Policies | Kubernetes

policyTypes は書き忘れると思わぬ結果になりやすいフィールドです。API リファレンスによると、policyTypes を書かないポリシーは、egress の規則を持つときだけ出ていく通信にもかかわり、ingress の規則があってもなくても入ってくる通信にはいつもかかわるとみなされます。そのため、出ていく通信だけを制限するポリシーを書くときは policyTypes に Egress だけを明示する必要があります。出ていく通信をまったく許さないポリシー(egress の規則を持たない)を書くときも、Egress を含めて明示する必要があります。書かないと Ingress だけのポリシーとみなされるからです。

たとえば、egress の規則だけを書いて policyTypes を省くと、そのポリシーは出ていく通信だけでなく入ってくる通信にもかかわるとみなされます。API リファレンスによると、ingress が空のポリシーは入ってくる通信を何も許さず、選んだ Pod を隔離するだけの働きをします。そのため、同じ Pod を選んで入ってくる通信を許すポリシーがほかに無ければ、選ばれた Pod には、その Pod のノードからの接続を除いて、何も入ってこられなくなると考えられます。逆に、後半の手順で使う access-nginx は policyTypes を省き、ingress の規則だけを持つ形です。そのため Ingress だけのポリシーになります。

参考:NetworkPolicy | Kubernetes API Reference

ポートの範囲を指定する endPort もあります。port から endPort までの範囲を許すフィールドで、v1.25 から安定版(Stable)です。port を数字で書いたときだけ使え、endPort は port 以上にする必要があります。公式ドキュメントによると、ネットワークプラグインが endPort に対応していない場合、endPort を書いたポリシーは port の 1 つのポートにだけ当てはまります。

参考:Network Policies | Kubernetes、NetworkPolicy | Kubernetes API Reference

ポリシーに選ばれた Pod だけが隔離される

NetworkPolicy の動きを理解する鍵は「隔離(isolation)」です。公式ドキュメントによると、Pod の隔離には、出ていく通信(egress)の隔離と、入ってくる通信(ingress)の隔離の 2 種類があります。ここでの隔離は「何らかの制限がかかる」という意味で、完全に遮断するという意味ではありません。2 種類の隔離はそれぞれ別に決まり、ある Pod から別の Pod への接続には、両方がかかわります。

参考:Network Policies | Kubernetes

公式ドキュメントの説明を、向きごとに次の表にまとめます。

項目出ていく通信(egress)入ってくる通信(ingress)
既定の状態隔離されていない。出ていく接続はすべて許される隔離されていない。入ってくる接続はすべて許される
隔離される条件その Pod を選び、policyTypes に Egress を含むポリシーが 1 つでもあるその Pod を選び、policyTypes に Ingress を含むポリシーが 1 つでもある
隔離されたときに許される接続その向きで当てはまるポリシーの egress の一覧が許すものその Pod のノードからの接続と、その向きで当てはまるポリシーの ingress の一覧が許すもの
許された接続で、相手から返ってくる応答逆向きの規則を書かなくても、暗黙のうちに許される逆向きの規則を書かなくても、暗黙のうちに許される

参考:Network Policies | Kubernetes

つまり、どの NetworkPolicy にも選ばれていない Pod は、何も制限されません。ポリシーを 1 つ作ると、そのポリシーに選ばれ、かつ policyTypes に含まれる向きについてだけ、Pod が隔離されます。隔離されると、許す規則に書かれた接続以外は許されなくなります(入ってくる通信では、その Pod のノードからの接続も許されます)。

参考:Network Policies | Kubernetes

ポリシーどうしがぶつかることはありません。公式ドキュメントによると、ポリシーは足し算(additive)で働きます。ある Pod のある向きに当てはまるポリシーが複数あれば、その向きで許される接続は、各ポリシーが許すものを合わせたもの(和集合)です。そのため、ポリシーを評価する順番は結果に影響しません。逆に、許可を打ち消す「拒否」の規則は書けません(「NetworkPolicy でできないこと」で見ます)。

参考:Network Policies | Kubernetes

以下の図は、ある Pod から別の Pod への接続が許されるために、どちらの側の規則がかかわるかを表しています。

Pod から Pod への接続にかかわる、送り元の egress の規則と宛先の ingress の規則

公式ドキュメントによると、送り元の Pod から宛先の Pod への接続が許されるには、送り元の Pod の egress のポリシーと、宛先の Pod の ingress のポリシーの両方が、その接続を許している必要があります。どちらか一方でも許していなければ、接続は成り立ちません。ただし、隔離されていない向きには制限がありません。たとえば送り元の Pod がどのポリシーにも egress で選ばれていなければ、送り元の側では制限がかからず、宛先の ingress の規則だけで決まると考えられます。

参考:Network Policies | Kubernetes

from と to で相手を選ぶ

ingress の from と egress の to には、相手を選ぶ書き方が 4 つあります。公式ドキュメントと API リファレンスの説明を、次の表にまとめます。

書き方選ぶ相手
podSelector だけポリシーと同じ Namespace の、ラベルが合う Pod
namespaceSelector だけラベルが合う Namespace の、すべての Pod
namespaceSelector と podSelector を 1 つの項目に書くラベルが合う Namespace の中の、ラベルが合う Pod
ipBlockIP アドレスの範囲(cidr)。except で除く範囲も書ける。ipBlock を書いた項目には、ほかの 2 つは書けない

参考:Network Policies | Kubernetes、NetworkPolicy | Kubernetes API Reference

表の 3 つ目の書き方と、namespaceSelector と podSelector を別々の項目に書く形は、YAML の見た目が似ていて意味が違います。公式ドキュメントは YAML の書き方に気を付けるよう注意し、次の 2 つの例を並べています(そのまま引用しています)。1 つ目は 1 つの項目に両方を書いた形です。

yaml
      ...
      ingress:
      - from:
        - namespaceSelector:
            matchLabels:
              user: alice
          podSelector:
            matchLabels:
              role: client
      ...

2 つ目は、podSelector の前に - があり、別の項目になっている形です。

yaml
      ...
      ingress:
      - from:
        - namespaceSelector:
            matchLabels:
              user: alice
        - podSelector:
            matchLabels:
              role: client
      ...

参考:Network Policies | Kubernetes

公式ドキュメントによると、1 つ目はラベル user=alice の Namespace の中の、ラベル role=client の Pod からの接続を許します(「かつ」)。2 つ目は、ポリシーと同じ Namespace のラベル role=client の Pod から、または、ラベル user=alice の Namespace のすべての Pod からの接続を許します(「または」)。公式ドキュメントは、迷ったときは kubectl describe で、Kubernetes がポリシーをどう解釈したかを確かめるよう勧めています。

参考:Network Policies | Kubernetes

この段落は ipBlock を使うときの細かい注意なので、今は読み飛ばしてもかまいません。ipBlock について、公式ドキュメントは、Pod の IP アドレスは長く続かず予測できないので、クラスタの外の IP アドレスに使うべき(should)だとしています。また、クラスタに入る通信や出る通信の仕組みは、パケットの送り元や宛先の IP アドレスを書き換えることがよくあります。その書き換えが NetworkPolicy の処理の前か後かは決められておらず、ネットワークプラグイン、クラウド、Service の実装の組み合わせによって違うことがあります。そのため、入ってくる通信では、NetworkPolicy が見る送り元の IP アドレスが、本来の送り元ではなくロードバランサーやノードの IP アドレスになる場合があります。

参考:Network Policies | Kubernetes

Namespace を名前で直接指定するフィールドはありません。代わりに、コントロールプレーンはすべての Namespace に、変えられないラベル kubernetes.io/metadata.name を付けます。値は Namespace の名前です。公式ドキュメントはこのラベルを namespaceSelector に書けば、特定の Namespace を選べると説明しています。

参考:Network Policies | Kubernetes

Namespace の既定の動きを変えるポリシー

podSelector を空({})にすると、Namespace のすべての Pod を選べます。これを使うと、Namespace の既定の動きを変えられます。以下は公式ドキュメントにある、入ってくる通信を既定で拒否するポリシーです(そのまま引用しています)。

yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

参考:Network Policies | Kubernetes

このポリシーは Namespace のすべての Pod を選び、policyTypes に Ingress を含みますが、ingress の規則を 1 つも持ちません。公式ドキュメントによると、これでほかのどの NetworkPolicy にも選ばれていない Pod も、入ってくる通信について隔離されます。出ていく通信の隔離には影響しません。ここに別のポリシーで許可を足していけば、許したものだけが届く Namespace になると考えられます。

参考:Network Policies | Kubernetes

公式ドキュメントは既定の動きを変えるポリシーの例を 5 つ挙げています。違いを次の表にまとめます。

例の名前policyTypes規則効果
default-deny-ingressIngress書かない入ってくる通信について、すべての Pod を隔離する
allow-all-ingressIngressingress: [{}](空の規則 1 つ)すべての入ってくる接続を許す。ほかのポリシーを足しても拒否されない
default-deny-egressEgress書かない出ていく通信について、すべての Pod を隔離する
allow-all-egressEgressegress: [{}](空の規則 1 つ)すべての出ていく接続を許す。ほかのポリシーを足しても拒否されない
default-deny-allIngress と Egress書かない両方の向きについて、すべての Pod を隔離する

参考:Network Policies | Kubernetes

以下は公式ドキュメントにある、入ってくる接続をすべて許すポリシーです(そのまま引用しています)。

yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

参考:Network Policies | Kubernetes

公式ドキュメントの allow-all-egress の例は、この例の名前を allow-all-egress に、Ingress と ingress を Egress と egress にしたものです。ingress: の下の - {} が空の規則 1 つです。空の規則({})は from も ports も書いていないので、すべての相手、すべてのポートに当てはまります。規則を書かないこととは意味が逆になる点に注意してください。

参考:Network Policies | Kubernetes、NetworkPolicy | Kubernetes API Reference

通信を実際に止めるのはネットワークプラグイン

公式ドキュメントの前提(Prerequisites)の節は、NetworkPolicy はネットワークプラグインが実装する、と書いています。NetworkPolicy を使うには、NetworkPolicy に対応したネットワークの仕組みを使う必要があります。NetworkPolicy を実装するもの(ふつうはネットワークプラグイン。公式ドキュメントはここでコントローラーと呼んでいます)が無いまま NetworkPolicy のリソースを作っても、効果はありません。

参考:Network Policies | Kubernetes

以下の図は、NetworkPolicy を実装するネットワークプラグインを使うクラスタと、実装しないネットワークプラグインを使うクラスタで、同じ NetworkPolicy がどう扱われるかを表しています。

NetworkPolicy を実装するネットワークプラグインと、実装しないネットワークプラグインの違い

どちらのクラスタでも、NetworkPolicy のオブジェクトは同じように作れます。違うのは、そのオブジェクトをもとに Pod の通信を制限するものがいるかどうかです。左の図ではネットワークプラグインが NetworkPolicy を読み取って Pod の通信を制限し、右の図では NetworkPolicy があっても通信は変わりません。図には、通信の制限にかかわる線だけを描いています。右の図に線が無いのは通信を制限していないという意味で、右のクラスタでもネットワークプラグインは Pod に IP アドレスを割り当て、通信を届けています(第20回)。図の点線は監視(読み取る)、破線は管理する(制限する)関係を表します。点線の矢印は、これまでの回の図と同じく、見張る側から見張られる側へ向けて描いています。ネットワークプラグインが NetworkPolicy をどう読み取るかは公式ドキュメントに書かれていないので、「読み取る」の線は実装するには読み取る必要があるという意味で描いたものです。

参考:Network Policies | Kubernetes、Services, Load Balancing, and Networking | Kubernetes

困るのは、効いていないことに Kubernetes の API からは気づきにくい点です。v1.37 時点の API リファレンスでは、NetworkPolicy のオブジェクトにあるのは apiVersion、kind、metadata、spec だけで、状態を表す status はありません。公式ドキュメントも、NetworkPolicy に適合した実装を使うクラスタについて、作った NetworkPolicy はいずれネットワークプラグインに処理されるが、それがいつなのかを Kubernetes の API から知る方法は無い、としています。そのため、ポリシーが効いているかどうかは、実際に通信を試して確かめることになると考えられます。後半の手順もその確かめ方です。

参考:NetworkPolicy | Kubernetes API Reference、Network Policies | Kubernetes

NetworkPolicy に対応するかどうかは、ネットワークプラグインを選ぶときの判断材料の 1 つになります。kubeadm の公式ドキュメントは、CNI を使う Pod のネットワークを提供する外部のプロジェクトがいくつかあり、その一部は NetworkPolicy にも対応している、と説明しています。公式のタスク(Declare Network Policy)は NetworkPolicy に対応したネットワークの仕組みの例として、Antrea、Calico、Cilium、Kube-router を挙げています。

参考:Creating a cluster with kubeadm | Kubernetes、Declare Network Policy | Kubernetes

この段落と次の段落では、ポリシーが効き始めるまでの細かい動きを説明します。今は「作った直後は効いていないことがある」とだけ覚えて、読み飛ばしてもかまいません。NetworkPolicy に対応したネットワークプラグインでも、作ったポリシーがすぐに効くとは限りません。公式ドキュメントの Pod lifecycle の節(NetworkPolicy に適合したネットワークプラグインを使うクラスタについての説明)によると、新しい NetworkPolicy をネットワークプラグインが処理するには、時間がかかることがあります。処理が終わる前に作られた Pod は、守られないまま起動することがあり、処理が終わった時点で隔離の規則が当てはまります。ネットワークプラグインは NetworkPolicy を分散して実装することがあるので、Pod やポリシーが変わったときに、一時的に食い違った状態が見えることもあります。

参考:Network Policies | Kubernetes

処理が終わったあとに作られた、ポリシーの対象の Pod は、起動する前に隔離されます。NetworkPolicy は Pod の単位で当てはまるので、init コンテナ、サイドカーコンテナ、ふつうのコンテナ(第10回)のどれにも同じように当てはまります。一方、許す規則は隔離の規則より後から(または同時に)当てはまります。最悪の場合、隔離の規則がもう当てはまっていて許す規則がまだ当てはまっていないと、新しい Pod は起動した直後、まったく通信できないことがあります。公式ドキュメントは、Pod は思っていたのと違う通信の状態で起動しても耐えられる必要があるとし、起動前に決まった相手に届くことを確かめたいなら、init コンテナで待つ方法を挙げています。

参考:Network Policies | Kubernetes

kind のクラスタの kindnetd と NetworkPolicy

第20回で見たとおり、kind のクラスタの既定のネットワークプラグインは kindnetd です。kind の公式ドキュメントの設定のページは、kindnetd を、標準の CNI プラグインと単純な経路の設定でできた、簡単なネットワークの実装と説明しています。このページには、NetworkPolicy への対応についての記載はありません。

参考:Configuration | kind

kube-network-policies は NetworkPolicy を実装する部品で、kind v0.33.0 では kindnetd に組み込まれていると考えられます。そうであれば、kind の既定のクラスタで NetworkPolicy を使うために、読者が別の部品を入れる必要は無いはずです。根拠は次のとおりです。

kind の v0.24.0 のリリースノートは、新しい機能として、sigs.k8s.io/kube-network-policies を使って NetworkPolicy にそのままの状態で(out-of-the-box)対応したことを挙げています。v0.25.0 のリリースノートにも、NetworkPolicy にかかわる修正として kube-network-policies を更新した記載があります。v0.26.0 から v0.33.0 までのリリースノートには、この対応を外したという記載は見当たりません。そのため、kind v0.33.0 で既定の設定のまま作ったクラスタでは、NetworkPolicy が効くと考えられます。

参考:Release v0.24.0 · kubernetes-sigs/kind、Release v0.25.0 · kubernetes-sigs/kind、Releases · kubernetes-sigs/kind

NetworkPolicy を実際に受け持つのが kindnetd 自身かどうかは、リリースノートには書かれていません。kind v0.33.0 のソースコードでは、kindnetd のプログラムが kube-network-policies を取り込み、NetworkPolicy を扱うコントローラーを起動しているように読めます。kindnetd の DaemonSet を定めたマニフェストでも、kindnetd の権限(ClusterRole。第38回)に、NetworkPolicy を一覧し(list)、見張る(watch)権限が含まれているように読めます。そうだとすると、kind の既定のクラスタでは、ノードごとに動く kindnetd が NetworkPolicy も実装していると考えられます。

参考:kind のソースコード(kindnetd)、kind のソースコード(kindnet)

kind の設定ファイルで networking の disableDefaultCNI を true にすると、kindnetd は入りません(第20回)。その場合、NetworkPolicy が効くかどうかは、代わりに入れたネットワークプラグインが NetworkPolicy を実装しているかどうかで決まります。kind の公式ドキュメントは、既定のネットワークプラグインを外すのは慣れた人向けの機能でサポートは限られるが、Calico などよく使われる CNI のマニフェストの多くは動くことがわかっている、としています。v0.24.0 のリリースノートも、kind のプロジェクトは自分たち以外が提供する CNI(原文は third-party CNI)をサポートしない、と書いています。

参考:Configuration | kind、Release v0.24.0 · kubernetes-sigs/kind

NetworkPolicy でできないこと

公式ドキュメントは v1.37 時点の NetworkPolicy の API に無い機能を挙げています。主なものを次の表にまとめます。

できないこと公式ドキュメントの補足
明示的に拒否する規則を書く今の NetworkPolicy は既定で拒否し、許す規則を足すだけの作り(ポリシーに選ばれて隔離された Pod では、許す規則に当てはまらない通信が拒否される、という意味)
Service を名前で指定するPod や Namespace をラベルで指定するのが、多くの場合に使える代わりの方法
localhost への通信や、自分のノードからの通信を止めるPod は localhost への通信も、自分が動くノードからの通信も止められない
すべての Namespace や Pod に当てはまる既定のポリシーを作るKubernetes のプロジェクト以外が提供する、Kubernetes の配布版(ディストリビューション)やプロジェクトの中には、できるものがある
止めた接続や許した接続の記録(ログ)を取る補足の記載なし
TLS にかかわることサービスメッシュや Ingress コントローラーを使う

参考:Network Policies | Kubernetes

表の TLS は一般に、通信を暗号化する仕組みです。サービスメッシュは一般に、Pod どうしの通信をプロキシ(通信を中継するプログラム)に通して管理する仕組みで、このシリーズでは扱いません。Ingress コントローラーは第24回で見ました。

NetworkPolicy の対象が決まっているのは、TCP、UDP、SCTP(SCTP は対応するプラグインだけ)の接続です。公式ドキュメントによると、すべてを拒否するポリシーでも、確実に拒否されるのは TCP、UDP、SCTP の接続だけです。ARP や ICMP などほかのプロトコルの扱いは決まっておらず、ネットワークプラグインによって許されたり拒否されたりします。ARP は一般に、IP アドレスから相手の機器のアドレスを調べるプロトコル、ICMP はネットワークの状態を知らせ合うためのプロトコルです。ping も一般に ICMP を使うので、ポリシーが効いているかを ping で確かめると、プラグインによって結果が変わると考えられます。後半の手順では TCP を使う HTTP の問い合わせで確かめます。

参考:Network Policies | Kubernetes

すでに続いている接続への影響も決まっていません。公式ドキュメントによると、ポリシーを変えたり、ポリシーが選ぶ Pod や Namespace のラベルを変えたりして、続いている接続に当てはまるポリシーが変わったとき、その接続に変更が及ぶかどうかは実装次第です。公式ドキュメントは続いている接続に影響するような形でポリシー、Pod、Namespace を変えないよう勧めています。

参考:Network Policies | Kubernetes

ノードのネットワークをそのまま使う Pod(hostNetwork: true。第4回、第20回)での動きも決まっていません。公式ドキュメントによると、ネットワークプラグインがこの Pod の通信を見分けられればふつうの Pod と同じようにポリシーを当て、見分けられなければセレクタで選ぶときにこの Pod を無視し、ノードの IP アドレスとの通信と同じに扱います。後者がもっともよくある実装だとしています。

参考:Network Policies | Kubernetes

手元で試す

ここまでの内容を手元の kind のクラスタで試す手順を示します。公式のタスク(Declare Network Policy)に沿って nginx の Deployment と Service を作り、ラベルの無い Pod と、ラベル access=true を持つ Pod から問い合わせます。NetworkPolicy を作る前はどちらからも届き、作ったあとはラベルを持つ Pod からだけ届くことを確かめます。最後に、ネットワークプラグインを替えたクラスタで同じ手順を試す方法を示します。

この節で引用する出力の例は、公式のタスクにある、ほかのクラスタのものです。表示の形を見るためのもので、名前や IP アドレスなどの値は手元とは違います。各段の最後には成功かどうかを見分ける点を短く挙げます。どれも公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。

コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。

項目内容
kindv0.33.0
Kubernetesv1.37.0(kind v0.33.0 の既定のノードイメージ)
ノード構成マルチノード(クラスタ名 multi-node):コントロールプレーン 1、ワーカー 2
ネットワークプラグインkindnetd(kind の既定)
必要なものDocker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ

参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes

kind でクラスタを作る手順は、以下の記事で解説しています。この回は第20回・第21回と同じマルチノードのクラスタを使います。公式のタスクが、コントロールプレーンではないノードが 2 つ以上あるクラスタで試すことを勧めているからです。

基本仕様 ・ 第2回 kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。

参考:Declare Network Policy | Kubernetes

マルチノードのクラスタを用意する

第2回と同じ次の設定ファイルを使います。公式ドキュメントの例をそのまま引用しています。kind-multi-node.yaml という名前で、コマンドを実行するディレクトリに保存します。前の回までに保存したファイルが残っていれば、それを使えます。

yaml
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker

参考:Quick Start | kind

最初に、手元の kind のバージョンを確かめます。kind の公式ドキュメントもバージョンを確かめるコマンドとして kind version を挙げています。

console
$ kind version

表示に v0.33.0 が含まれていれば、この記事と同じバージョンです。「kind のクラスタの kindnetd と NetworkPolicy」で見たとおり、kind が NetworkPolicy に対応したのは v0.24.0 からです。それより古い kind で作ったクラスタでは、NetworkPolicy が効かない可能性があると考えられます。

参考:Quick Start | kind、Release v0.24.0 · kubernetes-sigs/kind

続けて、前の回のクラスタが残っていないかを確かめます。

console
$ kind get clusters

multi-node が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。

console
$ kind create cluster --name multi-node --config kind-multi-node.yaml
$ kind get clusters

kind get clusters に multi-node が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-multi-node を付けて、操作するクラスタをはっきりさせます。

参考:Quick Start | kind

続けて、ノードを一覧します。

console
$ kubectl get nodes --context kind-multi-node

ノードが 3 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。

参考:kubectl get | Kubernetes、Node Status | Kubernetes

このクラスタのネットワークプラグインが kindnetd であることも確かめておきます。kube-system の Namespace の DaemonSet を一覧します。-n は第4回で使った、Namespace を指定するオプションです。

console
$ kubectl get daemonsets -n kube-system --context kind-multi-node

第20回で見たとおりなら、kindnet という名前の DaemonSet が表示されるはずです(kind v0.33.0 のソースコードで確かめた名前です)。第21回で見た kube-proxy も並ぶと考えられます。

参考:kind のソースコード(kindnet)、kubectl get | Kubernetes

前の回のクラスタを使い続けている場合は、この回で使う名前のオブジェクトと、NetworkPolicy が残っていないことを確かめます。この回の手順は、default の Namespace に NetworkPolicy が 1 つも無い状態を前提にしています。

console
$ kubectl get deployments,services,networkpolicies --context kind-multi-node
$ kubectl get pods --context kind-multi-node

1 つ目のコマンドの一覧に nginx という Deployment と Service が無く、NetworkPolicy も 1 つも無く、2 つ目のコマンドの一覧に busybox と busybox-access が無ければ、そのまま進めます。1 つ目のコマンドでは、kubernetes という Service が表示されると考えられます(第21回の片付けで説明しました)。

nginx の Deployment や Service、busybox や busybox-access の Pod が表示されたら、名前を指定して消してから進みます。--ignore-not-found は、消す相手が無くてもエラーにしない指定です。

console
$ kubectl delete deployment nginx --ignore-not-found --context kind-multi-node
$ kubectl delete service nginx --ignore-not-found --context kind-multi-node
$ kubectl delete pod busybox busybox-access --ignore-not-found --context kind-multi-node

NetworkPolicy が表示されたら、自分で作ったものかを確かめてから、kubectl delete networkpolicy <名前> --context kind-multi-node で消します。このシリーズの前の回までの手順では NetworkPolicy を作っていません。

参考:kubectl get | Kubernetes、kubectl delete | Kubernetes

マニフェストを用意する

使うマニフェストは 3 つです。1 つ目は公式のタスクにある NetworkPolicy です(そのまま引用しています)。タスクでは nginx-policy.yaml というファイルになっているので、同じ名前で、コマンドを実行するディレクトリに保存します。

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

参考:Declare Network Policy | Kubernetes

公式のタスクによると、このポリシーは nginx の Service への問い合わせを、ラベル access: true を持つ Pod からだけに絞るためのものです。podSelector でラベル app=nginx の Pod を選びます。このラベルは次の段で kubectl create deployment で作る nginx の Deployment の Pod に、自動で付きます。policyTypes を書いていないので、このポリシーは入ってくる通信(Ingress)にだけかかわり、nginx の Pod から出ていく通信は制限しないはずです。ingress の規則は同じ Namespace の、ラベル access=true を持つ Pod からの通信を許します。ports を書いていないので、すべてのポートに当てはまります。metadata.namespace を書いていないので、このポリシーは kubectl のコンテキストの Namespace に作られます(第3回)。このシリーズの kind のクラスタでは default と考えられ、公式のタスクでも nginx の Pod と Deployment は default の Namespace にあります。

参考:Declare Network Policy | Kubernetes、Declare Network Policy | Kubernetes、NetworkPolicy | Kubernetes API Reference、kubeconfig (v1) | Kubernetes、Namespaces | Kubernetes

ラベルの値の "true" は引用符で囲まれています。API リファレンスでは、ラベル(metadata.labels)は文字列のキーと値の組(Map of string keys and values)で、matchLabels はキーと値の組({key,value})を並べたものです。引用符で囲まないと、YAML では一般に真偽値の true として読まれ、文字列として扱われないと考えられます。

参考:ObjectMeta | Kubernetes API Reference、LabelSelector | Kubernetes API Reference

2 つ目と 3 つ目は通信元にする Pod です。公式のタスクは kubectl run で一時的な Pod を作り、その中のシェルでコマンドを打っています。この回では第20回・第21回と同じく、何もせずに待ち続ける Pod をマニフェストで作り、kubectl exec でコマンドを動かします。2 つ目はラベルの無い Pod です。busybox.yaml という名前で保存します。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: busybox
    image: busybox
    command: ['sh', '-c', 'sleep 3600']

3 つ目はラベル access: "true" を持つ Pod です。busybox-access.yaml という名前で保存します。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox-access
  labels:
    access: "true"
spec:
  containers:
  - name: busybox
    image: busybox
    command: ['sh', '-c', 'sleep 3600']

参考:Pod | Kubernetes API Reference、Declare Network Policy | Kubernetes

第20回・第21回の client の Pod は busybox:1.28 のイメージでしたが、この回は公式のタスクと同じく、タグを付けない busybox にしています。公式のタスクとイメージをそろえるためです。第21回で見たとおり、タグを書かないと latest のタグとみなされます。

参考:Images | Kubernetes

NetworkPolicy の監視を始める

ここからは端末を 2 つ使います。第11回・第13回〜第21回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でマニフェストを渡したり、Pod の中でコマンドを動かしたりします。この回で 1 つ目の端末が追うのは、default の Namespace の NetworkPolicy です。2 つ目の端末を開いたら、cd でマニフェストを保存したディレクトリに移動しておきます。

1 つ目の端末で、NetworkPolicy の一覧を監視します。第3回で見たとおり、--watch を付けると、一覧を表示したあとも変化を追い続けます。この監視は片付けの前まで止めずに続けます。

console
$ kubectl get networkpolicies --watch --context kind-multi-node

参考:kubectl get | Kubernetes

まだ NetworkPolicy が無いので、見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。

nginx の Deployment と Service を作る

2 つ目の端末で、公式のタスクと同じコマンドで nginx の Deployment を作り、nginx という Service で公開します。kubectl create deployment は第5回で紹介した命令的なコマンドで、Pod の数を指定しなければ 1 つになります。kubectl expose は第21回で触れた、Deployment のセレクタをそのまま使って Service を作るコマンドです。

console
$ kubectl create deployment nginx --image=nginx --context kind-multi-node
$ kubectl expose deployment nginx --port=80 --context kind-multi-node

参考:Declare Network Policy | Kubernetes、kubectl create deployment | Kubernetes、kubectl expose | Kubernetes

公式のタスクの例では、2 つのコマンドの出力は、それぞれ次のとおりです。

deployment.apps/nginx created
service/nginx exposed

参考:Declare Network Policy | Kubernetes

続けて、Service と Pod を一覧します。

console
$ kubectl get svc,pod --context kind-multi-node

公式のタスクの例では、出力は次のとおりです。

NAME                        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/kubernetes          10.100.0.1    <none>        443/TCP    46m
service/nginx               10.100.0.16   <none>        80/TCP     33s

NAME                        READY         STATUS        RESTARTS   AGE
pod/nginx-701339712-e0qfq   1/1           Running       0          35s

参考:Declare Network Policy | Kubernetes

例には TYPE 列がありませんが、手元では第21回の例と同じく TYPE 列が表示されることがあると考えられます。service/nginx の CLUSTER-IP 列の値は例とは違い、kind(v0.33.0 時点)の既定の Service の範囲 10.96.0.0/16 に入るはずです。この値を <nginx の CLUSTER-IP> として控えておきます。Pod の名前の nginx- より後ろの部分も手元では違うものになります。Pod の STATUS 列が Running になるまで、少し待ってからもう一度実行します。

参考:Configuration | kind

この段では、次の 2 点が合っていれば成功です。

  • service/nginx の行があり、CLUSTER-IP 列が 10.96. で始まる
  • nginx- で始まる名前の Pod の STATUS 列が Running になる

通信元の Pod を作り、ラベルを確かめる

2 つ目の端末で、通信元の 2 つの Pod のマニフェストを渡します。

console
$ kubectl apply -f busybox.yaml --context kind-multi-node
$ kubectl apply -f busybox-access.yaml --context kind-multi-node

pod/busybox と pod/busybox-access が作られたことを知らせる行が出るはずです。続けて、Pod をラベルの列つきで一覧します。--show-labels は第6回で使った、ラベルを最後の列に表示する指定です。

console
$ kubectl get pods --show-labels --context kind-multi-node

参考:kubectl get | Kubernetes

3 つの Pod の STATUS 列が Running になったら、LABELS 列を見ます。公式のタスクによると、nginx の Deployment の Pod にはラベル app=nginx が自動で付いています。手元でも、nginx- で始まる Pod の LABELS 列に app=nginx があるはずです(第6回で見た pod-template-hash のラベルも並ぶと考えられます)。busybox-access には access=true があり、busybox にはラベルが無いはずです。

参考:Declare Network Policy | Kubernetes

この段では、次の 3 点が合っていれば成功です。

  • 3 つの Pod の STATUS 列が Running になる
  • nginx- で始まる Pod の LABELS 列に app=nginx がある
  • busybox-access の LABELS 列に access=true があり、busybox には無い

ポリシーを作る前に問い合わせる

2 つ目の端末で、2 つの Pod の中で wget を動かし、nginx の Service の cluster IP に問い合わせます。<nginx の CLUSTER-IP> には控えた値を入れます。

console
$ kubectl exec busybox --context kind-multi-node -- wget --spider --timeout=1 <nginx の CLUSTER-IP>
$ kubectl exec busybox-access --context kind-multi-node -- wget --spider --timeout=1 <nginx の CLUSTER-IP>

参考:kubectl exec | Kubernetes、Declare Network Policy | Kubernetes

wget の指定は公式のタスクと同じです。--spider は一般に、中身をダウンロードせず、相手にファイルがあるかどうかだけを確かめる指定です。--timeout=1 は一般に、1 秒で応答が無ければあきらめる指定です。公式のタスクは Service の名前の nginx を宛先にしていますが、名前で接続すると、名前を引く DNS(第23回)への問い合わせも通信に加わります。この回では確かめる通信を 1 つに絞るため、第21回と同じく cluster IP を宛先にします。

公式のタスクの例では、ポリシーを作る前の問い合わせの出力は次のとおりです。

Connecting to nginx (10.100.0.16:80)
remote file exists

参考:Declare Network Policy | Kubernetes

例の 1 行目の nginx は宛先に書いた名前です。手元では宛先に cluster IP を書くので、この部分は控えた IP アドレスになると考えられます。どちらの Pod からも、2 行目に remote file exists が表示されれば、nginx に届いています。まだ NetworkPolicy が 1 つも無いので、nginx の Pod は隔離されておらず、すべての入ってくる接続が許されるからです。

参考:Network Policies | Kubernetes

この段では、次の点が合っていれば成功です。

  • busybox と busybox-access のどちらからも、remote file exists が表示される

NetworkPolicy を作る

2 つ目の端末で、NetworkPolicy のマニフェストを渡します。

console
$ kubectl apply -f nginx-policy.yaml --context kind-multi-node

公式のタスクの例では、出力は次のとおりです。

networkpolicy.networking.k8s.io/access-nginx created

参考:Declare Network Policy | Kubernetes

1 つ目の端末には、access-nginx という名前の行が現れるはずです。この行が現れたことからわかるのは、NetworkPolicy のオブジェクトが保存されたことまでです。「通信を実際に止めるのはネットワークプラグイン」で見たとおり、ネットワークプラグインがポリシーを処理したかどうかは、Kubernetes の API からはわかりません。

続けて、Kubernetes がポリシーをどう解釈したかを表示します。公式ドキュメントが迷ったときに使うよう勧めている方法です。

console
$ kubectl describe networkpolicy access-nginx --context kind-multi-node

参考:Network Policies | Kubernetes、kubectl describe | Kubernetes

表示の中に、ポリシーが選ぶ Pod のラベル app=nginx と、許す相手のラベル access=true が見つかるはずです。policyTypes を書かなかったので、ポリシーの向きは Ingress と表示されると考えられます。表示の形は公式ドキュメントに例が無いため、ここでは引用しません。

ポリシーを作ったあとに問い合わせる

2 つ目の端末で、まずラベルの無い busybox から問い合わせます。

console
$ kubectl exec busybox --context kind-multi-node -- wget --spider --timeout=1 <nginx の CLUSTER-IP>

公式のタスクによると、正しいラベルを持たない Pod からの問い合わせはタイムアウトします。タスクの例では、出力は次のとおりです。

Connecting to nginx (10.100.0.16:80)
wget: download timed out

参考:Declare Network Policy | Kubernetes

1 行目の nginx の部分は手元では控えた IP アドレスになると考えられます。wget: download timed out の行のあとに、kubectl がコマンドの失敗を知らせる行を出すこともあると考えられます。タイムアウトするのは、nginx の Pod がポリシー access-nginx に選ばれ、入ってくる通信について隔離されたので、許す規則に当てはまらない busybox からの接続が許されなくなったためです。busybox から出ていく通信はどのポリシーにも制限されていないので、止められたのは nginx の Pod の側だと考えられます。

参考:Network Policies | Kubernetes

ポリシーを作った直後に問い合わせると、まだ remote file exists が表示されることもあると考えられます。ネットワークプラグインが新しいポリシーを処理するまでに、時間がかかることがあるからです。その場合は、数秒待ってからもう一度問い合わせます。

参考:Network Policies | Kubernetes

数十秒待っても remote file exists が表示され続けるなら、次の 3 点を確かめます。

  • kubectl describe networkpolicy access-nginx --context kind-multi-node の表示に、app=nginx と access=true がある
  • kubectl get pods --show-labels --context kind-multi-node で、nginx の Pod の LABELS 列に app=nginx がある
  • kind version で表示されるバージョンが v0.24.0 以降で、クラスタを作るときに disableDefaultCNI を書いていない

3 点とも合っているのに届き続けるなら、このクラスタではネットワークプラグインが NetworkPolicy を実装していない可能性があると考えられます。その場合は、「通信を実際に止めるのはネットワークプラグイン」で見た「API はあっても効果がない」状態の例になります(「ネットワークプラグインを替えて試す」の表の 2 行目)。

参考:Network Policies | Kubernetes、Release v0.24.0 · kubernetes-sigs/kind

続けて、ラベル access=true を持つ busybox-access から問い合わせます。

console
$ kubectl exec busybox-access --context kind-multi-node -- wget --spider --timeout=1 <nginx の CLUSTER-IP>

公式のタスクによると、正しいラベルを持つ Pod からの問い合わせは許されます。タスクの例では、出力は次のとおりです。

Connecting to nginx (10.100.0.16:80)
remote file exists

参考:Declare Network Policy | Kubernetes

1 行目の nginx の部分は、ここでも手元では控えた IP アドレスになると考えられます。busybox-access は ingress の規則の podSelector(access: "true")に当てはまるので、接続が許されます。nginx から返ってくる応答も、許された接続の応答なので、逆向きの規則を書かなくても暗黙のうちに許されます。

参考:Network Policies | Kubernetes

通信元の Pod は Service の cluster IP に接続していますが、第21回で見たとおり、その通信は kube-proxy の規則で nginx の Pod へ振り向けられます。kind(v0.33.0 時点)のクラスタの kube-proxy は、既定では iptables のモードで動きます(第21回)。公式ドキュメントの iptables のモードの例では、パケットはクライアントの IP アドレスを書き換えずにバックエンドへ転送されます。この回のクラスタでも同じなら、nginx の Pod に届く通信の送り元は、通信元の Pod の IP アドレスのままだと考えられます。ネットワークプラグインは API にある Pod の IP アドレスとラベルを突き合わせて、その送り元がどのラベルを持つ Pod かを見分けていると考えられます。「from と to で相手を選ぶ」で見たとおり、アドレスの書き換えと NetworkPolicy の処理の順番は公式ドキュメントでは決められていません。ただ、公式のタスクも Service を通して確かめているので、Service を通した通信にも nginx の Pod のポリシーが効くことが期待できます。

参考:Configuration | kind、Virtual IPs and Service Proxies | Kubernetes、Network Policies | Kubernetes、Declare Network Policy | Kubernetes

この段では、次の 2 点が合っていれば成功です。

  • busybox からの問い合わせで、wget: download timed out が表示される
  • busybox-access からの問い合わせで、remote file exists が表示される

通信元の Pod にラベルを付ける

NetworkPolicy は通信の相手もラベルで選びます。ポリシーを変えずに、busybox にラベル access=true を付けてみます。kubectl label は第11回で使った、ラベルを書き換えるコマンドです。busybox にはまだ access のラベルが無いので、--overwrite は要りません。

console
$ kubectl label pod busybox access=true --context kind-multi-node

参考:kubectl label | Kubernetes

pod/busybox labeled のような 1 行が表示されるはずです。少し待ってから、busybox からもう一度問い合わせます。

console
$ kubectl exec busybox --context kind-multi-node -- wget --spider --timeout=1 <nginx の CLUSTER-IP>

今度は remote file exists が表示されるはずです。busybox がポリシーの podSelector(access: "true")に当てはまるようになったからです。公式ドキュメントによると、ネットワークプラグインはポリシーを分散して実装することがあり、Pod やポリシーが変わったときに一時的に食い違った状態が見えることがあります。すぐに届かなければ、数秒待ってからもう一度試します。

参考:Network Policies | Kubernetes、Network Policies | Kubernetes

この段では、次の点が合っていれば成功です。

  • ラベルを付けたあとの busybox からの問い合わせで、remote file exists が表示される

ネットワークプラグインを替えて試す

ここまでの結果は、kind の既定の kindnetd が NetworkPolicy を実装している、という前提での予想です。ネットワークプラグインによって結果が変わることを確かめたい場合は、kindnetd を入れないクラスタを別に作り、ほかのネットワークプラグインを入れて同じ手順を試します。この段は試したい人向けです。特に「効かない」側を確かめるには、NetworkPolicy を実装していないネットワークプラグインを自分で調べて入れる必要があり、この記事の範囲を超えます。試さなくても、この段の最後の表の 2 行目の状態が理解できれば十分です。

ここからは custom-cni という 2 つ目のクラスタを作ります。手元のマシンの負担を減らしたいときは、「片付け」の手順で multi-node のクラスタを先に消してから作ってもかまいません。

kind の公式ドキュメントの設定の例に、第2回のノードの構成を足した次の設定ファイルを使います。networking の部分は公式ドキュメントの例のままです。kind-custom-cni.yaml という名前で保存します。

yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
networking:
  # the default CNI will not be installed
  disableDefaultCNI: true

参考:Configuration | kind、Quick Start | kind

このファイルで、custom-cni という名前のクラスタを作ります。

console
$ kind create cluster --name custom-cni --config kind-custom-cni.yaml
$ kubectl get daemonsets -n kube-system --context kind-custom-cni

disableDefaultCNI を true にしたので、2 つ目のコマンドの一覧には kindnet が無いはずです。この時点のクラスタには、ネットワークプラグインがありません。kind は各ノードを kubeadm で初期化します(第2回)。kubeadm の公式ドキュメントによると、Pod どうしが通信するには CNI を使う Pod のネットワークのアドオンを入れる必要があり、ネットワークを入れるまでクラスタの DNS(CoreDNS)は起動しません。そのため、この時点で CoreDNS の Pod が動き出さないのは想定どおりです。1 つのクラスタに入れられる Pod のネットワークは 1 つだけです。ノードの STATUS 列もネットワークプラグインを入れるまでは Ready にならないことがあると考えられます。kind の公式ドキュメントにこの点の説明はありませんが、ネットワークプラグインを入れる前の状態なので、慌てずに次へ進んでかまいません。

参考:Configuration | kind、Creating a cluster with kubeadm | Kubernetes

続けて、使いたいネットワークプラグインを、そのプラグインの公式ドキュメントの手順で入れます。入れ方はプラグインごとに違い、kind の公式ドキュメントにも手順は無いので、この記事では示しません。kind の公式ドキュメントは、Calico などよく使われる CNI のマニフェストの多くは動くことがわかっている、としています。ただし、慣れた人向けの機能でサポートは限られます。

参考:Configuration | kind

ネットワークプラグインを入れて、ノードの STATUS 列が Ready になったら、1 つ目の端末の監視を Ctrl+C で止め、--context kind-custom-cni に替えて始め直します。

console
$ kubectl get networkpolicies --watch --context kind-custom-cni

そのうえで 2 つ目の端末で、「nginx の Deployment と Service を作る」から「ポリシーを作ったあとに問い合わせる」までを、--context kind-custom-cni に替えて試します。マニフェストのファイルは前のクラスタで使ったものをそのまま使えます。結果は、入れたネットワークプラグインが NetworkPolicy を実装しているかどうかで、次のように分かれると考えられます。

入れたネットワークプラグインポリシーを作ったあとの busybox からの問い合わせ
NetworkPolicy を実装しているkindnetd のときと同じく、タイムアウトする
NetworkPolicy を実装していないkubectl apply も監視の行も同じように出るが、remote file exists が表示され、届いたままになる

参考:Services, Load Balancing, and Networking | Kubernetes、Network Policies | Kubernetes

表の 2 行目では、NetworkPolicy のオブジェクトは作られ、一覧にも表示されるのに、通信は何も変わりません。公式ドキュメントが「API はあっても効果がない」と書いている状態です。公式のタスクの前提の節は NetworkPolicy に対応したネットワークの仕組みの例として、Antrea、Calico、Cilium、Kube-router を挙げています。NetworkPolicy への対応はプラグインごとに、それぞれの公式ドキュメントで確かめてください。

参考:Services, Load Balancing, and Networking | Kubernetes、Declare Network Policy | Kubernetes

片付け

1 つ目の端末で Ctrl+C を押して、監視を止めます。

「ネットワークプラグインを替えて試す」の前に multi-node のクラスタを消した場合は、次の削除と確認のコマンドは飛ばして、最後のクラスタの削除へ進みます。custom-cni のクラスタの中に作ったものは、クラスタと一緒に消えると考えられます。

2 つ目の端末で、作った NetworkPolicy と Pod はマニフェストを指定して、Service と Deployment は名前を指定して削除します。Service と Deployment はコマンドで作ったので、マニフェストのファイルがありません。

console
$ kubectl delete -f nginx-policy.yaml --context kind-multi-node
$ kubectl delete -f busybox.yaml --context kind-multi-node
$ kubectl delete -f busybox-access.yaml --context kind-multi-node
$ kubectl delete service nginx --context kind-multi-node
$ kubectl delete deployment nginx --context kind-multi-node

参考:kubectl delete | Kubernetes

Deployment を消すと、その ReplicaSet と Pod も消えます(第6回)。busybox と busybox-access を消すコマンドは、Pod の終了までの猶予の時間(既定では 30 秒)のぶん、終わるまで少しかかることがあります(第20回)。

参考:Garbage Collection | Kubernetes、Pod Lifecycle | Kubernetes

少し待ってから、残っていないかを確かめます。

console
$ kubectl get deployments,pods,networkpolicies --context kind-multi-node
$ kubectl get services --context kind-multi-node

Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。1 つ目のコマンドで、見つからないことを知らせる 1 行が出れば、削除できています。2 つ目のコマンドでは、nginx は表示されず、kubernetes という Service だけが表示されると考えられます。kubernetes はこの回で作ったものではないので、消さずに残します(第21回)。

最後に、クラスタを削除し、消えたことを確かめます。「ネットワークプラグインを替えて試す」で custom-cni のクラスタを作った場合は、2 つ目のコマンドで、そのクラスタも削除します。

console
$ kind delete cluster --name multi-node
$ kind delete cluster --name custom-cni
$ kind get clusters

kind delete cluster は、存在しないクラスタを指定してもエラーを返しません。kind get clusters に multi-node と custom-cni が表示されなくなったことを確かめます。kubeconfig に kind-multi-node や kind-custom-cni のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context <コンテキストの名前> で消します。

参考:Quick Start | kind、kubectl config | Kubernetes

注意点

NetworkPolicy でつまずきやすい点と、バージョンで変わり得る点を、次の表にまとめます。

項目内容
ネットワークプラグインが対応していないと効果がないNetworkPolicy を作れても、実装するネットワークプラグインが無ければ効果がない。NetworkPolicy には status が無く、効いたかどうかもいつ効いたかも、Kubernetes の API からはわからない
kind の既定のクラスタでの対応kind は v0.24.0 から、kube-network-policies を使って NetworkPolicy に対応したとリリースノートにある。v0.33.0 まで、対応を外した記載は見当たらない
ポリシーに選ばれていない Pod は制限されないPod が隔離されるのは、その Pod を選び、その向きを policyTypes に含むポリシーがあるときだけ
規則は許可を足すだけ明示的に拒否する規則は書けない。複数のポリシーの許可は足し合わされ、評価の順番は結果に影響しない
policyTypes を書かないと Ingress だけになることがあるIngress はいつも入り、Egress は egress の規則があるときだけ入る。出ていく通信を制限するポリシーには Egress を明示する
接続には両方の側の許可が要る送り元の egress と宛先の ingress の両方が許している必要がある
from の書き方で「かつ」と「または」が変わるnamespaceSelector と podSelector を 1 つの項目に書くと「かつ」、別々の項目に書くと「または」
出ていく通信をすべて止めると DNS も止まるDNS を使うなら、クラスタの DNS への出ていく通信を許すポリシーを別に足す必要がある(第23回)
TCP、UDP、SCTP 以外の扱いは決まっていないICMP や ARP は、ネットワークプラグインによって許されたり拒否されたりする
自分のノードからの通信と localhost は止められないv1.37 時点の NetworkPolicy の API には、この機能が無い
作った直後は効いていないことがあるネットワークプラグインがポリシーを処理するまで時間がかかることがある。新しい Pod が起動直後に通信できないこともある
続いている接続への影響は実装次第ポリシーやラベルを変えたとき、続いている接続に変更が及ぶかはネットワークプラグインによる

参考:Network Policies | Kubernetes、NetworkPolicy | Kubernetes API Reference、Release v0.24.0 · kubernetes-sigs/kind、Releases · kubernetes-sigs/kind

関連記事

出ていく通信を制限するときに気を付けたい、クラスタの中の DNS については、以下の記事で解説しています。

基本仕様 ・ 第23回 なぜ Service 名で通信できるのかService の名前を IP アドレスに変えるクラスタ DNS の仕組みを図で説明します。CoreDNS と kube-dns の Service、kubelet が Pod に書き込む /etc/resolv.conf の search と ndots、Namespace をまたぐ名前の書き方を押さえ、kind のクラスタで名前を引いて確かめる手順を紹介します。

namespaceSelector で選ぶ Namespace と、Namespace で分けられるものと分けられないものについては、以下の記事で解説しています。

基本仕様 ・ 第37回 Namespace で分けられるもの、分けられないもの1 つのクラスタの中でリソースをグループに分ける Namespace の仕組みを図で説明します。名前の範囲になること、Namespace に属さないクラスタ全体のリソース、既定では Namespace をまたいで届く通信、Namespace を消すと中身も消える流れを押さえ、kind のクラスタで確かめる手順を紹介します。

Kubernetes 基本仕様の目次へ