GKE ClusterNetworkPolicy でワークロードを保護する - AI 推論の例(Gemma、TPU)

1. 概要

このラボでは、Google Kubernetes Engine(GKE)の ClusterNetworkPolicy について説明します。ClusterNetworkPolicy は、さまざまなユースケースに適用され、クラスタ全体に適用して、管理ティアとベースライン ティアで適用できます。この例では、TPU、vLLM、検証を使用して、GKE での LLM デプロイ(Gemma)を制御する方法を説明します。

次のテクノロジーを使用します。

  • Google Kubernetes Engine(GKE) - プライベートでスケーラブルなワークロードをホストするための基盤となるコンテナ オーケストレーション プラットフォーム。
  • ClusterNetworkPolicy(CNP) - クラスタ スコープのマルチティア ネットワーク ポリシー。管理、NetworkPolicy、ベースラインの各ティアにわたって SecOps ガードレールを有効にします。
  • vLLM TPU 推論エンジン - OpenAI 互換の REST エンドポイントを使用して、Google TPU で軽量の Gemma 4 モデル(google/gemma-4-E2B-it)を提供する高スループットの推論。

役割とペルソナ

ネットワーク ポリシー API は、特定の組織ペルソナを中心に構成されています。

  • クラスタ管理者の使用(ClusterNetworkPolicy): 管理者ティアとベースライン ティアでクラスタ全体にセキュリティ ガードレールを適用します。
  • 名前空間管理者の用途(NetworkPolicy): 特定の名前空間内のテナントレベルの境界と分離を管理します。
  • アプリケーション デベロッパーの使用(NetworkPolicy): アプリケーション固有のマイクロサービスの詳細なルールを定義します。

この環境を構成するには、カスタムのプライベート VPC、Cloud NAT、専用の TPU ノードプールを備えたプライベート GKE クラスタをデプロイします。

クラスタを保護するには、マルチティアの ClusterNetworkPolicy リソースを標準の NetworkPolicy オブジェクトとともに作成して、厳格なネットワーク ガードレールを適用します。次に、vLLM と Google Cloud TPU を使用して Gemma 4 モデルをサービングする AI ワークロードをデプロイします。

最後に、作業ベースラインを確立し、セキュリティ ポリシーを適用します。また、Cloud Logging とライブ Hubble eBPF オブザーバビリティ ストリームを使用して NetworkLogging を構成し、ポリシーに関する決定を監査してパケットのドロップを確認します。すべての構成で Terraform、gcloud、kubectl. の組み合わせが使用されます。

このラボでは、次のタスクの実行方法について学びます。

  • Terraform を使用して、プライベート ネットワーキング、GKE クラスタ、Cloud TPU v5e アクセラレータをプロビジョニングします。
  • 接続テレメトリーを記録するように GKE NetworkLogging を構成する。
  • 公式の vllm/vllm-tpu コンテナ イメージを使用して、TPU に Gemma 4 E2B をデプロイします。
  • 制限を適用する前に、エンドツーエンドの接続性を証明する作業ベースラインを確立する。
  • Baseline-tier ClusterNetworkPolicy を適用して、承認されていない Namespace 間のトラフィックをデフォルトで拒否します。
  • デベロッパーが管理する NetworkPolicy を適用して、承認済みクライアントにアクセスを委任する。
  • Admin-tier ClusterNetworkPolicy を適用して、デベロッパーの設定をオーバーライドするグローバルな下り(外向き)ガードレールを適用します。
  • Hubble eBPF ストリームと Cloud Logging を使用して、ライブ パケットのドロップとパスを確認します。

このラボでは、次のパターンを作成します。

図 1. dbe5e5f7b35afb8e.jpeg

2. Google Cloud サービスの設定

セルフペース型の環境設定

  1. Google Cloud コンソール にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

295004821bab6a87.png37d264871000675d.png96d86d3d5655cdbe.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列です。いつでも更新できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud コンソールでは一意の文字列が自動生成されます。通常は、この内容を意識する必要はありません。ほとんどの Codelab では、プロジェクト ID(通常は PROJECT_ID と識別されます)を参照する必要があります。生成された ID が好みではない場合は、ランダムに別の ID を生成できます。または、ご自身で試して、利用可能かどうかを確認することもできます。このステップ以降は変更できず、プロジェクトを通して同じ ID になります。
  • なお、3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud コンソールで課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルの終了後に請求が発生しないようにリソースをシャットダウンするには、作成したリソースを削除するか、プロジェクトを削除します。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloud Shell を起動する

Google Cloud はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。

Google Cloud コンソール で、右上のツールバーにある Cloud Shell アイコンをクリックします。

Cloud Shell をアクティブにする

プロビジョニングと環境への接続にはそれほど時間はかかりません。完了すると、次のように表示されます。

環境が接続されたことを示す Google Cloud Shell ターミナルのスクリーンショット

この仮想マシンには、必要なデベロッパー ツールがすべて用意されています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働します。そのため、ネットワークのパフォーマンスと認証機能が大幅に向上しています。この Codelab での作業はすべて、ブラウザ内から実行できます。インストールは不要です。

3. Terraform を使用して環境を設定する

このラボを完了するには、

  • 選択したリージョンとゾーン(us-central1-a, us-west4-a, us-south1-a, us-west1-c, europe-west4-b など)で TPU v5e の割り当てにアクセスする必要があります。TPU プランのドキュメントに沿って、TPU の割り当てを有効にしてアクセスできるようにする必要があります。
  • これは、4 個の TPU v5e チップ(ct5lp-hightpu-4t),)を必要とする小規模なデプロイで、単一ゾーンに 2x2 スライスを形成します。

Hugging Face を使用するには

  • Hugging Face トークン: Gemma モデルの重みをダウンロードするには、アクセス トークンが必要です。
  • 必要に応じて、利用規約に同意します。

ファイアウォール ルール、サブネット、Autopilot クラスタを備えたカスタム VPC を作成します。Cloud コンソールを開き、使用するプロジェクトを選択します。

  1. コンソールの右上にある Cloud Shell を開きます。Cloud Shell に正しいプロジェクト ID が表示されていることを確認し、アクセスを許可するプロンプトが表示されたら確認します。b51b80043d3bac90.png
  2. gke-cnp-lab, という名前のディレクトリを作成し、そのディレクトリに移動します。
mkdir -p gke-cnp-lab && cd gke-cnp-lab
  1. 次に、いくつかの変数を作成します。p.s. REGION、ZONE を編集して、TPU リソースにアクセスできる実際のロケーションを反映します。ラボのリージョン us-west4 とゾーン us-west4-a で使用されるデフォルト
export PROJECT_ID=$(gcloud config get-value project)
export REGION="us-west4"
export ZONE="us-west4-a"
export NETWORK="cnp-demo-vpc"
export SUBNET="cnp-main-subnet"
export CLUSTER_NAME="cnp-demo-cluster"
  1. 構成変数を gke-cnp-lab ディレクトリのヘルパー スクリプトに保存します。Cloud Shell セッションが切断またはリセットされた場合は、source ./env.sh を実行して、アクティブな変数をすぐに復元できます。
cat << EOF > env.sh
export PROJECT_ID="${PROJECT_ID}"
export REGION="${REGION}"
export ZONE="${ZONE}"
export NETWORK="${NETWORK}"
export SUBNET="${SUBNET}"
export CLUSTER_NAME="${CLUSTER_NAME}"

echo -e "\n=== Validating Environment Configuration ==="
echo "Project ID   : \${PROJECT_ID}"
echo "Region       : \${REGION}"
echo "Zone         : \${ZONE}"
echo "Network Name : \${NETWORK}"
echo "Subnet Name  : \${SUBNET}"
echo "Cluster Name : \${CLUSTER_NAME}"

if [[ -z "\${PROJECT_ID}" || -z "\${REGION}" || -z "\${ZONE}" || -z "\${NETWORK}" || -z "\${SUBNET}" || -z "\${CLUSTER_NAME}" ]]; then
  echo -e "\n[ERROR] One or more environment variables are missing! Check your configuration."
else
  echo -e "\n[SUCCESS] Environment variables successfully populated and validated."
fi
EOF

chmod +x env.sh
source ./env.sh
  1. 次に、構成ファイルを追加します。これにより、gke-cnp-lab ディレクトリに terraform.tfvars、variables.tf、main.tf の各ファイルが作成されます。
cat << EOF > terraform.tfvars
project_id   = "${PROJECT_ID}"
region       = "${REGION}"
zone         = "${ZONE}"
network_name = "${NETWORK}"
subnet_name  = "${SUBNET}"
cluster_name = "${CLUSTER_NAME}"
EOF

cat << 'EOF' > variables.tf
variable "project_id" {
  type        = string
  description = "Google Cloud Project ID"
}

variable "region" {
  type        = string
  description = "Target GCP region"
}

variable "zone" {
  type        = string
  description = "Target GCP zone"
}

variable "network_name" {
  type        = string
  description = "VPC network name"
}

variable "subnet_name" {
  type        = string
  description = "Subnetwork name"
}

variable "cluster_name" {
  type        = string
  description = "GKE cluster name"
}
EOF

cat << 'EOF' > main.tf
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 7.32.0"
    }
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "~> 7.32.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

provider "google-beta" {
  project = var.project_id
  region  = var.region
}

# 1. Custom Private VPC Network
resource "google_compute_network" "vpc" {
  name                    = var.network_name
  auto_create_subnetworks = false
  mtu                     = 8896
}

# 2. Private Subnet with Secondary IP Ranges for Pods & Services
resource "google_compute_subnetwork" "subnet" {
  name                     = var.subnet_name
  ip_cidr_range            = "10.10.0.0/20"
  region                   = var.region
  network                  = google_compute_network.vpc.id
  private_ip_google_access = true

  secondary_ip_range {
    range_name    = "gke-pods"
    ip_cidr_range = "10.20.0.0/16"
  }
  secondary_ip_range {
    range_name    = "gke-services"
    ip_cidr_range = "10.30.0.0/20"
  }
}

# 3. Cloud Router & Cloud NAT for Secure Outbound Egress
resource "google_compute_router" "router" {
  name    = "${var.network_name}-router"
  network = google_compute_network.vpc.id
  region  = var.region
}

resource "google_compute_router_nat" "nat" {
  name                               = "${var.network_name}-nat"
  router                             = google_compute_router.router.name
  region                             = var.region
  nat_ip_allocate_option             = "AUTO_ONLY"
  source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}

# 4. Internal Firewall Rules
resource "google_compute_firewall" "allow_internal" {
  name      = "${var.network_name}-allow-internal"
  network   = google_compute_network.vpc.id
  direction = "INGRESS"
  priority  = 1000
  allow {
    protocol = "all"
  }
  source_ranges = ["10.10.0.0/20", "10.20.0.0/16", "10.30.0.0/20"]
}

# 5. Private GKE Cluster with Observability and Dataplane V2
resource "google_container_cluster" "primary" {
  name               = var.cluster_name
  location           = var.region
  node_locations     = [var.zone]
  network            = google_compute_network.vpc.id
  subnetwork         = google_compute_subnetwork.subnet.id
  initial_node_count = 1

  datapath_provider = "ADVANCED_DATAPATH"

  # FIX: Observability config must be nested inside monitoring_config
  monitoring_config {
    advanced_datapath_observability_config {
      enable_metrics = true
      enable_relay   = true
    }
  }

  release_channel {
    channel = "RAPID"
  }

  ip_allocation_policy {
    cluster_secondary_range_name  = "gke-pods"
    services_secondary_range_name = "gke-services"
  }

  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false
    master_ipv4_cidr_block  = "172.16.0.0/28"
  }

  node_config {
    machine_type = "e2-standard-4"
    oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
  }

  deletion_protection = false
}

# 6. Dedicated Cloud TPU v5e Node Pool (ct5lp-hightpu-4t / 2x2 Topology)
resource "google_container_node_pool" "tpu_pool" {
  provider       = google-beta
  name           = "tpu-v5e-pool"
  location       = var.region
  node_locations = [var.zone]
  cluster        = google_container_cluster.primary.name
  node_count     = 1

  node_config {
    machine_type = "ct5lp-hightpu-4t"
    oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]

    labels = {
      "cloud.google.com/gke-tpu-accelerator" = "tpu-v5-lite-podslice"
      "cloud.google.com/gke-tpu-topology"    = "2x2"
    }

    taint {
      key    = "google.com/tpu"
      value  = "present"
      effect = "NO_SCHEDULE"
    }
  }
}
EOF
  1. gke-cnp-lab ディレクトリにいることを確認し、次のコマンドを実行します。
    terraform init 作業ディレクトリを初期化します。これは最初のステップで、指定された構成に必要なプロバイダをダウンロードします。
    terraform plan -out は実行プランを生成し、Terraform がインフラストラクチャをデプロイするために実行するアクションを示します。-out を使用すると、実行プランを名前付きバイナリに保存できます。変更を加えずに、何が起こるかを確認できます。
    terraform apply は更新を実行します。
terraform init 
terraform plan -out cnpgke 
  1. 実行計画を適用します。保存された実行プランを適用しているため、確認を求めることなくすぐに実行されます(6 ~ 10 分ほどかかる場合があります)。
terraform apply cnpgke
  1. インフラストラクチャのデプロイを確認します。
gcloud container clusters get-credentials ${CLUSTER_NAME} --region ${REGION} --project=${PROJECT_ID}
echo -e "\n=== Verifying Cluster Nodes ==="
kubectl get nodes -L cloud.google.com/gke-tpu-accelerator,cloud.google.com/gke-tpu-topology

8b187e355548b7c5.png

4. ネットワーク ロギングを構成する

ワークロードとポリシーをデプロイする前に、GKE ネットワーク ロギングを構成して、接続テレメトリーとポリシーに関する決定を Cloud Logging に記録します。

  1. gke-cnp-lab ディレクトリに、NetworkLogging 構成マニフェストを作成します。
cat << 'EOF' > 00-network-logging.yaml
apiVersion: networking.gke.io/v1alpha1
kind: NetworkLogging
metadata:
  name: default
spec:
  cluster:
    allow:
      delegate: false
      log: true
    deny:
      delegate: false
      log: true
EOF
  1. クラスタに構成を適用する
kubectl apply -f 00-network-logging.yaml
kubectl get networklogging default

注: リソースに kubectl.kubernetes.io/last-applied-configuration annotation. がないという警告が表示されることがありますが、これは完全に正常で、想定される動作です。GKE は、クラスタの起動時にデフォルトの NetworkLogging オブジェクトを事前に作成します。kubectl apply は、その管理を引き継いで新しいルールを適用するだけです。

5. Gemma 4 ワークロードとクライアント プローブをデプロイする

マルチティア ポリシーの評価をテストするために、分離された Namespace を作成します。

  • gemma-llm: コア Gemma 4 推論ワークロード(tier: ai-core)を格納します。
  • client-ns: 承認済みの RAG チャット クライアント(tier: app-frontend)を格納します。
  • untrusted-tenant: 認可されていないプローブ コンテナ(tier: untrusted)を格納します。
  1. gke-cnp-lab directory, で、Namespace とテスト クライアント Pod を含む 01-namespaces-clients.yaml マニフェストを作成します。
cat << 'EOF' > 01-namespaces-clients.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: gemma-llm
  labels:
    tier: ai-core
---
apiVersion: v1
kind: Namespace
metadata:
  name: client-ns
  labels:
    tier: app-frontend
---
apiVersion: v1
kind: Namespace
metadata:
  name: untrusted-tenant
  labels:
    tier: untrusted
---
# Authorized Client Pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rag-agent-client
  namespace: client-ns
  labels:
    app: rag-agent
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rag-agent
  template:
    metadata:
      labels:
        app: rag-agent
    spec:
      containers:
        - name: client
          image: curlimages/curl:latest
          command: ["/bin/sh", "-c", "sleep 86400"]
---
# Rogue / Untrusted Probe Pod
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rogue-client
  namespace: untrusted-tenant
  labels:
    app: rogue-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rogue-app
  template:
    metadata:
      labels:
        app: rogue-app
    spec:
      containers:
        - name: probe
          image: curlimages/curl:latest
          command: ["/bin/sh", "-c", "sleep 86400"]
EOF
  • Namespace とクライアント Deployment を適用します。
kubectl apply -f 01-namespaces-clients.yaml
kubectl get namespaces gemma-llm client-ns untrusted-tenant --show-labels

ワークロード vLLM と Gemma をデプロイする

Hugging Face の Secret を作成します。YOUR_ACTUAL_HUGGING_FACE_TOKEN は、実際のトークンに置き換えます。

export HF_TOKEN="YOUR_ACTUAL_HUGGING_FACE_TOKEN"
echo $HF_TOKEN
kubectl create secret generic hf-secret -n gemma-llm --from-literal=hf_token=${HF_TOKEN}
  1. vllm-tpu を使用して、Cloud TPU v5e をターゲットとする Gemma 4 推論ワークロード マニフェストを作成します。
cat << 'EOF' > 02-gemma4-tpu-vllm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gemma-serving
  namespace: gemma-llm
  labels:
    app: gemma-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gemma-service
  template:
    metadata:
      labels:
        app: gemma-service
        ai.gke.io/model: gemma4
        ai.gke.io/inference-server: vllm-tpu
    spec:
      tolerations:
        - key: "google.com/tpu"
          operator: "Exists"
          effect: "NoSchedule"
      nodeSelector:
        cloud.google.com/gke-tpu-accelerator: tpu-v5-lite-podslice
        cloud.google.com/gke-tpu-topology: 2x2
      containers:
        - name: vllm-inference
          image: vllm/vllm-tpu:latest
          command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
          args:
            - "--model=google/gemma-4-E2B-it"
            - "--tensor-parallel-size=4"
            - "--host=0.0.0.0"
            - "--port=8000"
            - "--max-model-len=4096"
            - "--max-num-batched-tokens=4096"
          env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_token
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_token
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
          ports:
            - name: http-inference
              containerPort: 8000
          resources:
            requests:
              google.com/tpu: "4"
              memory: "32Gi"
              cpu: "8"
            limits:
              google.com/tpu: "4"
              memory: "32Gi"
              cpu: "8"
          startupProbe:
            httpGet:
              path: /health
              port: 8000
            failureThreshold: 120
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            periodSeconds: 5
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
---
apiVersion: v1
kind: Service
metadata:
  name: gemma-service
  namespace: gemma-llm
spec:
  type: ClusterIP
  selector:
    app: gemma-service
  ports:
    - name: http
      protocol: TCP
      port: 8000
      targetPort: 8000
EOF
  1. Deployment を作成します。
kubectl apply -f 02-gemma4-tpu-vllm.yaml
  1. 完了ステータスをモニタリングするには、次のスクリプトを実行します。Pod は、ノードがプロビジョニングされるまで待機してから処理を続行します。これには 13 分以上かかることがあります。
echo "       __|__"
echo "  --@--(_|_)--@--"
echo ""
echo "Waiting for Gemma 4 TPU Pod to initialize and become Ready..."

# 1. Wait for the pod to be scheduled and reach the Running state
kubectl wait -n gemma-llm --for=condition=Ready pod -l app=gemma-service --timeout=600s

echo ""
echo "✅ Pod is Running! Streaming vLLM engine initialization logs..."

# 2. Stream logs until Uvicorn/vLLM is ready to serve
kubectl logs -n gemma-llm -l app=gemma-service -c vllm-inference -f | sed -u '/Application startup complete/q'

echo ""
echo "🚀 Gemma 4 on TPU v5e is ready for inference!"

6. ポリシーの評価とデフォルトの状態について

ネットワーク ポリシーを適用する前に、GKE ClusterNetworkPolicy と標準の Kubernetes NetworkPolicy がさまざまな評価階層でどのように相互作用するかを理解してください。

GKE のトラフィックは、多層優先度モデルで評価されます。

Admin Tier ⟶ NetworkPolicy Tier ⟶ Baseline Tier ⟶ Default GKE Behavior

d4cf83398c69b6b8.png

テストフロー

===================================================
           4-STAGE POLICY EVALUATION FLOW
===================================================

[Stage 1: Default State]
 ├── Pre-Policy State: Default Kubernetes allow-all
 ├── Ingress: client-ns  ──▶ Gemma 4: ALLOWED (200 OK)
 ├── Ingress: untrusted  ──▶ Gemma 4: ALLOWED (200 OK)
 ├── Egress:  All Pods   ──▶ Metadata: ALLOWED (Exposed)
 └── Security Posture: ⚠️ Overly permissive
      │
      ▼
[Stage 2: Baseline Lockdown]
 ├── Policy: ClusterNetworkPolicy (tier: Baseline)
 ├── Rule: Default-deny all cross-namespace ingress
 ├── Ingress: client-ns  ──▶ Gemma 4: DROPPED (Timeout)
 ├── Ingress: untrusted  ──▶ Gemma 4: DROPPED (Timeout)
 └── Security Posture: 🛑 Zero-trust baseline active
      │
      ▼
[Stage 3: Developer Delegation]
 ├── Policy: NetworkPolicy (tier: NetworkPolicy)
 ├── Rule: App team allows ingress from client-ns
 ├── Ingress: client-ns  ──▶ Gemma 4: ALLOWED (200 OK)
 ├── Ingress: untrusted  ──▶ Gemma 4: DROPPED (Timeout)
 └── Security Posture: 🔀 App isolation achieved
      │
      ▼
[Stage 4: Admin Guardrail]
 ├── Policy: ClusterNetworkPolicy (tier: Admin)
 ├── Rule: Cluster-wide metadata drop (169.254.169.254)
 ├── Egress:  All Pods   ──▶ Metadata: DROPPED (Timeout)
 ├── Dev NetPol bypass attempt:   OVERRIDDEN (Blocked)
 └── Security Posture: 🔒 Enforced guardrail
===================================================

ベースライン基盤を確立する(ポリシー適用前のテスト)

Gemma 4 モデルが正常に動作し、推論リクエストを処理していることを確認します。ポリシーのない標準の Kubernetes では、すべての Namespace 間のトラフィックが暗黙的に許可されます。

  1. client-ns の承認済みクライアントから推論をテストします。
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'
  1. untrusted-tenant の不正なクライアントからの推論をテストします。
kubectl exec -n untrusted-tenant deploy/rogue-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'
  1. 3 つの Namespace すべての Pod がメタデータ サーバー(169.254.169.254)にアクセスできるかどうかをテストします。
echo -e "\n[1/3] Testing Metadata access from Gemma serving pod (gemma-llm)..."
kubectl exec -n gemma-llm deploy/gemma-serving -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"

echo -e "\n[2/3] Testing Metadata access from authorized client (client-ns)..."
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"

echo -e "\n[3/3] Testing Metadata access from rogue client (untrusted-tenant)..."
kubectl exec -n untrusted-tenant deploy/rogue-client -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"

7. 評価テストのベースラインとネットワーク ポリシー ティア

このステップでは、クラスタ管理者として、ベースライン ティアの ClusterNetworkPolicy を適用して、クラスタ全体にフォールバックのデフォルト拒否体制を確立します。上位の階層で明示的に許可されていない Namespace 間の通信はすべてドロップされます。

ゼロトラスト体制を確立する(ベースライン ティア)

  1. 03-baseline-deny.yaml Baseline ティアのポリシー マニフェストを作成します。
cat << 'EOF' > 03-baseline-deny.yaml
apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: baseline-default-deny
spec:
  tier: Baseline
  priority: 1000
  subject:
    namespaces: {}
  ingress:
    # 1. Allow CoreDNS Ingress cluster-wide so DNS resolution never breaks
    - name: allow-dns-ingress
      action: Accept
      from:
        - namespaces: {}
      protocols:
        - udp:
            destinationPort:
              number: 53
        - tcp:
            destinationPort:
              number: 53
    # 2. Default Deny all other cross-namespace ingress
    - name: baseline-deny-cross-ns
      action: Deny
      from:
        - namespaces: {}
EOF
  1. ベースライン ティアのポリシーを適用します。
kubectl apply -f 03-baseline-deny.yaml
  1. client-ns. からのクライアント接続をテストする
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'
  1. untrusted-tenant の不正なクライアントからの推論をテストします。
kubectl exec -n untrusted-tenant deploy/rogue-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'

デベロッパーにアクセス権を委任する(NetworkPolicy ティア)

gemma-llm アプリケーション チームは、標準の Namespace スコープの NetworkPolicy を適用して、client-ns からの承認済みトラフィックを許可します。

  1. デベロッパーの 04-app-networkpolicy.yaml NetworkPolicy マニフェストを作成します。
cat << 'EOF' > 04-app-networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-rag-client
  namespace: gemma-llm
spec:
  podSelector:
    matchLabels:
      app: gemma-service
  policyTypes:
    - Ingress
  ingress:
    # Explicitly permit ingress only from pods matching app: rag-agent in client-ns
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: client-ns
          podSelector:
            matchLabels:
              app: rag-agent
      ports:
        - protocol: TCP
          port: 8000
EOF
  1. ポリシーを適用します。
kubectl apply -f 04-app-networkpolicy.yaml
  1. client-ns からのクライアント接続をテストする
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'
  1. untrusted-tenant の不正なクライアントからの接続をテストします。
kubectl exec -n untrusted-tenant deploy/rogue-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'

8. 評価でグローバル ガードレールを適用する(管理者ティア)

クラスタ管理者として、GKE の評価順序の最上位に管理者ティアの ClusterNetworkPolicy を適用します。この階層は最初に評価され、一致するルールは他のすべての階層よりも優先されます。一致するものがない場合、パケットは下位の階層(NetworkPolicy と Baseline)に渡されて評価されます。

このステップでは、次の処理を行うポリシーを適用します。

  • クラスタ全体の下り(外向き)ガードレール: すべての Namespace のすべての Pod が Google Cloud Metadata Server(169.254.169.254/32)にアクセスできないようにブロックします。
  • DNS サポート: ポート 53 のすべての DNS トラフィックを許可して、Pod がホスト名を解決できるようにします(GKE のローカル DNS キャッシュを考慮)。
  • 上り(内向き)と下り(外向き)の委任: Pass アクションを使用して他のすべてのトラフィック決定を渡します。これにより、標準のデベロッパー ネットワーク ポリシーとベースラインのデフォルト拒否ルールで最終的な決定を行うことができます。
  1. すべての Namespace をターゲットとする 05-admin-guardrails.yaml Admin ティア ポリシー マニフェストを作成する
cat << 'EOF' > 05-admin-guardrails.yaml
apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: admin-global-guardrails
spec:
  tier: Admin
  priority: 10
  subject:
    namespaces: {}
  ingress:
    - name: delegate-all-ingress
      action: Pass
      from:
        - namespaces: {}
  egress:
    - name: allow-all-dns
      action: Accept
      to:
        - networks:
            - 0.0.0.0/0
      protocols:
        - udp:
            destinationPort:
              number: 53
        - tcp:
            destinationPort:
              number: 53
    - name: block-metadata-server-global
      action: Deny
      to:
        - networks:
            - 169.254.169.254/32
    - name: delegate-other-egress
      action: Pass
      to:
        - namespaces: {}
EOF
  1. Admin ティアのポリシーを適用します。
kubectl apply -f 05-admin-guardrails.yaml
  1. 3 つの名前空間すべてでメタデータの引き出しをテストします。
echo -e "\n[1/3] Testing Metadata access from Gemma serving pod (gemma-llm)..."
kubectl exec -n gemma-llm deploy/gemma-serving -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"

echo -e "\n[2/3] Testing Metadata access from authorized client (client-ns)..."
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"

echo -e "\n[3/3] Testing Metadata access from rogue client (untrusted-tenant)..."
kubectl exec -n untrusted-tenant deploy/rogue-client -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Blocked)"
  1. client-ns からのクライアント接続をテストする
kubectl exec -n client-ns deploy/rag-agent-client -- curl --connect-timeout 3 -s -X POST \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-E2B-it",
    "messages": [{"role": "user", "content": "Explain zero trust in one short sentence."}],
    "max_tokens": 40
  }' | jq -r '.choices[0].message.content'
  1. gemma-llm 内にすべての下り(外向き)を許可する NetworkPolicy を適用して、デベロッパーによるバイパスの試行をシミュレートします。
cat << 'EOF' > 06-bypass-attempt.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: dev-allow-all-egress
  namespace: gemma-llm
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - {}
EOF

kubectl apply -f 06-bypass-attempt.yaml
  1. デベロッパー バイパスでメタデータ アクセスのブロックを解除できるかどうかをテストします。
kubectl exec -n gemma-llm deploy/gemma-serving -- curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "🛑 RESULT: Connection Timed Out (Admin Guardrail Overrode Developer Policy)"

9. Hubble と Cloud Logging を使用したインタラクティブな検証

クラスタ全体でリアルタイムのポリシー適用とパケットの監査決定をモニタリングするには、カーネルレベルの eBPF パケット トレースと自動テスト実行を組み合わせたデュアルペインの Cloud Shell セッションを使用します。

  1. Cloud Shell メニューの「+」アイコンをクリックして、2 つ目のターミナルタブを開きます。
  2. この新しいタブで次のコマンドを実行して、エイリアスを作成し、gemma-llm Namespace のライブ トラフィックのモニタリングを開始します。
# Create an alias to execute Hubble natively inside the relay pod
alias hubble="kubectl exec -it -n gke-managed-dpv2-observability deploy/hubble-relay -c hubble-cli -- hubble"

# Observe real-time traffic to and from the Gemma namespace
hubble observe --follow --namespace gemma-llm
  1. 最初の Cloud Shell タブに戻り、gke-cnp-lab ディレクトリにいることを確認します。
cd ~/gke-cnp-lab
  1. 次のブロックを実行して、自動検証スクリプトを作成して実行します。このスクリプトは、必要なトラフィック テストを実行し、GKE の正確なルートレベルのメタデータ スキーマを使用して Cloud Logging にクエリを実行し、リクエストを評価した正確な Dataplane V2 ルールを表示します。
cat << 'EOF' > verify-suite.sh
#!/usr/bin/env bash
set -e

echo "========================================================"
echo " 🧪 RUNNING MULTI-TIER POLICY VERIFICATION SUITE"
echo "========================================================"

# Test 1: Ingress Authorized Client -> Gemma (Allowed by NetworkPolicy)
echo -e "\n[1/5] Testing Authorized Client -> Gemma Serving..."
if kubectl exec -n client-ns deploy/rag-agent-client -- \
  curl -s --connect-timeout 3 \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions > /dev/null; then
  echo "  ✅ RESULT: FORWARDED (Allowed by Developer Network Policy)"
else
  echo "  🛑 RESULT: FAILED"
fi

# Test 2: Ingress Rogue Client -> Gemma (Blocked by Baseline Tier)
echo -e "\n[2/5] Testing Unauthorized Rogue Tenant -> Gemma Serving..."
if kubectl exec -n untrusted-tenant deploy/rogue-client -- \
  curl -s --connect-timeout 3 \
  http://gemma-service.gemma-llm.svc.cluster.local:8000/v1/chat/completions > /dev/null; then
  echo "  ⚠️ RESULT: UNEXPECTED ALLOW"
else
  echo "  🛑 RESULT: DROPPED (Blocked by Baseline Tier)"
fi

# Test 3: Egress Gemma -> GCP Metadata Server (Blocked by Admin Tier)
echo -e "\n[3/5] Testing Metadata access from Gemma serving pod (gemma-llm)..."
kubectl exec -n gemma-llm deploy/gemma-serving -- \
  curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "  🛑 RESULT: Connection Timed Out (Blocked)"

# Test 4: Egress Authorized Client -> GCP Metadata Server (Blocked by Admin Tier)
echo -e "\n[4/5] Testing Metadata access from authorized client (client-ns)..."
kubectl exec -n client-ns deploy/rag-agent-client -- \
  curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "  🛑 RESULT: Connection Timed Out (Blocked)"

# Test 5: Egress Rogue Client -> GCP Metadata Server (Blocked by Admin Tier)
echo -e "\n[5/5] Testing Metadata access from rogue client (untrusted-tenant)..."
kubectl exec -n untrusted-tenant deploy/rogue-client -- \
  curl --connect-timeout 3 -s -w "\nHTTP Code: %{http_code}\n" -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/zone || echo "  🛑 RESULT: Connection Timed Out (Blocked)"

echo -e "\n========================================================"
echo " 📋 FETCHING AUDIT TRAIL FROM CLOUD LOGGING"
echo "========================================================"
echo "Waiting 12 seconds for telemetry ingestion..."
sleep 12

echo -e "\n🔍 1. Validating Baseline Tier (Rogue Client Block):"
gcloud logging read '
  resource.type="k8s_node"
  logName=~"logs/policy-action"
  jsonPayload.disposition="deny"
  jsonPayload.src.namespace="untrusted-tenant"
  jsonPayload.dest.namespace="gemma-llm"
' --limit=1 --format="table(
    timestamp.date('%H:%M:%S', tz=LOCAL):label=TIME,
    jsonPayload.src.namespace:label=SRC_NS,
    jsonPayload.dest.namespace:label=DEST_NS,
    jsonPayload.policies[0].name:label=BLOCKED_BY_POLICY
)"

echo -e "\n🔍 2. Validating Admin Tier (Metadata Block across all namespaces):"
gcloud logging read '
  resource.type="k8s_node"
  logName=~"logs/policy-action"
  jsonPayload.disposition="deny"
  jsonPayload.connection.dest_ip="169.254.169.254"
' --limit=3 --format="table(
    timestamp.date('%H:%M:%S', tz=LOCAL):label=TIME,
    jsonPayload.src.namespace:label=SRC_NS,
    jsonPayload.connection.dest_ip:label=DEST_IP,
    jsonPayload.policies[0].name:label=BLOCKED_BY_POLICY
)"
EOF

chmod +x verify-suite.sh
./verify-suite.sh
  1. 最初のタブでスクリプトが実行されている間に、2 番目のタブで Hubble の出力を確認します。

Hubble(タブ 2): 承認された HTTP リクエストが緑色で FORWARDED と表示されます。ブロックされたトラフィックについては、赤色の(DROPPED)イベントが表示されます。不正なテナント: 接続は管理ティアを通過し、ベースライン ティアでブロックされます。メタデータ クエリ: クラウド認証情報にアクセスするリクエストは、管理層で即座にブロックされます。

7d68cc0e9b90f820.png

Cloud Logging(タブ 1): スクリプトは、Google Cloud Logging に送信された正確な監査レコードを pull して終了します。これにより、不正なトラフィックを停止した Tier(Admin または Baseline)とポリシーが明示的に証明され、ローカライズされたタイムゾーン対応のタイムスタンプでフォーマットされます。

b9e48fd473451ca2.png

10. クリーンアップ

  1. 2 つ目のタブを閉じる
  2. 最初の Cloud Shell タブに戻ります。
  3. デプロイされた Kubernetes マニフェスト、Namespace、Secret を次の順序で削除します。
cd ~/gke-cnp-lab

# Delete policy and verification scripts
rm -f verify-suite.sh
kubectl delete -f 05-admin-guardrails.yaml --ignore-not-found
kubectl delete -f 04-app-networkpolicy.yaml --ignore-not-found
kubectl delete -f 03-baseline-deny.yaml --ignore-not-found
kubectl delete -f 00-network-logging.yaml --ignore-not-found

# Delete workload deployments, secrets, and namespaces
kubectl delete -f 02-gemma4-tpu-vllm.yaml --ignore-not-found
kubectl delete secret hf-secret -n gemma-llm --ignore-not-found
kubectl delete -f 01-namespaces-clients.yaml --ignore-not-found
  1. 次のコマンドでインフラストラクチャをクリーンアップし、「yes」と入力して確定します。
terraform destroy --auto-approve
  1. アクティブなプロジェクトですべてのリソースが正常に終了していることを確認します。
gcloud container clusters list --region=us-west4
gcloud compute networks list
  1. gke-cnp-lab ディレクトリとその内容を削除します。
cd
rm -r gke-cnp-lab

11. 完了

「GKE ClusterNetworkPolicy を使用して AI 推論ワークロードを保護する」Codelab を完了しました。

このハンズオンラボを完了することで、Google Kubernetes Engine で機密性の高いプライベート LLM ワークロードを処理するための、堅牢な多層防御ネットワーク アーキテクチャを設計し、検証しました。

これまでの成果:

  • Infrastructure as Code: Terraform を使用して、安全性の高いプライベート Standard GKE クラスタ、プライベート リージョン サブネット、専用の Cloud TPU v5e ハードウェアを動的にプロビジョニングしました。
  • テレメトリーとオブザーバビリティ: Dataplane V2 ネットワーク ロギングとカーネルレベルの eBPF Hubble トレースを構成し、ランタイム パケットの詳細な可視性を実現しました。
  • 階層型セキュリティ ポスチャー: 例: 3 つの異なる評価階層にわたる運用業務の明確な分離。
  • 管理ティア: クラスタ全体に適用され、バイパスできない外向きのガードレール。メタデータ認証情報へのラテラル アクセス(169.254.169.254)をブロックします。
  • NetworkPolicy ティア: 名前空間スコープの上り(内向き)境界をアプリケーション デベロッパーに委任して、本番環境クライアント接続を安全に認可します。
  • ベースライン ティア: クラスタ全体のフォールバック セーフティ ネットを構築し、デフォルトで未承認のテナント トラフィックを拒否しました。
  • リアルタイム監査: 完全な検証テストスイートを実行し、Linux カーネルレベルでリアルタイムのパケットドロップを監視し、Google Cloud Logging で構造化されたポリシー アクション監査を直接クエリします。

次のステップと詳細情報

GKE ネットワーキングの詳細を確認する。