使用 GKE ClusterNetworkPolicy 保護工作負載 - AI 推論範例 (Gemma、TPU)

1. 總覽

本實驗室將介紹 Google Kubernetes Engine (GKE) 的 ClusterNetworkPolicy。ClusterNetworkPolicy 適用於多種用途,可套用至整個叢集,並在管理員和基準層級強制執行。在本範例中,我們將示範如何使用 TPU、vLLM 和驗證,在 GKE 上控管 LLM 部署作業 (Gemma)。

您將使用下列技術:

角色和人物

網路政策 API 是圍繞特定組織角色建構而成:

  • 叢集管理員用途 (ClusterNetworkPolicy):在管理員和基準層級,強制執行叢集安全防護措施。
  • 命名空間管理員用途 (NetworkPolicy):管理特定命名空間內的租戶層級邊界和隔離。
  • 應用程式開發人員用途 (NetworkPolicy):為應用程式專屬微服務定義精細規則。

如要設定這個環境,您需要部署自訂私人虛擬私有雲、Cloud NAT,以及具備專屬 TPU 節點集區的私人 GKE 叢集。

為保護叢集,您將編寫多層級 ClusterNetworkPolicy 資源,並搭配標準 NetworkPolicy 物件,強制執行嚴格的網路防護措施。接著,您將部署 AI 工作負載,使用 vLLM 和 Google Cloud TPU 提供 Gemma 4 模型。

最後,您將建立工作基準、套用安全性政策,並使用 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,預設拒絕未經授權的跨命名空間流量。
  • 套用開發人員管理的 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 控制台會自動產生一組不重複的字串,通常您不需要知道這組字串。在大多數程式碼實驗室中,您必須參照專案 ID (通常會標示為 PROJECT_ID)。如果您不喜歡系統產生的 ID,可以再產生一組隨機 ID。或者,您也可以嘗試使用自己的名稱,看看是否可用。這個步驟完成後即無法變更,且在專案期間會保持不變。
  • 此外,還有第三個值,也就是「專案編號」,部分 API 會使用這個值。如要進一步瞭解這三個值,請參閱說明文件。
  1. 接著,您必須在 Cloud 控制台中啟用計費功能,才能使用 Cloud 資源/API。完成這個程式碼實驗室的費用極低,甚至可能完全免費。如要關閉資源,避免產生本教學課程以外的費用,可以刪除建立的資源或專案。新註冊 Google Cloud 的使用者可參加價值$300 美元的免費試用計畫。

啟動 Cloud Shell

雖然您可以透過筆電遠端操作 Google Cloud,但在本程式碼實驗室中,您將使用 Google Cloud Shell,這是在雲端執行的指令列環境。

在 Google Cloud 控制台中,點選右上角工具列的「Cloud Shell」圖示:

啟用 Cloud Shell

佈建並連線至環境的作業需要一些時間才能完成。完成後,您應該會看到類似下方的內容:

Google Cloud Shell 終端機螢幕截圖,顯示環境已連線

這個虛擬機器已預先安裝所有必要的開發人員工具,提供永久的 5 GB 主目錄,而且在 Google Cloud 中運作,大幅提升網路效能並強化驗證功能。本程式碼實驗室的所有工作都可以在瀏覽器中完成。您不需要安裝任何項目。

3. 使用 Terraform 設定環境

為順利完成這個實驗室,

  • 您必須有權存取所選區域和可用區的 TPU v5e 配額,例如 us-central1-a, us-west4-a, us-south1-a, us-west1-c, europe-west4-b。請按照 TPU 方案文件啟用 TPU 配額,取得存取權。
  • 這項小型部署作業需要 4 個 TPU v5e 晶片 (ct5lp-hightpu-4t),),在單一可用區中形成 2x2 配量。

使用 Hugging Face

  • Hugging Face 權杖:必須提供存取權杖,才能下載 Gemma 模型權重。
  • 視需要接受任何使用協議。

我們會建立自訂虛擬私有雲,其中包含防火牆規則、子網路和 Autopilot 叢集。開啟 Cloud 控制台,選取要使用的專案。

  1. 開啟控制台右上方的 Cloud Shell,確認 Cloud Shell 顯示正確的專案 ID,並在出現提示時允許存取。b51b80043d3bac90.png
  2. 建立名為 gke-cnp-lab, 的目錄,然後切換至該目錄。
mkdir -p gke-cnp-lab && cd gke-cnp-lab
  1. 現在建立一些變數。附註:請編輯 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 工作負載和用戶端探測

您將建立獨立的命名空間,測試多層級政策評估:

  • gemma-llm:存放核心 Gemma 4 推論工作負載 (tier: ai-core)。
  • client-ns:存放授權的 RAG 即時通訊用戶端 (tier: app-frontend)。
  • untrusted-tenant:含有未經授權的探測容器 (tier: untrusted)。
  1. 在 gke-cnp-lab directory, 中,建立包含命名空間和測試用戶端 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
  • 套用命名空間和用戶端部署作業:
kubectl apply -f 01-namespaces-clients.yaml
kubectl get namespaces gemma-llm client-ns untrusted-tenant --show-labels

部署工作負載 vLLM 和 Gemma

建立 Hugging Face 密鑰。將 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 建立 Gemma 4 推論工作負載資訊清單,並指定 Cloud TPU v5e:
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. 建立部署作業。
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 中,所有跨命名空間流量都會隱含允許。

  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. 測試三個命名空間中的 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,建立叢集層級的備用預設拒絕機制。如果跨命名空間通訊未經較高層級明確允許,就會遭到捨棄。

建立零信任安全機制 (基準層級)

  1. 建立 03-baseline-deny.yaml Baseline-tier 政策資訊清單:
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 應用程式團隊現在會套用標準命名空間範圍的 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) 進行評估。

在這個步驟中,您將套用政策,

  • 叢集層級輸出守則:禁止所有命名空間中的 Pod 存取 Google Cloud 中繼資料伺服器 (169.254.169.254/32)。
  • DNS 支援:允許通訊埠 53 上的所有 DNS 流量,讓 Pod 能解析主機名稱 (考量到 GKE 的本機 DNS 快取)。
  • 輸入和輸出委派:使用「Pass」動作向下傳遞所有其他流量決策,讓標準開發人員網路政策和基準預設拒絕規則做出最終決策。
  1. 建立 05-admin-guardrails.yaml 管理員層級政策資訊清單,指定所有命名空間
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. 套用管理員層級政策:
kubectl apply -f 05-admin-guardrails.yaml
  1. 測試所有三個命名空間的中繼資料竊取:
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 進行互動式驗證

您將使用雙窗格 Cloud Shell 工作階段,結合核心層級 eBPF 封包追蹤與自動化測試執行,觀察叢集中的即時政策強制執行和稽核封包決策。

  1. 點選 Cloud Shell 選單中的「+」圖示,開啟第二個終端機分頁。
  2. 在這個新分頁中,執行下列指令來建立別名,並開始觀察 gemma-llm 命名空間的即時流量:
# 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. 指令碼在第一個分頁中執行時,請在第二個分頁中查看 Hubble 輸出內容。

在 Hubble (分頁 2):您會看到已授權的 HTTP 要求以綠色顯示為「FORWARDED」。如果是遭封鎖的流量,則會顯示紅色 (已捨棄) 事件。惡意租戶:連線會由管理員層級向下傳遞,並在基準層級遭到封鎖。中繼資料查詢:存取雲端憑證的要求會立即在管理員層級遭到封鎖。

7d68cc0e9b90f820.png

在 Cloud Logging (分頁 1):指令碼會提取傳送至 Google Cloud Logging 的確切稽核記錄,然後結束。這項資訊會明確指出是哪個層級 (管理員或基準) 和哪項政策阻止了未經授權的流量,並以本地化、時區感知的時間戳記格式呈現。

b9e48fd473451ca2.png

10. 清除

  1. 關閉第二個分頁
  2. 切換回第一個 Cloud Shell 分頁。
  3. 依序刪除已部署的 Kubernetes 資訊清單、命名空間和 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 推論工作負載」程式碼實驗室!

完成這個實作實驗室後,您已設計並驗證穩健的多層縱深防禦網路架構,可在 Google Kubernetes Engine 提供機密私人 LLM 工作負載。

您已完成以下事項:

  • 基礎架構即程式碼:使用 Terraform 動態佈建高度安全的私人 Standard GKE 叢集、私人區域子網路,以及專屬 Cloud TPU v5e 硬體。
  • 遙測和觀測能力:設定 Dataplane V2 網路記錄,並搭配核心層級 eBPF Hubble 追蹤功能,深入掌握執行階段封包。
  • 階層式資安態勢:例如,在三個不同的評估層級中,明確區分營運職責:
  • 管理員層級:在整個叢集強制執行不可略過的輸出防護措施,封鎖橫向中繼資料憑證存取 (169.254.169.254)。
  • NetworkPolicy 層級:將命名空間範圍的輸入邊界委派給應用程式開發人員,安全地授權正式版用戶端連線。
  • 基準層級:建立叢集層級的備用安全網,預設拒絕未經授權的租戶流量。
  • 即時稽核:執行完整的驗證測試套件、在 Linux 核心層級監控即時封包捨棄情形,以及直接在 Google Cloud Logging 查詢結構化政策動作稽核記錄

後續步驟/瞭解詳情

如要進一步瞭解 GKE 網路,請參閱這篇文章。