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.ingress と spec.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
注釈
-
手元ではこれで動かしているのですが、なぜ Pod 名まで絞れなかったのかは忘れました。もう少し工夫すると namespace 全体の指定は無くせそうです。 ↩
-
この
exceptの指定はおそらく不十分で、Mastodon の実装などを見ていると他の IP アドレスも除外する必要がありそうです。 ↩ -
正直なぜそう扱われるのかはよく分かっていません。 ↩