blog.anqou.net
rss
author
tags

k3s で NetworkPolicy を設定する

k3s では NetworkPolicy リソースを使い、Pod の通信を制限することができます。自分はわくわく鮟鱇ランドなどのサービスを k3s で提供するにあたり、デフォルトでは全ての通信を遮断した上で、必要な通信のみを許可するような NetworkPolicy を書いて運用しています。以下ではその環境で便利に使っている NetworkPolicy を紹介します。なおこの記事では(地の文の)NetworkPolicy を netpol と略します。紹介する netpol は k3s 1.34.5+k3s1 で動作確認しています。

まずベースとなる、全ての通信を遮断するための netpol は以下のように書けます。これを各 Namespace に配置します:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: your-system
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

必要に応じて Pod の通信を許可します。ポリシーには ingress と egress があり、適切に使い分けます。例えばウェブアプリが PostgreSQL にアクセスするなら以下のような egress の netpol を書きます:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: np
  namespace: your-system
spec:
  egress:
    - ports:
        - port: 5432
          protocol: TCP
      to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: postgres
          podSelector:
            matchLabels:
              app.kubernetes.io/name: postgresql
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Egress

k3s で使用できる Traefik で Ingress を立ち上げてサービスをインターネットに公開する場合、Traefik 経由で Pod に通信がやってくるので、この通信を許可する必要があります。Traefik は kube-system ns にいるので、以下のような ingress の netpol を書きます:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: np
  namespace: your-system
spec:
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              app.kubernetes.io/name: traefik
      ports:
        - port: 8000 # 接続先の Pod のポート番号を指定
          protocol: TCP
  podSelector:
    matchLabels:
      app: web
  policyTypes:
    - Ingress

以下では簡単のため spec.ingressspec.egress の中身のみを書きます。

名前解決を行う場合 kube-system ns にある Pod の 53 番ポートに UDP でアクセスする必要があります[1]

egress:
  - ports:
      - port: 53
        protocol: UDP
    to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system

Kubernetes の API にアクセスする controller や operator のような Pod の場合、k3s では default ns にある kubernetes EndpointSlice が指すエンドポイントにアクセスします。そこで、この IP アドレスを確認したうえで egress に指定します:

egress:
  - ports:
      - port: 6443
        protocol: TCP
    to:
      - ipBlock:
          cidr: 203.0.113.1/32 # 適切に指定

インターネットに繋ぎに行く Pod の場合は、以下のようにローカルの IP アドレスを外したような ipBlock を指定します[2]

egress:
  - to:
      - ipBlock:
          cidr: 0.0.0.0/0
          except:
            - 10.0.0.0/8
            - 192.168.0.0/16
            - 172.16.0.0/20

なお Pod が自分自身が提供するサービスにアクセスする場合、つまり example.com をサーブする Pod が自ら https://example.com に繋ぎに行くような場合は上記の ipBlock の指定では不十分です。というのも、このようなケースでは Traefik の Pod へのアクセスとして扱われるためです[3]。そのため以下のように Traefik が使用する 8000 と 8443 番ポートへの通信を許可する必要があります:

egress:
  - ports:
      - port: 8000
        protocol: TCP
      - port: 8443
        protocol: TCP
    to:
      - namespaceSelector:
          matchLabels:
            kubernetes.io/metadata.name: kube-system
        podSelector:
          matchLabels:
            app.kubernetes.io/name: traefik

注釈

  1. 手元ではこれで動かしているのですが、なぜ Pod 名まで絞れなかったのかは忘れました。もう少し工夫すると namespace 全体の指定は無くせそうです。

  2. この except の指定はおそらく不十分で、Mastodon の実装などを見ていると他の IP アドレスも除外する必要がありそうです。

  3. 正直なぜそう扱われるのかはよく分かっていません。