Protege las cargas de trabajo con ClusterNetworkPolicy de GKE: Ejemplo de inferencia de IA (Gemma, TPU)

1. Descripción general

En este lab, se presenta ClusterNetworkPolicy en Google Kubernetes Engine (GKE). ClusterNetworkPolicy se aplica a varios casos de uso y se puede aplicar a todo el clúster y aplicarse en los niveles de administrador y de referencia. En este ejemplo, demostraremos el control con una implementación de LLM (Gemma) en GKE con TPU, vLLM y validación.

Trabajarás con las siguientes tecnologías:

  • Google Kubernetes Engine (GKE): Es la plataforma de organización de contenedores fundamental para alojar cargas de trabajo privadas y escalables.
  • ClusterNetworkPolicy (CNP): Son políticas de red de varios niveles con alcance de clúster que habilitan las protecciones de SecOps en los niveles Admin, NetworkPolicy y Baseline.
  • Motor de inferencia de TPU de vLLM: Inferencia de alta capacidad de procesamiento que entrega el modelo ligero Gemma 4 (google/gemma-4-E2B-it) en TPU de Google con extremos de REST compatibles con OpenAI.

Roles y perfiles

La API de política de red se estructura en torno a roles organizacionales específicos:

  • Uso del administrador del clúster (ClusterNetworkPolicy): Aplica protecciones de seguridad en los clústeres en los niveles de administrador y de referencia.
  • Uso del administrador de espacios de nombres (NetworkPolicy): Administra los límites y el aislamiento a nivel de los usuarios dentro de espacios de nombres específicos.
  • Uso del desarrollador de aplicaciones (NetworkPolicy): Define reglas detalladas para microservicios específicos de la aplicación.

Para configurar este entorno, implementarás una VPC privada personalizada, Cloud NAT y un clúster de GKE privado con un grupo de nodos TPU dedicado.

Para proteger tu clúster, crearás recursos ClusterNetworkPolicy de varios niveles junto con objetos NetworkPolicy estándar para aplicar protecciones de red estrictas. Luego,implementarás una carga de trabajo de IA que use vLLM y Google Cloud TPUs para entregar el modelo de Gemma 4.

Por último, establecerás un modelo de referencia de trabajo, aplicarás las políticas de seguridad y configurarás NetworkLogging con Cloud Logging y transmisiones de observabilidad de Hubble eBPF en vivo para auditar esas decisiones basadas en políticas y verificar los paquetes descartados. Todas las configuraciones usarán una combinación de Terraform, gcloud y kubectl.

En este lab, aprenderás a realizar la siguiente tarea:

  • Aprovisiona redes privadas, un clúster de GKE y aceleradores de Cloud TPU v5e con Terraform.
  • Configura NetworkLogging de GKE para registrar la telemetría de conexión.
  • Implementar Gemma 4 E2B en TPU con la imagen de contenedor oficial vllm/vllm-tpu
  • Establece un modelo de referencia de trabajo que demuestre la conectividad de extremo a extremo antes de aplicar restricciones.
  • Aplica una ClusterNetworkPolicy de Baseline-tier para denegar de forma predeterminada el tráfico no autorizado entre espacios de nombres.
  • Aplica una NetworkPolicy administrada por el desarrollador para delegar el acceso a los clientes autorizados.
  • Aplica una ClusterNetworkPolicy de Admin-tier para aplicar protecciones de salida globales que anulen la configuración de los desarrolladores.
  • Verificar los paquetes descartados y pasados en vivo con (transmisiones de eBPF de Hubble y Cloud Logging)

En este lab, crearás el siguiente patrón.

Figura 1. dbe5e5f7b35afb8e.jpeg

2. Configuración de los servicios de Google Cloud

Configuración del entorno de autoaprendizaje

  1. Accede a la consola de Google Cloud y crea un proyecto nuevo o reutiliza uno existente. Si aún no tienes una cuenta de Gmail o de Google Workspace, debes crear una.

295004821bab6a87.png37d264871000675d.png96d86d3d5655cdbe.png

  • El Nombre del proyecto es el nombre visible de los participantes de este proyecto. Es una cadena de caracteres que no se utiliza en las APIs de Google. Puedes actualizarla cuando quieras.
  • El ID del proyecto es único en todos los proyectos de Google Cloud y es inmutable (no se puede cambiar después de configurarlo). La consola de Cloud genera automáticamente una cadena única. Por lo general, no importa cuál sea. En la mayoría de los codelabs, deberás hacer referencia al ID de tu proyecto (suele identificarse como PROJECT_ID). Si no te gusta el ID que se generó, podrías generar otro aleatorio. También puedes probar uno propio y ver si está disponible. No se puede cambiar después de este paso y se usa el mismo durante todo el proyecto.
  • Recuerda que hay un tercer valor, un número de proyecto, que usan algunas APIs. Obtén más información sobre estos tres valores en la documentación.
  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar las APIs o los recursos de Cloud. Ejecutar este codelab no costará mucho, tal vez nada. Para cerrar recursos y evitar que se generen cobros más allá de este instructivo, puedes borrar los recursos que creaste o borrar el proyecto. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.

Inicia Cloud Shell

Si bien Google Cloud y Spanner se pueden operar de manera remota desde tu laptop, en este codelab usarás Google Cloud Shell, un entorno de línea de comandos que se ejecuta en la nube.

En la consola de Google Cloud, haz clic en el ícono de Cloud Shell en la barra de herramientas en la parte superior derecha:

Activar Cloud Shell

El aprovisionamiento y la conexión al entorno deberían tomar solo unos minutos. Cuando termine el proceso, debería ver algo como lo siguiente:

Captura de pantalla de la terminal de Google Cloud Shell que muestra que el entorno se conectó

Esta máquina virtual está cargada con todas las herramientas para desarrolladores que necesitará. Ofrece un directorio principal persistente de 5 GB y se ejecuta en Google Cloud, lo que permite mejorar considerablemente el rendimiento de la red y la autenticación. Todo tu trabajo en este codelab se puede hacer en un navegador. No es necesario que instales nada.

3. Configura el entorno con Terraform

Para completar este lab,

  • Necesitas acceso a la cuota de TPU v5e en la región y zona que seleccionaste, p. ej., (us-central1-a, us-west4-a, us-south1-a, us-west1-c, europe-west4-b). Debes seguir el documento del plan de TPU y habilitar la cuota de TPU para obtener acceso.
  • Esta es una implementación pequeña que requiere 4 chips de TPU v5e (ct5lp-hightpu-4t), que formarán una porción de 2x2 en una sola zona.

Para usar Hugging Face

  • Token de Hugging Face: Se necesita un token de acceso para descargar los pesos del modelo de Gemma.
  • Acepta cualquier acuerdo de uso según sea necesario.

Crearemos una VPC personalizada con reglas de firewall, una subred y, luego, un clúster de Autopilot. Abre la consola de Cloud y selecciona el proyecto que usarás.

  1. Abre Cloud Shell, que se encuentra en la parte superior derecha de la consola, asegúrate de ver el ID del proyecto correcto en Cloud Shell y confirma los mensajes para permitir el acceso. b51b80043d3bac90.png
  2. Crea un directorio llamado gke-cnp-lab, y muévete a él.
mkdir -p gke-cnp-lab && cd gke-cnp-lab
  1. Ahora, crea algunas variables. P.D. : Edita REGION y ZONE para reflejar la ubicación real en la que tienes acceso a los recursos TPU. La región us-west4 y la zona us-west4-a predeterminadas que se usan en el lab
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. Guarda tus variables de configuración en una secuencia de comandos auxiliar en el directorio gke-cnp-lab. Si tu sesión de Cloud Shell se desconecta o se restablece, puedes restaurar rápidamente tus variables activas ejecutando 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. Ahora, agrega algunos archivos de configuración. Esto creará los siguientes archivos terraform.tfvars, variables.tf y main.tf en el directorio gke-cnp-lab.
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. Asegúrate de estar en el directorio gke-cnp-lab y ejecuta los siguientes comandos:
    terraform init Inicializa el directorio de trabajo. Este es el primer paso y descarga los proveedores necesarios para la configuración dada.
    terraform plan -out genera un plan de ejecución que muestra las acciones que Terraform realizará para implementar tu infraestructura. El -out te permite guardar el plan de ejecución en un objeto binario con nombre. Puedes ver lo que sucederá sin realizar ningún cambio.
    terraform apply ejecuta las actualizaciones.
terraform init 
terraform plan -out cnpgke 
  1. Aplica el plan de ejecución. Como estás aplicando el plan de ejecución guardado, se ejecutará de inmediato sin solicitar confirmación (esto puede tardar entre 6 y 10 minutos).
terraform apply cnpgke
  1. Verifica la implementación de tu infraestructura:
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. Configura el registro de red

Antes de implementar cargas de trabajo y políticas, configura el registro de red de GKE para registrar la telemetría de conexión y las decisiones basadas en políticas de auditoría en Cloud Logging.

  1. En el directorio gke-cnp-lab, crea el manifiesto de configuración de 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. Aplica la configuración al clúster
kubectl apply -f 00-network-logging.yaml
kubectl get networklogging default

Nota: Es posible que veas una advertencia que indica que al recurso le falta el kubectl.kubernetes.io/last-applied-configuration annotation. Esto es completamente normal y esperado. GKE crea previamente un objeto NetworkLogging predeterminado cuando se inicia el clúster, y kubectl apply simplemente se encarga de administrarlo para aplicar tus reglas nuevas.

5. Implementa la carga de trabajo de Gemma 4 y los sondeos de cliente

Crearás espacios de nombres aislados para probar las evaluaciones de políticas de varios niveles:

  • gemma-llm: Aloja la carga de trabajo de inferencia principal de Gemma 4 (tier: ai-core).
  • client-ns: Aloja el cliente de chat de RAG autorizado (tier: app-frontend).
  • untrusted-tenant: Aloja un contenedor de sondeo no autorizado (tier: untrusted).
  1. En gke-cnp-lab directory,, crea el manifiesto 01-namespaces-clients.yaml que contiene los espacios de nombres y los Pods de cliente de prueba:
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
  • Aplica los espacios de nombres y las implementaciones de clientes:
kubectl apply -f 01-namespaces-clients.yaml
kubectl get namespaces gemma-llm client-ns untrusted-tenant --show-labels

Implementa la carga de trabajo de vLLM y Gemma

Crea tu secreto de Hugging Face. Reemplaza YOUR_ACTUAL_HUGGING_FACE_TOKEN por tu 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. Crea el manifiesto de la carga de trabajo de inferencia de Gemma 4 orientado a Cloud TPU v5e con vllm-tpu:
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. Crea la implementación.
kubectl apply -f 02-gemma4-tpu-vllm.yaml
  1. Para supervisar el estado de finalización, ejecuta la siguiente secuencia de comandos. Los Pods esperarán hasta que se aprovisione el nodo antes de continuar, lo que puede tardar más de 13 minutos.
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. Información sobre la evaluación de políticas y el estado predeterminado

Antes de aplicar políticas de red, comprende cómo interactúan ClusterNetworkPolicy de GKE y NetworkPolicy estándar de Kubernetes en diferentes niveles de evaluación.

El tráfico en GKE se evalúa a través de un modelo de precedencia de varios niveles:

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

d4cf83398c69b6b8.png

Nuestro flujo de prueba

===================================================
           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
===================================================

Establece la base de referencia (prueba previa a la política)

Verifica que el modelo Gemma 4 esté en buen estado y entregue solicitudes de inferencia. En Kubernetes estándar sin políticas, todo el tráfico entre espacios de nombres se permite de forma implícita.

  1. Prueba la inferencia desde el cliente autorizado en 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. Prueba la inferencia desde el cliente no autorizado en 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. Prueba si los Pods de los tres espacios de nombres pueden acceder al servidor de metadatos (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. Prueba de evaluación de referencia y nivel de política de red

En este paso, como administrador del clúster, aplicarás un ClusterNetworkPolicy de nivel de referencia para establecer una postura de rechazo predeterminada de resguardo en todo el clúster. Se descartará cualquier comunicación entre espacios de nombres que no esté permitida de forma explícita por un nivel superior.

Establece una postura de confianza cero (nivel de referencia)

  1. Crea el manifiesto de la política de nivel de referencia de 03-baseline-deny.yaml:
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. Aplica la política del nivel de referencia:
kubectl apply -f 03-baseline-deny.yaml
  1. Prueba la conectividad del cliente desde 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. Prueba la inferencia del cliente no autorizado en 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'

Delega el acceso a los desarrolladores (nivel de NetworkPolicy)

El equipo de la aplicación gemma-llm ahora aplicará una NetworkPolicy estándar con alcance de espacio de nombres para otorgar tráfico autorizado desde client-ns.

  1. Crea el manifiesto NetworkPolicy 04-app-networkpolicy.yaml del desarrollador:
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. Aplica la política:
kubectl apply -f 04-app-networkpolicy.yaml
  1. Prueba la conectividad del cliente desde 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. Prueba la conectividad desde el cliente no autorizado en 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. La evaluación aplica las barreras de protección globales (nivel de administrador)

Como administrador del clúster, ahora aplicarás una política de nivel de administrador ClusterNetworkPolicy en la parte superior del orden de evaluación de GKE. Este nivel se evalúa primero, y cualquier regla que coincida tiene prioridad inmediata sobre todos los demás niveles. Si no hay coincidencia, el paquete se pasa para que lo evalúen los niveles inferiores (NetworkPolicy y Baseline).

En este paso, aplicarás una política que haga lo siguiente:

  • Barrera de salida para todo el clúster: Bloquea todos los Pods de todos los espacios de nombres para que no lleguen al servidor de metadatos de Google Cloud (169.254.169.254/32).
  • Compatibilidad con DNS: Permite todo el tráfico de DNS en el puerto 53 para que los Pods puedan resolver los nombres de host (teniendo en cuenta las cachés de DNS locales de GKE).
  • Delegación de entrada y salida: Pasa todas las demás decisiones de tráfico hacia abajo con la acción Pass para que las políticas de red estándar de los desarrolladores y las reglas de denegación predeterminadas de referencia puedan tomar las decisiones finales.
  1. Crea el manifiesto de la política de nivel de administrador 05-admin-guardrails.yaml que se orienta a todos los espacios de nombres
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. Aplica la política de nivel de administrador:
kubectl apply -f 05-admin-guardrails.yaml
  1. Prueba el robo de metadatos en los tres espacios de nombres:
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. Prueba la conectividad del cliente desde 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. Simula un intento de omisión por parte de un desarrollador aplicando una NetworkPolicy de salida que permita todo dentro de gemma-llm:
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. Prueba si la omisión para desarrolladores puede desbloquear el acceso a los metadatos:
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. Verificación interactiva con Hubble y Cloud Logging

Para observar la aplicación de políticas en tiempo real y auditar las decisiones de paquetes en todo tu clúster, usarás una sesión de Cloud Shell de doble panel que combina el seguimiento de paquetes de eBPF a nivel del kernel con la ejecución de pruebas automatizadas.

  1. Haz clic en el ícono "+" en el menú de Cloud Shell para abrir una segunda pestaña de la terminal.
  2. En esta pestaña nueva, ejecuta los siguientes comandos para crear el alias y comenzar a observar el tráfico en vivo del espacio de nombres 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. Regresa a la primera pestaña de Cloud Shell y asegúrate de estar en el directorio gke-cnp-lab.
cd ~/gke-cnp-lab
  1. Ejecuta el siguiente bloque para crear y ejecutar la secuencia de comandos de verificación automatizada. Esta secuencia de comandos ejecuta las pruebas de tráfico requeridas y consulta Cloud Logging con el esquema de metadatos de nivel raíz exacto de GKE para mostrar las reglas exactas de Dataplane V2 que evaluaron las solicitudes:
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. Mientras se ejecuta la secuencia de comandos en la primera pestaña, observa el resultado de Hubble en la segunda.

En Hubble (pestaña 2): Verás las solicitudes HTTP autorizadas que se REENVÍAN en verde. Para el tráfico bloqueado, verás eventos rojos (DROPPED). El usuario malicioso: La conexión se pasa por el nivel de administrador y se bloquea en el nivel de referencia. La consulta de metadatos: Su solicitud para acceder a las credenciales de la nube se bloquea de inmediato en el nivel de administrador.

7d68cc0e9b90f820.png

En Cloud Logging (pestaña 1): El script finaliza extrayendo los registros de auditoría exactos que se enviaron a Google Cloud Logging. Esto demuestra de forma explícita qué nivel (administrador o de referencia) y qué política fueron responsables de detener el tráfico no autorizado, con marcas de tiempo localizadas y que tienen en cuenta la zona horaria.

b9e48fd473451ca2.png

10. Limpia

  1. Cierra la segunda pestaña
  2. Regresa a la primera pestaña de Cloud Shell.
  3. Borra los manifiestos, los espacios de nombres y los Secrets de Kubernetes implementados en orden:
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. Limpia la infraestructura con el siguiente comando y escribe yes para confirmar
terraform destroy --auto-approve
  1. Verifica que todos los recursos se hayan finalizado de forma correcta en tu proyecto activo:
gcloud container clusters list --region=us-west4
gcloud compute networks list
  1. Borra el directorio gke-cnp-lab y su contenido:
cd
rm -r gke-cnp-lab

11. Felicitaciones

Completaste correctamente el codelab Protege las cargas de trabajo de inferencia de IA con ClusterNetworkPolicy de GKE.

Con este lab práctico, diseñaste y validaste una arquitectura de red de defensa en profundidad sólida y de varios niveles para entregar cargas de trabajo de LLM privadas y sensibles en Google Kubernetes Engine.

Qué lograste:

  • Infraestructura como código: Se aprovisionó un clúster de GKE estándar privado altamente seguro, una subred regional privada y hardware de Cloud TPU v5e dedicado de forma dinámica con Terraform.
  • Telemetría y observabilidad: Se configuró el registro de red de Dataplane V2 junto con el seguimiento de Hubble de eBPF a nivel del kernel para lograr una visibilidad profunda de los paquetes en el entorno de ejecución.
  • Postura de seguridad jerárquica: Por ejemplo, una separación clara de las obligaciones operativas en tres niveles de evaluación distintos:
  • Nivel de administrador: Se aplican protecciones de salida que no se pueden omitir en todo el clúster y que bloquean el acceso lateral a las credenciales de metadatos (169.254.169.254).
  • Nivel de NetworkPolicy: Se delegaron límites de entrada con alcance de espacio de nombres a los desarrolladores de aplicaciones para autorizar las conexiones de clientes de producción de forma segura.
  • Nivel de referencia: Creó una red de seguridad de resguardo para todo el clúster que denegaba de forma predeterminada el tráfico no autorizado de los usuarios.
  • Auditoría en tiempo real: Ejecuta un paquete de pruebas de verificación completo, observa las pérdidas de paquetes en tiempo real a nivel del kernel de Linux y consulta auditorías estructuradas de acciones de políticas directamente en Google Cloud Logging.

Próximos pasos y más información

Puedes obtener más información sobre las redes de GKE.