Sécuriser les charges de travail avec GKE ClusterNetworkPolicy : exemple d'inférence d'IA (Gemma, TPU)

1. Présentation

Cet atelier vous présente ClusterNetworkPolicy sur Google Kubernetes Engine (GKE). ClusterNetworkPolicy est appliqué à différents cas d'utilisation. Il peut être appliqué à l'échelle du cluster et appliqué aux niveaux Admin et Baseline. Dans cet exemple, nous allons illustrer le contrôle avec un déploiement de LLM (Gemma) sur GKE avec des TPU, vLLM et la validation.

Vous allez utiliser les technologies suivantes :

  • Google Kubernetes Engine (GKE) : plate-forme d'orchestration de conteneurs de base pour l'hébergement de charges de travail privées et évolutives.
  • ClusterNetworkPolicy (CNP) : règles de réseau multiniveaux à l'échelle du cluster permettant d'appliquer des garde-fous SecOps aux niveaux Admin, NetworkPolicy et Baseline.
  • Moteur d'inférence vLLM sur TPU : inférence à haut débit diffusant le modèle léger Gemma 4 (google/gemma-4-E2B-it) sur des TPU Google avec des points de terminaison REST compatibles avec OpenAI.

Rôles et personas

L'API règle de réseau est structurée autour de personas organisationnels spécifiques :

  • Utilisation par l'administrateur de cluster (ClusterNetworkPolicy) : applique des garde-fous de sécurité dans les clusters aux niveaux "Admin" et "Baseline".
  • Utilisation par l'administrateur d'espace de noms (NetworkPolicy) : gère les limites et l'isolation au niveau du locataire dans des espaces de noms spécifiques.
  • Utilisation par le développeur d'applications (NetworkPolicy) : définit des règles précises pour les microservices spécifiques à l'application.

Pour configurer cet environnement, vous allez déployer un VPC privé personnalisé, Cloud NAT et un cluster GKE privé avec un pool de nœuds TPU dédié.

Pour sécuriser votre cluster, vous allez créer des ressources ClusterNetworkPolicy multiniveaux ainsi que des objets NetworkPolicy standards afin d'appliquer des garde-fous réseau stricts. Vous allez ensuite déployer une charge de travail d'IA qui utilise vLLM et les TPU Google Cloud pour diffuser le modèle Gemma 4.

Enfin, vous allez établir une référence de travail, appliquer les stratégies de sécurité et configurer NetworkLogging à l'aide de Cloud Logging et de flux d'observabilité Hubble eBPF en direct pour auditer ces décisions liées aux règles et vérifier les pertes de paquets. Toutes les configurations utiliseront une combinaison de Terraform, gcloud et kubectl..

Dans cet atelier, vous allez apprendre à effectuer la tâche suivante :

  • Provisionnez un réseau privé, un cluster GKE et des accélérateurs Cloud TPU v5e à l'aide de Terraform.
  • Configurez GKE NetworkLogging pour enregistrer la télémétrie de connexion.
  • Déployez Gemma 4 E2B sur un TPU à l'aide de l'image de conteneur vllm/vllm-tpu officielle.
  • Établissez une référence de travail prouvant la connectivité de bout en bout avant d'appliquer des restrictions.
  • Appliquez un objet ClusterNetworkPolicy Baseline-tier pour refuser par défaut le trafic non autorisé entre les espaces de noms.
  • Appliquez une NetworkPolicy gérée par le développeur pour déléguer l'accès aux clients autorisés.
  • Appliquez une règle Admin-tier ClusterNetworkPolicy pour appliquer des garde-fous de sortie globaux qui remplacent les paramètres des développeurs.
  • Vérifier les pertes et les transmissions de paquets en direct à l'aide des flux eBPF Hubble et de Cloud Logging

Dans cet atelier, vous allez créer le modèle suivant.

Figure 1. dbe5e5f7b35afb8e.jpeg

2. Configuration des services Google Cloud

Configuration de l'environnement au rythme de chacun

  1. Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. (Si vous ne possédez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.)

295004821bab6a87.png37d264871000675d.png96d86d3d5655cdbe.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères qui n'est pas utilisée par les API Google. Vous pourrez toujours la modifier.
  • L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet (généralement identifié par PROJECT_ID). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet.
  • Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
  1. Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai sans frais pour bénéficier d'un crédit de 300 $.

Démarrer Cloud Shell

Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Dans la console Google Cloud, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure :

Activer Cloud Shell

Le provisionnement et la connexion à l'environnement prennent quelques instants seulement. Une fois l'opération terminée, le résultat devrait ressembler à ceci :

Capture d'écran du terminal Google Cloud Shell montrant que l'environnement s'est connecté

Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez effectuer toutes les tâches de cet atelier de programmation dans un navigateur. Vous n'avez rien à installer.

3. Configurer l'environnement avec Terraform

Pour réaliser cet atelier,

  • Vous devez avoir accès au quota de TPU v5e dans la région et la zone sélectionnées (par exemple, us-central1-a, us-west4-a, us-south1-a, us-west1-c, europe-west4-b). Vous devez suivre le document sur le plan TPU et activer le quota de TPU pour obtenir l'accès.
  • Il s'agit d'un petit déploiement nécessitant quatre puces TPU v5e (ct5lp-hightpu-4t),) qui formeront une tranche 2x2 dans une seule zone.

Pour utiliser Hugging Face

  • Jeton Hugging Face : un jeton d'accès est nécessaire pour télécharger les pondérations du modèle Gemma.
  • Acceptez les conditions d'utilisation si nécessaire.

Nous allons créer un VPC personnalisé avec des règles de pare-feu, un sous-réseau, puis un cluster Autopilot. Ouvrez la console Cloud et sélectionnez le projet que vous allez utiliser.

  1. Ouvrez Cloud Shell en haut à droite de la console, vérifiez que l'ID de projet est correct dans Cloud Shell, puis confirmez les invites pour autoriser l'accès. b51b80043d3bac90.png
  2. Créez un répertoire appelé gke-cnp-lab, et accédez-y.
mkdir -p gke-cnp-lab && cd gke-cnp-lab
  1. Créez maintenant des variables. p.s. Modifiez REGION et ZONE pour refléter l'emplacement réel où vous avez accès aux ressources TPU. La région us-west4 et la zone us-west4-a par défaut utilisées dans l'atelier
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. Enregistrez vos variables de configuration dans un script d'aide du répertoire gke-cnp-lab. Si votre session Cloud Shell se déconnecte ou se réinitialise, vous pouvez rapidement restaurer vos variables actives en exécutant 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. Ajoutez maintenant des fichiers de configuration. Cela créera les fichiers terraform.tfvars, variables.tf et main.tf dans le répertoire 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. Assurez-vous d'être dans le répertoire gke-cnp-lab et exécutez les commandes suivantes :
    terraform init Initialise le répertoire de travail. Il s'agit de la première étape, qui télécharge les fournisseurs requis pour la configuration donnée.
    terraform plan -out génère un plan d'exécution, qui montre les actions que Terraform effectuera pour déployer votre infrastructure. -out vous permet d'enregistrer le plan d'exécution dans un binaire nommé. Vous pouvez voir ce qui se passera sans apporter de modifications.
    terraform apply exécute les mises à jour.
terraform init 
terraform plan -out cnpgke 
  1. Appliquez le plan d'exécution. Comme vous appliquez le plan d'exécution enregistré, il s'exécutera immédiatement sans demander de confirmation (cela peut prendre entre 6 et 10 minutes).
terraform apply cnpgke
  1. Vérifiez le déploiement de votre infrastructure :
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. Configurer la journalisation réseau

Avant de déployer des charges de travail et des règles, configurez la journalisation réseau GKE pour enregistrer la télémétrie des connexions et auditer les décisions liées aux règles dans Cloud Logging.

  1. Dans le répertoire gke-cnp-lab, créez le fichier manifeste de configuration 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. Appliquer la configuration au cluster
kubectl apply -f 00-network-logging.yaml
kubectl get networklogging default

Remarque : Un avertissement peut s'afficher, indiquant que la ressource ne dispose pas de l'autorisation kubectl.kubernetes.io/last-applied-configuration annotation.. C'est tout à fait normal et attendu. GKE précrée un objet NetworkLogging par défaut au démarrage du cluster, et kubectl apply prend simplement en charge sa gestion pour appliquer vos nouvelles règles.

5. Déployer la charge de travail Gemma 4 et les sondes clientes

Vous allez créer des espaces de noms isolés pour tester les évaluations de règles multiniveaux :

  • gemma-llm : héberge la charge de travail d'inférence Gemma 4 principale (tier: ai-core).
  • client-ns : héberge le client de chat RAG autorisé (tier: app-frontend).
  • untrusted-tenant : héberge un conteneur de vérification non autorisé (tier: untrusted).
  1. Dans gke-cnp-lab directory,, créez le fichier manifeste 01-namespaces-clients.yaml contenant les espaces de noms et les pods de client de test :
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
  • Appliquez les espaces de noms et les déploiements clients :
kubectl apply -f 01-namespaces-clients.yaml
kubectl get namespaces gemma-llm client-ns untrusted-tenant --show-labels

Déployer la charge de travail vLLM et Gemma

Créez votre secret Hugging Face. Remplacez YOUR_ACTUAL_HUGGING_FACE_TOKEN par votre jeton :

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. Créez le fichier manifeste de la charge de travail d'inférence Gemma 4 ciblant Cloud TPU v5e à l'aide de 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. Créez le déploiement.
kubectl apply -f 02-gemma4-tpu-vllm.yaml
  1. Pour surveiller l'état d'avancement, exécutez le script suivant. Les pods attendront que le nœud soit provisionné avant de pouvoir continuer. Cela peut prendre plus de 13 minutes.
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. Comprendre l'évaluation des règles et l'état par défaut

Avant d'appliquer des règles de réseau, découvrez comment les objets ClusterNetworkPolicy de GKE et NetworkPolicy standards de Kubernetes interagissent entre les différents niveaux d'évaluation.

Le trafic dans GKE est évalué via un modèle de priorité à plusieurs niveaux :

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

d4cf83398c69b6b8.png

Notre flux de test

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

Établir la base de référence (test avant l'application de la règle)

Vérifiez que le modèle Gemma 4 est opérationnel et qu'il diffuse des requêtes d'inférence. Dans Kubernetes standard, sans règles, tout le trafic entre les espaces de noms est implicitement autorisé.

  1. Testez l'inférence à partir du client autorisé dans 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. Testez l'inférence à partir du client non autorisé dans 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. Vérifiez si les pods des trois espaces de noms peuvent accéder au serveur de métadonnées (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. Tests d'évaluation : niveau de base et niveau de règle de réseau

Dans cette étape, en tant qu'administrateur de cluster, vous allez appliquer une de niveau de référenceClusterNetworkPolicy pour établir une stratégie de refus par défaut de remplacement à l'échelle du cluster. Toute communication entre espaces de noms qui n'est pas explicitement autorisée par un niveau supérieur sera supprimée.

Établir une stratégie zéro confiance (niveau de référence)

  1. Créez le fichier manifeste de la règle 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. Appliquez la règle du niveau de référence :
kubectl apply -f 03-baseline-deny.yaml
  1. Tester la connectivité client depuis 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. Testez l'inférence à partir du client non autorisé dans 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'

Déléguer l'accès aux développeurs (niveau NetworkPolicy)

L'équipe de l'application gemma-llm va maintenant appliquer une NetworkPolicy standard limitée à l'espace de noms pour autoriser le trafic autorisé depuis client-ns.

  1. Créez le fichier manifeste NetworkPolicy 04-app-networkpolicy.yaml du développeur :
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. Appliquez la règle :
kubectl apply -f 04-app-networkpolicy.yaml
  1. Tester la connectivité client depuis 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. Testez la connectivité depuis le client non autorisé dans 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. Évaluation de l'application des garde-fous globaux (niveau administrateur)

En tant qu'administrateur de cluster,vous allez maintenant appliquer un niveau d'administrationClusterNetworkPolicy tout en haut de l'ordre d'évaluation de GKE. Ce niveau est évalué en premier. Toute règle correspondante prévaut immédiatement sur tous les autres niveaux. S'il n'y a pas de correspondance, le paquet est transmis pour être évalué par les niveaux inférieurs (NetworkPolicy et Baseline).

Au cours de cette étape, vous allez appliquer une règle qui :

  • Règle de protection de sortie à l'échelle du cluster : empêche tous les pods de chaque espace de noms d'accéder au serveur de métadonnées Google Cloud (169.254.169.254/32).
  • Compatibilité DNS : autorise tout le trafic DNS sur le port 53 afin que les pods puissent résoudre les noms d'hôte (en tenant compte des caches DNS locaux de GKE).
  • Délégation d'entrée et de sortie : transmet toutes les autres décisions de trafic à l'aide de l'action "Passer" afin que les règles de réseau standards des développeurs et les règles de refus par défaut de référence puissent prendre les décisions finales.
  1. Créez le fichier manifeste de la règle de niveau administrateur 05-admin-guardrails.yaml ciblant tous les espaces de noms.
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. Appliquez la règle de niveau administrateur :
kubectl apply -f 05-admin-guardrails.yaml
  1. Testez l'exfiltration des métadonnées dans les trois espaces de noms :
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. Tester la connectivité client depuis 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. Simulez une tentative de contournement par un développeur en appliquant une NetworkPolicy de sortie "Tout autoriser" dans 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. Vérifiez si le contournement du développeur peut débloquer l'accès aux métadonnées :
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. Validation interactive avec Hubble et Cloud Logging

Pour observer l'application des règles en temps réel et auditer les décisions concernant les paquets dans votre cluster, vous allez utiliser une session Cloud Shell à double volet combinant le traçage des paquets eBPF au niveau du noyau et l'exécution de tests automatisés.

  1. Cliquez sur l'icône "+" dans le menu Cloud Shell pour ouvrir un deuxième onglet de terminal.
  2. Dans ce nouvel onglet, exécutez les commandes suivantes pour créer l'alias et commencer à observer le trafic en direct pour l'espace de noms 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. Revenez au premier onglet Cloud Shell et assurez-vous que vous vous trouvez dans le répertoire gke-cnp-lab.
cd ~/gke-cnp-lab
  1. Exécutez le bloc suivant pour créer et exécuter le script de validation automatisé. Ce script exécute les tests de trafic requis et interroge Cloud Logging à l'aide du schéma de métadonnées exact au niveau racine de GKE pour afficher les règles Dataplane V2 exactes qui ont évalué les requêtes :
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. Pendant que le script s'exécute dans le premier onglet, observez la sortie de Hubble dans le deuxième onglet.

Dans Hubble (onglet 2) : les requêtes HTTP autorisées sont transférées (FORWARDED) et s'affichent en vert. Pour le trafic bloqué, vous verrez des événements rouges (DROPPED). Le locataire non autorisé : la connexion est transmise par le niveau d'administration et bloquée au niveau de référence. Requête de métadonnées : sa demande d'accès aux identifiants cloud est immédiatement bloquée au niveau de l'administrateur.

7d68cc0e9b90f820.png

Dans Cloud Logging (onglet 1) : le script se termine en extrayant les enregistrements d'audit exacts envoyés à Google Cloud Logging. Cela permet de prouver explicitement quel niveau (administrateur ou de référence) et quelle règle ont permis d'arrêter le trafic non autorisé, avec des codes temporels localisés et tenant compte du fuseau horaire.

b9e48fd473451ca2.png

10. Effectuer un nettoyage

  1. Fermez le deuxième onglet.
  2. Revenez au premier onglet Cloud Shell.
  3. Supprimez les secrets, les espaces de noms et les fichiers manifestes Kubernetes déployés dans l'ordre suivant :
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. Nettoyez l'infrastructure à l'aide de la commande suivante, puis saisissez yes pour confirmer.
terraform destroy --auto-approve
  1. Vérifiez que toutes les ressources sont correctement arrêtées dans votre projet actif :
gcloud container clusters list --region=us-west4
gcloud compute networks list
  1. Supprimez le répertoire gke-cnp-lab et son contenu :
cd
rm -r gke-cnp-lab

11. Félicitations

Vous avez terminé l'atelier de programmation "Sécuriser les charges de travail d'inférence d'IA avec ClusterNetworkPolicy de GKE".

En suivant cet atelier pratique, vous avez conçu et validé une architecture réseau robuste de défense en profondeur à plusieurs niveaux pour diffuser des charges de travail LLM privées sensibles sur Google Kubernetes Engine.

Ce que vous avez accompli :

  • Infrastructure as Code : provisionnement dynamique d'un cluster GKE Standard privé hautement sécurisé, d'un sous-réseau régional privé et d'un matériel Cloud TPU v5e dédié à l'aide de Terraform.
  • Télémétrie et observabilité : configuration de la journalisation réseau Dataplane V2 avec le traçage Hubble eBPF au niveau du kernel pour obtenir une visibilité approfondie des paquets d'exécution.
  • Stratégie de sécurité hiérarchique : par exemple, une séparation claire des tâches opérationnelles entre trois niveaux d'évaluation distincts :
  • Niveau administrateur : garde-fous de sortie non contournables appliqués à l'échelle du cluster, qui bloquent l'accès latéral aux identifiants de métadonnées (169.254.169.254).
  • Niveau NetworkPolicy : limites d'entrée déléguées au niveau de l'espace de noms pour les développeurs d'applications afin d'autoriser les connexions des clients de production en toute sécurité.
  • Niveau de référence : création d'un filet de sécurité de remplacement à l'échelle du cluster qui refuse par défaut le trafic non autorisé des locataires.
  • Audit en temps réel : exécutez une suite complète de tests de validation, observez les pertes de paquets en temps réel au niveau du noyau Linux et interrogez les audits structurés des actions de stratégie directement dans Google Cloud Logging.

Étapes suivantes et informations supplémentaires

Pour en savoir plus, consultez Mise en réseau GKE.