↓メインコンテンツへスキップ
  1. Blogs/

TerraformでGKE Dataplane V2を有効化し、NetworkPolicyでPod間通信を遮断してみた

2 分
Terraform GoogleCloud GKE NetworkPolicy
0222-nnn
著者
0222-nnn
猫が好き
目次
Terraform-GoogleCloud - この記事は連載の一部です
パート 28: この記事

概要
#

GKEプライベートクラスタと踏み台の構成は、デフォルトのデータプレーン(kube-proxy、iptablesベース)を使っていました。同じネームスペースに複数チームのアプリが同居する場合、Pod間の通信を意図したものだけに制限したい場面があります。

GKE Dataplane V2(Cilium/eBPFベース)は、NetworkPolicyの強制がビルトインされています。追加のCNI(Calico等)を用意しなくても、ラベルベースでPod間通信を制限できます。

「Dataplane V2を有効化した」ことと「NetworkPolicyが実際に意図通り機能する」ことは別です。許可したラベルのPodからは通り、許可していないPodからは遮断されることを、実際にPodを起動して確認します。

Terraformの基本操作と、前々回のGKEプライベートクラスタ+踏み台構成は前提としています。

Filestore CSI、GCS FUSE CSI、Secret Manager CSI、NodeLocal DNSCache、詳細なLogging/Monitoring設定は扱いません。Dataplane V2とNetworkPolicyの検証に範囲を絞ります。

検証すること
#

  • datapath_provider = "ADVANCED_DATAPATH"でDataplane V2を有効化できること
  • 許可したラベルのPodからは、NetworkPolicyで保護されたPodへ通信できること
  • 許可していないPodからは、実際に通信が遮断されること(apply成功とは別の確認)

前提環境
#

  • Google Cloud CLI、Terraform、Docker
  • Billingが有効な検証用Google Cloud Project
  • GKE、Compute Engineを操作できるGoogleアカウント
  • 検証時のバージョン:Terraform 1.14.3、google 7.46.0

使用するTerraformコード
#

通信がどこで遮断されるか
#

flowchart LR
    subgraph NODE["GKE ノード"]
        A["Pod A
label: role=client"] EBPF["Dataplane V2
eBPF による適用点"] B["Pod B
label: role=server"] end NP["NetworkPolicy
podSelector: role=server
ingress: role=client のみ"] NP -.->|"ルールを配る"| EBPF A -->|"許可"| EBPF --> B C["Pod C
label なし"] -->|"遮断"| EBPF

NetworkPolicy はルールを宣言するだけで、実際に落とすのは Dataplane V2 側です。

Dataplane V2とNetworkPolicyの関係(一次情報での裏取り)
#

Dataplane V2を有効にする際、Google Cloudの公式ドキュメントには次のように明記されています。

GKE Dataplane V2 comes with Kubernetes network policy enforcement built-in. This means that you don’t need to enable network policy in clusters that use GKE Dataplane V2.

別途Calicoベースのnetwork_policyアドオンを有効にする必要はなく、むしろ設定してはいけません。 両方を有効にすると、次のエラーでapplyが失敗します。

Enabling NetworkPolicy for clusters with DatapathProvider=ADVANCED_DATAPATH is not allowed.

GKEクラスタの設定
#

resource "google_container_cluster" "primary" {
  # ...

  datapath_provider = "ADVANCED_DATAPATH"

  # network_policy ブロックは設定しない(Dataplane V2にビルトイン済み)
}

サーバPod・Service・NetworkPolicy
#

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: netpol-server-allow-labeled-only
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: netpol-server
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: netpol-allowed
      ports:
        - protocol: TCP
          port: 8080

podSelectorで保護対象のPod(app: netpol-server)を指定し、ingress.from.podSelectorで許可する送信元(role: netpol-allowed)を指定します。この2つのラベルが一致しない限り、通信はブロックされます。

設定と実行
#

cd Advanced-Examples/16-gke-dataplane-v2-networkpolicy
cp terraform.tfvars.example terraform.tfvars
project_id = "YOUR_PROJECT_ID"
iap_member = "user:YOUR_ACCOUNT"
terraform init
terraform fmt -check
terraform validate
terraform plan
terraform apply
Apply complete! Resources: 25 added, 0 changed, 0 destroyed.

Dataplane V2が実際に有効化されたかは、コンソールの表示ではなくgcloudの出力で確認します。

gcloud container clusters describe "$(terraform output -raw cluster_name)" \
  --zone="$(terraform output -raw zone)" --format="value(networkConfig.datapathProvider)"
ADVANCED_DATAPATH

サーバとNetworkPolicyをデプロイする
#

gcloud compute scp k8s/netpol-demo.yaml \
  "$(terraform output -raw bastion_name):/tmp/netpol-demo.yaml" \
  --zone="$(terraform output -raw zone)" --tunnel-through-iap \
  --project="$(terraform output -raw project_id)"
# 踏み台の中で実行
gcloud container clusters get-credentials "$(terraform output -raw cluster_name)" \
  --zone="$(terraform output -raw zone)" --project="$(terraform output -raw project_id)"
kubectl apply -f /tmp/netpol-demo.yaml
kubectl wait --for=condition=ready pod -l app=netpol-server --timeout=90s

許可したラベルのPodからは通信できることを確認する
#

role=netpol-allowedラベルを付けたPodから接続します。kubectl run --rm -itは踏み台への非対話SSH実行では接続が不安定だったため、sleepで待機させたPodへexecする方式にしています。

kubectl run netpol-debug --image=busybox:1.36 --restart=Never \
  --labels="role=netpol-allowed" -- sleep 3600
kubectl wait --for=condition=ready pod/netpol-debug --timeout=60s
kubectl exec netpol-debug -- wget -qO- --timeout=5 http://netpol-server:8080
authorized-reached

サーバが返す固定文字列がそのまま取得できました。

許可していないPodからは遮断されることを確認する
#

同じServiceに、ラベルなしのPodから接続を試みます。

kubectl run netpol-debug-denied --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl wait --for=condition=ready pod/netpol-debug-denied --timeout=60s
kubectl exec netpol-debug-denied -- wget -qO- --timeout=5 http://netpol-server:8080
wget: download timed out
command terminated with exit code 1

タイムアウトして失敗しました。同じService・同じPodへの接続でも、送信元のラベルによって結果が変わることを確認できました。

後片付け
#

kubectl delete pod netpol-debug netpol-debug-denied --ignore-not-found=true
terraform destroy
Destroy complete! Resources: 25 destroyed.

GKE Clusterと踏み台VMは利用中に料金が発生します。検証後は必ずdestroyします。

まとめ
#

  • Dataplane V2はdatapath_provider = "ADVANCED_DATAPATH"で有効化する。別途Calicoベースのnetwork_policyアドオンを設定するとapplyが失敗する
  • NetworkPolicyはpodSelector(保護対象)とingress.from.podSelector(許可する送信元)のラベルの一致で通信を制御する
  • 許可したラベルのPodからは通り、許可していないPodからは実際にタイムアウトして遮断される。同じServiceへの接続でも送信元のラベルで結果が変わることを、実際に確認できた

参考資料
#

次回
#

GKEのノードあたり最大Pod数を小さくしすぎるとどうなるか試します。

Terraform-GoogleCloud - この記事は連載の一部です
パート 28: この記事

関連記事

Peering越しのPod→VM通信が落ちる原因を、ルートだと思い込んでいた話
4 分
Terraform GoogleCloud GKE VPC
TerraformでGKEのノードあたり最大Pod数を小さくしすぎるとどうなるか試してみた
2 分
Terraform GoogleCloud GKE
TerraformでGKEのPodをInternal HTTP LB(NEG)でポート別に振り分けてみた
3 分
Terraform GoogleCloud GKE LoadBalancing
TerraformでGKEのコントロールプレーンをプライベート+パブリックの両方から接続してみた
2 分
Terraform GoogleCloud GKE
TerraformでGKEプライベートクラスタからMemorystore for Redis Clusterへ接続してみた
3 分
Terraform GoogleCloud GKE Redis
TerraformでGKEプライベートクラスタからMemorystore for Redisへ接続してみた
2 分
Terraform GoogleCloud GKE Redis