GKE で FQDN による Network Policy を定義する

GKE において、FQDN に基づく Network Policy が定義できるようになりました。
この記事では、FQDN Network Policy の概要と、実際に使ってみた様子をご紹介したいと思います。

リリースノートはこちら。

cloud.google.com

FQDN Network Policy 登場の背景と概要

GKE では現在 2 つのネットワーキングの実装(Calico と Dataplane V2 (cillium))1 が提供されており、iptable に基づく制御か、eBPF に基づく制御か、といった違いはあるものの、いずれも Kubernetes Network Policy を利用することができました。

Kubernetes Network Policy では、クラスタ内における Pod 間通信であれば、namespaceSelector や podSelector といったラベルで送信元や宛先のワークロードを選択することができます。
一方、送信元や宛先がクラスタ外となる場合には、ipBlocks で対象の IP アドレスレンジを指定する以外に Network Policy で通信を制御する方法はありませんでした。

今回新たに登場した FQDN Network Policy では、Pod から送信する通信の宛先がクラスタ外となる場合に、FQDN で宛先を指定することができるようになりました。
これにより、宛先の IP アドレスが変動するような場合でも、FQDN に基づいて名前解決ができれば Network Policy を変更することなく下り通信(クラスタ内からクラスタ外への通信)を制御することができます。
許可する FQDN の指定には、正規表現を用いたパターンマッチングを利用することも可能となっています。

現時点では許可設定のみ行うことができ、明示的な拒否設定はできません。
Kubernetes Network Policy と同様、ポリシーを適用すると、対象のワークロードは許可された宛先以外には通信できなくなります(未許可のドメインに対しても dig や nslookup による名前解決は行うことができますが、実際には解決された IP アドレス宛てに通信することができなくなります)。

ちなみに、この記事の執筆時点ではプレビューでの提供となっていますが、今後 GA した際には有料の機能となるようです。

FQDN network policy is a paid feature, but payment will not be required at this time until FQDN network policy becomes a generally available offering.

引用元

利用にあたり、現時点では以下のような制約があります。
(なお、制約の正確なリストは 公式ドキュメント を参照ください)

  • 1.26.4-gke.500+ もしくは 1.27.1-gke.400+ の GKE が必要(Standard でも Autopilot でも利用可能)
  • 対象の GKE クラスタで Dataplane V2 が有効化されていること
  • GKE のクラスタDNS の機能である kube-dns または Cloud DNS が使われていること(自己管理の DNS サーバはサポート対象外)
  • Anthos Service Mesh を有効化している環境では利用できない
  • FQDNNetworkPolicy リソースのネットワーク ポリシー ロギングはサポートされていない

実機検証

今回は 1.26.4-gke.500 の GKE Standard のクラスタを用意しました。
FQDN Network Policy の有効化は、クラスタの作成時でも、作成済みのクラスタに後からでも行えます。
今回は FQDN Network Policy を有効化せずに既に作成していたクラスタに対して、後から有効化してみることにします。

$ gcloud container clusters describe sample-cluster
currentMasterVersion: 1.26.4-gke.500
currentNodeVersion: 1.26.4-gke.500
name: sample-cluster
networkConfig:
  dnsConfig:
    clusterDns: CLOUD_DNS
    clusterDnsScope: CLUSTER_SCOPE
  enableFqdnNetworkPolicy: true
  ...

$ gcloud beta container clusters update sample-cluster --enable-fqdn-network-policy
Updating sample-cluster ...done.                                                                                                                                          
Updated [https://container.googleapis.com/v1beta1/projects/**********/zones/us-central1-c/clusters/sample-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/...
    
$ kubectl rollout restart ds -n kube-system anetd
daemonset.apps/anetd restarted

上記の手順によってクラスタに FQDNNetworkPolicy というリソースを扱うための CRD が展開されます。
展開された CRD は kubectl get/describe crd コマンドなどで確認することができます。

$ kubectl describe crd fqdnnetworkpolicies.networking.gke.io
Name:         fqdnnetworkpolicies.networking.gke.io
Namespace:    
Labels:       addonmanager.kubernetes.io/mode=Reconcile
Annotations:  components.gke.io/layer: addon
              controller-gen.kubebuilder.io/version: v0.6.2
API Version:  apiextensions.k8s.io/v1
Kind:         CustomResourceDefinition
Spec:
  Conversion:
    Strategy:  None
  Group:       networking.gke.io
  Names:
    Kind:       FQDNNetworkPolicy
    List Kind:  FQDNNetworkPolicyList
    Plural:     fqdnnetworkpolicies
    Short Names:
      fqdnnp
    Singular:  fqdnnetworkpolicy
  Scope:       Namespaced
  ...

kube-system に展開されている anetd (Dataplane V2 を構成するサービスのひとつ)を再起動する必要がありますので注意しましょう。
(私はこの手順を見逃して少々の時間を溶かしました)

準備ができたので FQDNNetworkPolicy を定義して適用してみましょう。
動作確認のために Nginx のサンプルアプリも展開します。

$ cat fqdnnetpol.yaml 
apiVersion: networking.gke.io/v1alpha1
kind: FQDNNetworkPolicy
metadata:
  name: allow-out-fqdnnp
spec:
  podSelector:
    matchLabels:
      app: curl-client
  egress:
  - matches:
    - pattern: "*.yourdomain.com"
    - name: "www.google.com"
    ports:
    - protocol: "TCP"
      port: 443

$ kubectl apply -f fqdnnetpol.yaml 
fqdnnetworkpolicy.networking.gke.io/allow-out-fqdnnp created

$ kubectl run nginx --image nginx
pod/nginx created

$ kubectl get pod nginx --show-labels
NAME    READY   STATUS    RESTARTS   AGE     LABELS
nginx   1/1     Running   0          3d23h   app=curl-client

今回はコンテナに kubectl exec コマンドでログインして動作を確認します。
まずは FQDNNetworkPolicy で許可したドメインと、そうでないドメインへそれぞれ nslookup および curl コマンドを実行してみましょう。

$ kubectl exec nginx -it -- bash

root@nginx:/# nslookup www.google.com
Server:         169.254.169.254
Address:        169.254.169.254#53

Non-authoritative answer:
Name:   www.google.com
Address: 142.250.1.99
Name:   www.google.com
Address: 142.250.1.104
Name:   www.google.com
Address: 142.250.1.105
Name:   www.google.com
Address: 142.250.1.106
Name:   www.google.com
Address: 142.250.1.147
Name:   www.google.com
Address: 142.250.1.103
Name:   www.google.com
Address: 2607:f8b0:4001:c0f::67
Name:   www.google.com
Address: 2607:f8b0:4001:c0f::69
Name:   www.google.com
Address: 2607:f8b0:4001:c0f::93
Name:   www.google.com
Address: 2607:f8b0:4001:c0f::68

root@nginx:/# curl -I -m 3 https://www.google.com
HTTP/2 200 
...

root@nginx:/# nslookup www.cloudflare.com
Server:         169.254.169.254
Address:        169.254.169.254#53

Non-authoritative answer:
Name:   www.cloudflare.com
Address: 104.16.123.96
Name:   www.cloudflare.com
Address: 104.16.124.96
Name:   www.cloudflare.com
Address: 2606:4700::6810:7c60
Name:   www.cloudflare.com
Address: 2606:4700::6810:7b60

root@nginx:/# curl -I -m 3 https://www.cloudflare.com
curl: (28) Connection timed out after 3000 milliseconds

期待どおり、許可されたドメインにのみアクセスできる状態となっています。
許可されていないドメインへの通信はエラー返却ではなくドロップになるため、アプリケーションの視点ではリトライやタイムアウトの上限に達するまで通信の失敗に気付けない点に注意が必要です。

ちなみに、適用したポリシーはプロトコルを指定した許可設定のため、http (port 80) でアクセスしようとすると、許可されたドメインであっても通信は失敗(ドロップ)します。

root@nginx:/# curl -I -m 3 http://www.google.com
curl: (28) Connection timed out after 3000 milliseconds

なお、プロトコルTCP, UDP, ALL の 3 つから選択でき、無指定の場合は ALL になります。
ポリシーの ports フィールド以下を省略して、プロトコルやポート番号を問わない設定とすれば、http でも対象のドメインにアクセスできるようになります。

$ kubectl explain FQDNNetworkPolicy.spec.egress.ports.protocol
GROUP:      networking.gke.io
KIND:       FQDNNetworkPolicy
VERSION:    v1alpha1

FIELD: protocol <string>

DESCRIPTION:
    Protocol is the L4 protocol. Valid options are "TCP", "UDP", or "ALL". If
    Protocol is missing or empty, it defaults to allowing all protocols.

$ cat fqdnnetpol.yaml 
apiVersion: networking.gke.io/v1alpha1
kind: FQDNNetworkPolicy
metadata:
  name: allow-out-fqdnnp
spec:
  podSelector:
    matchLabels:
      app: curl-client
  egress:
  - matches:
    - pattern: "*.yourdomain.com"
    - name: "www.google.com"

$ kubectl apply -f fqdnnetpol.yaml 
fqdnnetworkpolicy.networking.gke.io/allow-out-fqdnnp configured

$ kubectl exec nginx -it -- bash
root@nginx:/# curl -I -m 3 http://www.google.com
HTTP/1.1 200 OK
...

利用時の注意点

公式ドキュメントでも言及されていますが、FQDN Network Policy と Kubernetes Network Policy には上下関係がありません。
そのため、同じワークロードに対して両方のポリシーが定義されている場合は、どちらかひとつでも一致する許可設定があればその通信は許可(クラスタ外に送信)されます。
どちらか一方だけのポリシーを見ても許可設定の全量が把握できないため、利用する際は注意が必要です。

まとめ

GKE で FQDN に基づく Network Policy が利用できるようになりました。
クラスタ外の通信先の IP アドレスが動的に変動するなどの場合に活躍してくれそうな機能ですね。

※ 2023/6/29 追記 記事公開の直前に Autopilot でも利用可能となったとアナウンスがあったため、GKE Standard のみ利用可能としていた記載を更新しました


  1. 厳密には、Calico と Dataplane V2 の他に GKE のネイティブ CNI という実装もありますが、こちらは Network Policy を利用できないためここでは割愛しています