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-tierpour 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-tierClusterNetworkPolicy 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. 
2. Configuration des services Google Cloud
Configuration de l'environnement au rythme de chacun
- 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.)



- 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.
- 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 :

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

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

- Créez un répertoire appelé
gke-cnp-lab,et accédez-y.
mkdir -p gke-cnp-lab && cd gke-cnp-lab
- 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-west4et la zoneus-west4-apar 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"
- 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
- 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
- Assurez-vous d'être dans le répertoire gke-cnp-lab et exécutez les commandes suivantes :
terraform initInitialise 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 -outgénère un plan d'exécution, qui montre les actions que Terraform effectuera pour déployer votre infrastructure.-outvous permet d'enregistrer le plan d'exécution dans un binaire nommé. Vous pouvez voir ce qui se passera sans apporter de modifications.terraform applyexécute les mises à jour.
terraform init
terraform plan -out cnpgke
- 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
- 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

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.
- 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
- 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).
- Dans
gke-cnp-lab directory,, créez le fichier manifeste01-namespaces-clients.yamlcontenant 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}
- 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
- Créez le déploiement.
kubectl apply -f 02-gemma4-tpu-vllm.yaml
- 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

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é.
- 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'
- 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'
- 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)
- Créez le fichier manifeste de la règle
03-baseline-deny.yamlBaseline-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
- Appliquez la règle du niveau de référence :
kubectl apply -f 03-baseline-deny.yaml
- 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'
- 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.
- Créez le fichier manifeste NetworkPolicy
04-app-networkpolicy.yamldu 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
- Appliquez la règle :
kubectl apply -f 04-app-networkpolicy.yaml
- 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'
- 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.
- Créez le fichier manifeste de la règle de niveau administrateur
05-admin-guardrails.yamlciblant 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
- Appliquez la règle de niveau administrateur :
kubectl apply -f 05-admin-guardrails.yaml
- 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)"
- 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'
- 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
- 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.
- Cliquez sur l'icône "+" dans le menu Cloud Shell pour ouvrir un deuxième onglet de terminal.
- 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
- Revenez au premier onglet Cloud Shell et assurez-vous que vous vous trouvez dans le répertoire
gke-cnp-lab.
cd ~/gke-cnp-lab
- 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
- 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.

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.

10. Effectuer un nettoyage
- Fermez le deuxième onglet.
- Revenez au premier onglet Cloud Shell.
- 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
- Nettoyez l'infrastructure à l'aide de la commande suivante, puis saisissez
yespour confirmer.
terraform destroy --auto-approve
- 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
- Supprimez le répertoire
gke-cnp-labet 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.
- En savoir plus sur la configuration de ClusterNetworkPolicy GKE