1. Présentation
Cet atelier vous présente la création d'une infrastructure d'IA autogérée directement sur Google Compute Engine (GCE). Vous allez amorcer un cluster Kubernetes non géré sur des machines virtuelles (certaines avec des TPU) à l'aide de Terraform et kubeadm, et configurer l'allocation dynamique de ressources (DRA) Kubernetes à l'aide du pilote Open Source. Vous allez travailler avec les éléments suivants :
- Google Compute Engine : fournit les ressources de calcul pour amorcer vos clusters.
- TPU : puces d'accélération conçues sur mesure par Google.
- Kubernetes OSS : logiciel permettant d'installer et de configurer Kubernetes manuellement
- OSS DRANET : pilote de mise en réseau DRA
- OSS DRA pour TPU : pilotes DRA compatibles avec les TPU
Pour configurer l'environnement, vous allez déployer plusieurs réseaux VPC indépendants, chacun avec son propre sous-réseau. Cela vous permet de provisionner vos instances de VM avec plusieurs interfaces réseau (multi-NIC), en séparant le trafic de gestion du trafic de données TPU à haut débit.
Ensuite, pour activer l'allocation dynamique des ressources (DRA) Open Source, vous devez installer le pilote matériel DRA Google TPU et le pilote réseau DRANET. Vous configurerez ensuite les DeviceClasses Kubernetes et écrirez des ResourceClaimTemplates pour gérer le provisionnement dynamique de ces ressources.
Enfin, vous déploirez une charge de travail de benchmarking hautes performances à l'aide de Neper pour valider les chemins de données réseau Jumbo Frame entre vos nœuds de calcul, suivi d'un test Python JAX pour valider le silicium TPU sous-jacent. Vous allez ensuite déployer vLLM pour diffuser le modèle Gemma 4 de Google via Hugging Face en utilisant des revendications DRA de matériel et de réseau entièrement isolés.
Les configurations utiliseront une combinaison de Terraform, gcloud et kubectl.
Dans cet atelier, vous allez apprendre à effectuer la tâche suivante :
- Configurer des réseaux VPC
- Déployer trois nœuds sur GCE (un nœud Standard et deux nœuds TPU v6)
- Amorcer Kubernetes
- Configurer OSS DRANET et DRA pour TPU
- Comparer les performances
- Créer des DeviceClasses et des ResourceClaimTemplates
- Comparer les performances du réseau et du matériel
- Déployer Gemma 4 : diffuser le modèle sur du matériel TPU v6e à l'aide de vLLM et de revendications DRA actives
- Tester la connectivité au LLM
Dans cet atelier, vous allez créer le modèle suivant.
Figure 1.

2. Configurer les 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 non utilisée par les API Google. Vous pourrez toujours le 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 aux TPU. La version exacte utilisée est TPU v6e.
- Pour y accéder, vous devez suivre le document sur le plan TPU et activer le quota TPU.
- Utilisez une région dans laquelle vous disposez d'un quota de TPU. Pour en savoir plus, consultez le document Valider la disponibilité des TPU dans GKE.
- Nous utilisons un petit déploiement nécessitant deux puces TPU v6e (
ct6e-standard-4t)), ce qui correspond à une tranche 2x2 dans une seule région. - Jeton Hugging Face : un jeton d'accès est nécessaire pour télécharger les poids du modèle Gemma.
Nous allons créer trois VPC personnalisés avec des règles de pare-feu et des sous-réseaux. Ouvrez la console Cloud et sélectionnez le projet que vous allez utiliser.
- Ouvrez Cloud Shell en haut à droite de la console. Assurez-vous que le bon ID de projet s'affiche dans Cloud Shell et confirmez les éventuelles invites vous demandant d'autoriser l'accès.

- Créez un dossier nommé
oss-kube-dra,, accédez-y et ajoutez des variables. Remarque : Mettez à jour les valeurs des variables "REGION" et "ZONE" en indiquant votre région et votre zone réelles. La région par défaut utilisée est "europe-west4" et la zone par défaut est "europe-west4-a".
mkdir -p oss-kube-dra && cd oss-kube-dra
export PROJECT_ID=$(gcloud config get-value project)
export REGION="europe-west4"
export ZONE="europe-west4-a"
echo $PROJECT_ID
echo $REGION
echo $ZONE
- Ajoutez maintenant des fichiers de configuration. Ces commandes créeront les fichiers terraform.tfvars, variables.tf et vpc.tf.
cat << EOF > terraform.tfvars
project_id = "${PROJECT_ID}"
region = "${REGION}"
zone = "${ZONE}"
EOF
cat << 'EOF' > variables.tf
variable "project_id" {
type = string
description = "The Google Cloud Project ID"
}
variable "region" {
type = string
description = "The region to deploy the resources"
}
variable "zone" {
type = string
description = "The specific zone for the VMs"
}
variable "control_plane_machine_type" {
type = string
default = "e2-standard-8"
description = "Machine type for the Kubernetes control plane node"
}
variable "tpu_worker_machine_type" {
type = string
default = "ct6e-standard-4t"
description = "The machine type for TPU workers (TPU v6e Trillium VM)"
}
EOF
cat << 'EOF' > vpc.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 7.32.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# 1. Primary Management VPC and Subnet
resource "google_compute_network" "primary_vpc" {
name = "oss-k8s-primary-vpc"
auto_create_subnetworks = false
mtu = 1460
}
resource "google_compute_subnetwork" "primary_subnet" {
name = "oss-k8s-primary-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.primary_vpc.id
}
# 2. Cloud NAT Router and NAT Gateway for Primary VPC (Outbound Access)
resource "google_compute_router" "router" {
name = "oss-k8s-router"
network = google_compute_network.primary_vpc.id
region = var.region
}
resource "google_compute_router_nat" "nat" {
name = "oss-k8s-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"
}
# 3. Firewalls for Primary VPC
resource "google_compute_firewall" "allow_internal" {
name = "oss-k8s-primary-allow-internal"
network = google_compute_network.primary_vpc.id
allow {
protocol = "tcp"
}
allow {
protocol = "udp"
}
allow {
protocol = "icmp"
}
source_ranges = ["10.0.0.0/24"]
}
resource "google_compute_firewall" "allow_iap" {
name = "oss-k8s-allow-iap-ssh"
network = google_compute_network.primary_vpc.id
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["35.235.240.0/20"]
}
# 4. Multi-NIC TPU Networks and Subnets (With Jumbo Frames MTU 8896)
resource "google_compute_network" "tpu_vpc" {
count = 2
name = "oss-tpu-vpc-${count.index + 1}"
auto_create_subnetworks = false
mtu = 8896
}
resource "google_compute_subnetwork" "tpu_subnet" {
count = 2
name = "oss-tpu-vpc-${count.index + 1}-subnet"
ip_cidr_range = "10.${count.index + 1}0.0.0/24"
region = var.region
network = google_compute_network.tpu_vpc[count.index].id
}
resource "google_compute_firewall" "tpu_allow_internal" {
count = 2
name = "oss-tpu${count.index + 1}-allow-internal"
network = google_compute_network.tpu_vpc[count.index].id
allow {
protocol = "tcp"
}
allow {
protocol = "udp"
}
allow {
protocol = "icmp"
}
source_ranges = ["10.${count.index + 1}0.0.0/24"]
}
EOF
- Assurez-vous d'être dans le répertoire
oss-kube-draet exécutez les commandes suivantes :terraform initinitialise le répertoire de travail. Il s'agit de la première étape, qui consiste à télécharger les fournisseurs requis pour la configuration donnée.terraform plan -outgénère un plan d'exécution, qui indique 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 qu'il se passera sans apporter de modifications.terraform applyexécute les mises à jour.
terraform init
terraform plan -out=tfplan
- Exécutez maintenant le déploiement après avoir exécuté
terraform apply. Étant donné que vous appliquez le plan d'exécution enregistré, il s'exécutera immédiatement sans demander de confirmation. (cela peut prendre entre 5 et 10 minutes).
terraform apply tfplan
- Vérifiez la configuration.
echo -e "\n=== Verifying VPC Networks ==="
gcloud compute networks list --filter="name~oss-.*" --project=$PROJECT_ID
echo -e "\n=== Verifying Subnetworks ==="
gcloud compute networks subnets list --filter="name~oss-.*" --project=$PROJECT_ID
echo -e "\n=== Verifying Firewall Rules ==="
gcloud compute firewall-rules list --filter="name~oss-.*" --project=$PROJECT_ID
echo -e "\n=== Verifying Cloud NAT ==="
gcloud compute routers nats list --router=oss-k8s-router --router-region=$REGION --project=$PROJECT_ID
Créer vos nœuds de VM
Vous allez maintenant définir les instances Compute Engine.
- Assurez-vous d'être dans le répertoire
oss-kube-dra, puis exécutez la commande suivante dans Cloud Shell pour écrire le fichiernodes.tf.
cat << 'EOF' > nodes.tf
# 1. K8s Control Plane VM (No TPU)
resource "google_compute_instance" "control_plane" {
name = "k8s-control-plane"
machine_type = var.control_plane_machine_type
zone = var.zone
boot_disk {
initialize_params {
image = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts"
size = 100
}
}
network_interface {
network = google_compute_network.primary_vpc.id
subnetwork = google_compute_subnetwork.primary_subnet.id
# No public IP block keeps this node private
}
service_account {
scopes = ["cloud-platform"]
}
}
# 2. TPU Worker VMs (Multi-NIC ct6e-standard-4t instances)
resource "google_compute_instance" "tpu_workers" {
count = 2
name = "k8s-tpu-worker-${count.index + 1}"
machine_type = var.tpu_worker_machine_type
zone = var.zone
boot_disk {
initialize_params {
image = "projects/ubuntu-os-accelerator-images/global/images/family/ubuntu-accel-2204-amd64-tpu-v5e-v5p-v6e"
size = 200
}
}
scheduling {
on_host_maintenance = "TERMINATE"
provisioning_model = "STANDARD"
}
# NIC 1: Management VPC Subnet
network_interface {
network = google_compute_network.primary_vpc.id
subnetwork = google_compute_subnetwork.primary_subnet.id
}
# NIC 2: TPU VPC 1 Subnet
network_interface {
network = google_compute_network.tpu_vpc[0].id
subnetwork = google_compute_subnetwork.tpu_subnet[0].id
}
# NIC 3: TPU VPC 2 Subnet
network_interface {
network = google_compute_network.tpu_vpc[1].id
subnetwork = google_compute_subnetwork.tpu_subnet[1].id
}
service_account {
scopes = ["cloud-platform"]
}
lifecycle {
ignore_changes = [
boot_disk[0].initialize_params[0].image,
guest_accelerator,
metadata
]
}
}
EOF
- Une fois votre nouvelle configuration écrite, générez un plan et appliquez-le pour provisionner vos instances.
terraform plan -out=tfplan
terraform apply tfplan
- Vérifiez.
echo -e "\n=== Verifying Provisioned VM Instances ==="
gcloud compute instances list --filter="name~k8s-.*" --project=$PROJECT_ID
echo -e "\n=== Verifying Network Interfaces on Workers ==="
for i in 1 2; do
echo -e "\n--- Interfaces for k8s-tpu-worker-${i} ---"
gcloud compute instances describe k8s-tpu-worker-${i} \
--zone=$ZONE \
--project=$PROJECT_ID \
--format="table(networkInterfaces[].network.basename(), networkInterfaces[].networkIP)"
done
4. Amorcer le nœud de contrôle de votre cluster Kubernetes
Dans cette section, vous allez vous connecter de manière sécurisée à l'instance de VM du plan de contrôle que vous venez de créer, configurer le système d'exploitation sous-jacent, installer l'environnement d'exécution de conteneur et les packages Kubernetes, initialiser votre cluster et déployer Calico CNI avec une isolation stricte du trafic sur le réseau de gestion.
- Connectez-vous de manière sécurisée à l'instance
k8s-control-planeà l'aide du tunnel Identity-Aware Proxy (IAP) de GCE. Exécutez la commande suivante dans votre terminal Cloud Shell :
gcloud compute ssh k8s-control-plane \
--zone=$ZONE \
--tunnel-through-iap
- Sur la VM
k8s-control-plane, créez un script nomméinit-control-plane.shpour automatiser les étapes d'installation et de configuration.
cat << 'CONTROL_PLANE_EOF' > init-control-plane.sh
#!/bin/bash
# Strict error handling: fail instantly if any command exits with a non-zero status
set -e
echo "=== 1. Neutralizing Background Updates & Preparing Base OS ==="
# Prevent unattended upgrades from locking apt or breaking network configuration mid-setup
sudo systemctl stop apt-daily.timer apt-daily-upgrade.timer || true
sudo systemctl disable apt-daily.timer apt-daily-upgrade.timer || true
sudo systemctl mask apt-daily.service apt-daily-upgrade.service || true
# Turn off swap (mandatory for Kubernetes)
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# Load required kernel modules
cat << 'EOT' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOT
sudo modprobe overlay
sudo modprobe br_netfilter
# Configure sysctl requirements for Kubernetes bridging
cat << 'EOT' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOT
sudo sysctl --system
echo "=== 2. Installing Container Runtime (Containerd) ==="
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg bash-completion
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
echo "=== 3. Configuring Containerd with Systemd Cgroups ==="
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl daemon-reload
sudo systemctl restart containerd
sudo systemctl enable containerd
# Validation Step: Verify runtime engine health
if ! systemctl is-active --quiet containerd; then
echo "❌ ERROR: Containerd failed to start properly."
exit 1
fi
echo "✅ Containerd runtime is active and healthy."
echo "=== 4. Installing Kubernetes 1.36 Binaries ==="
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# Configure Autocomplete and Aliases system-wide
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null
kubeadm completion bash | sudo tee /etc/bash_completion.d/kubeadm > /dev/null
if ! grep -q 'alias k=kubectl' ~/.bashrc; then
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
fi
echo "=== 5. Initializing Control Plane Engine ==="
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
echo "=== 6. Configuring Administrative Cluster Credentials ==="
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Validation Step: Verify API Server local responsiveness
echo "Waiting for local API server context..."
until kubectl cluster-info &>/dev/null; do
sleep 2
done
echo "✅ Kubernetes API server is responding locally."
echo "=== 7. Deploying Calico Network Operator ==="
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.3/manifests/tigera-operator.yaml
# Validation Step: Ensure Tigera Operator CRD is fully available before applying configuration
echo "Waiting for Tigera Installation CRD to register on the API server..."
kubectl wait --for=condition=established crd/installations.operator.tigera.io --timeout=60s
echo "=== 8. Deploying Calico Custom Resources (Subnet Interlock Locked to 10.0.0.0/24) ==="
cat << 'CALICO_EOF' > custom-calico.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
nodeAddressAutodetectionV4:
cidrs:
- "10.0.0.0/24"
ipPools:
- blockSize: 26
cidr: 192.168.0.0/16
encapsulation: VXLANCrossSubnet
natOutgoing: Enabled
nodeSelector: all()
CALICO_EOF
kubectl apply -f custom-calico.yaml
# Validation Step: Confirm Calico daemon configurations are processing
echo "Waiting 10 seconds for Calico system namespaces to initialize..."
sleep 10
echo "Current Calico workload deployment status:"
kubectl get pods -n calico-system
echo "=== 9. Exporting Worker Cluster Join Token ==="
sudo kubeadm token create --print-join-command > ~/join.sh
chmod +x ~/join.sh
echo "--------------------------------------------------------"
echo "✅ CONTROL PLANE BOOTSTRAP COMPLETE!"
echo "Your cluster join command for the TPU workers is saved below:"
echo "--------------------------------------------------------"
cat ~/join.sh
CONTROL_PLANE_EOF
- Exécutez le script.
chmod +x init-control-plane.sh
./init-control-plane.sh
- Une fois la commande exécutée, validez l'objectif. L'activation de toutes les fonctionnalités prendra quelques minutes.
kubectl get nodes
kubectl get pods -A
Le résultat doit ressembler à ceci :
NAME STATUS ROLES AGE VERSION k8s-control-plane Ready control-plane 6m50s v1.36.2 NAMESPACE NAME READY STATUS RESTARTS AGE calico-system calico-kube-controllers-5578ff64dd-87vp2 1/1 Running 0 6m33s calico-system calico-node-fxzpp 1/1 Running 0 6m33s calico-system calico-typha-785cbc858-rv4nz 1/1 Running 0 6m33s calico-system csi-node-driver-wlrhx 2/2 Running 0 6m33s kube-system coredns-589f44dc88-pqfrl 1/1 Running 0 6m42s kube-system coredns-589f44dc88-sdwmj 1/1 Running 0 6m42s kube-system etcd-k8s-control-plane 1/1 Running 0 6m47s kube-system kube-apiserver-k8s-control-plane 1/1 Running 0 6m47s kube-system kube-controller-manager-k8s-control-plane 1/1 Running 0 6m47s kube-system kube-proxy-jnm2p 1/1 Running 0 6m42s kube-system kube-scheduler-k8s-control-plane 1/1 Running 0 6m47s tigera-operator tigera-operator-6bc8d879b5-w5mrq 1/1 Running 0 6m42s
- Quittez la connexion
sshpour revenir à Cloud Shell.
exit
5. Ajouter les nœuds de calcul TPU
Vous exécuterez un script à partir de Cloud Shell qui se connecte de manière sécurisée à votre VM de plan de contrôle, récupère le jeton de participation au cluster, puis configure et enregistre simultanément vos travailleurs TPU dans le cluster.
- Exécutez la commande suivante dans Cloud Shell pour écrire le script d'orchestration :
cat << 'WORKER_BOOTSTRAP_EOF' > bootstrap-workers.sh
#!/bin/bash
# Strict error handling: fail instantly if any command exits with a non-zero status
set -e
# Fetch the join command safely from the control plane
echo "Fetching join command from Control Plane..."
JOIN_CMD=$(gcloud compute ssh k8s-control-plane --zone=$ZONE --tunnel-through-iap --command="cat ~/join.sh" 2>/dev/null)
if [ -z "$JOIN_CMD" ]; then
echo "❌ ERROR: Failed to retrieve the join command. Ensure the control plane is reachable."
exit 1
fi
echo "✅ Successfully retrieved join command."
# Create the setup script locally to be copied to the workers
cat << 'WORKER_INIT_EOF' > init-worker.sh
#!/bin/bash
set -e
echo "=== 1. Neutralizing Background Updates & Setting Non-Interactive Mode ==="
export DEBIAN_FRONTEND=noninteractive
sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/g" /etc/needrestart/needrestart.conf 2>/dev/null || true
# Prevent unattended upgrades from tearing down network interfaces mid-setup
sudo systemctl stop apt-daily.timer apt-daily-upgrade.timer || true
sudo systemctl disable apt-daily.timer apt-daily-upgrade.timer || true
sudo systemctl mask apt-daily.service apt-daily-upgrade.service || true
echo "=== 2. Base OS Prep ==="
# Disable swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# Load required kernel modules
cat << 'EOT' | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOT
sudo modprobe overlay
sudo modprobe br_netfilter
# Configure bridging and IP forwarding sysctls
cat << 'EOT' | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOT
sudo sysctl --system
echo "=== 3. Installing Containerd (CRI-Only) ==="
sudo apt-get update && sudo apt-get install -yq ca-certificates curl gnupg bash-completion
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install only containerd to avoid unnecessary Docker CE overhead
sudo apt-get update && sudo apt-get install -yq containerd.io
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl daemon-reload
sudo systemctl restart containerd
sudo systemctl enable containerd
# Validation: Check containerd status
if ! systemctl is-active --quiet containerd; then
echo "❌ ERROR: Containerd failed to start."
exit 1
fi
echo "=== 4. Installing Kubernetes 1.36 Binaries ==="
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update && sudo apt-get install -yq kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
WORKER_INIT_EOF
# Append the actual join command to the script
echo "echo \"=== 5. Joining Cluster ===\"" >> init-worker.sh
echo "sudo $JOIN_CMD" >> init-worker.sh
# Push and run on both Workers concurrently
echo "Starting concurrent bootstrap on both workers..."
(
echo "[Worker 1] Copying script..."
gcloud compute scp init-worker.sh k8s-tpu-worker-1:~ --zone=$ZONE --tunnel-through-iap --quiet
echo "[Worker 1] Executing script..."
gcloud compute ssh k8s-tpu-worker-1 --zone=$ZONE --tunnel-through-iap --command="bash ~/init-worker.sh"
echo "✅ [Worker 1] Bootstrap and Join complete!"
) &
(
echo "[Worker 2] Copying script..."
gcloud compute scp init-worker.sh k8s-tpu-worker-2:~ --zone=$ZONE --tunnel-through-iap --quiet
echo "[Worker 2] Executing script..."
gcloud compute ssh k8s-tpu-worker-2 --zone=$ZONE --tunnel-through-iap --command="bash ~/init-worker.sh"
echo "✅ [Worker 2] Bootstrap and Join complete!"
) &
# Wait for both background processes to finish
wait
echo "--------------------------------------------------------"
echo "✅ BOTH WORKERS HAVE FINISHED PROCESSING"
echo "--------------------------------------------------------"
# Final Validation Check from Control Plane
echo "Verifying cluster node status..."
sleep 5 # Give kubelet a moment to register the nodes
gcloud compute ssh k8s-control-plane --zone=$ZONE --tunnel-through-iap --command="kubectl get nodes -o wide"
WORKER_BOOTSTRAP_EOF
- Exécutez la configuration du nœud de calcul. (Ce processus exécute les deux installations simultanément en arrière-plan et prend environ trois à cinq minutes.)
chmod +x bootstrap-workers.sh
./bootstrap-workers.sh
Vous devriez voir quelque chose de similaire lorsque tous les nœuds sont ajoutés au cluster.
To increase the performance of the tunnel, consider installing NumPy. For instructions, please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME k8s-control-plane Ready control-plane 25m v1.36.2 10.0.0.2 <none> Ubuntu 22.04.5 LTS 6.8.0-1064-gcp (amd64) containerd://2.2.6 k8s-tpu-worker-1 NotReady <none> 10s v1.36.2 10.0.0.3 <none> Ubuntu 22.04.5 LTS 6.8.0-1064-gcp (amd64) containerd://2.2.6 k8s-tpu-worker-2 Ready <none> 27s v1.36.2 10.0.0.4 <none> Ubuntu 22.04.5 LTS 6.8.0-1064-gcp (amd64) containerd://2.2.6
6. Déployer le pilote TPU OSS DRA
Dans cette section, vous allez revenir au plan de contrôle, attribuer à vos nœuds de calcul TPU des libellés contenant des informations spécifiques sur leur topologie d'accélérateur, puis installer le pilote Google TPU DRA Open Source à l'aide de Helm. Ce pilote est chargé de découvrir les puces TPU v6e physiques et de les mapper de manière native à l'API Kubernetes.
- Reconnectez-vous de manière sécurisée à la VM
k8s-control-planedepuis Cloud Shell.
gcloud compute ssh k8s-control-plane \
--zone=$ZONE \
--tunnel-through-iap
- Exécutez ces commandes dans votre session SSH
k8s-control-plane. Libeller les nœuds avec l'ensemble complet de libellés (y compris les clés de nombre exact de chips)
kubectl label node k8s-tpu-worker-1 \
cloud.google.com/gke-tpu-accelerator=tpu-v6e-slice \
cloud.google.com/gke-tpu-topology=2x2 \
cloud.google.com/gke-tpu-dra-driver=true \
cloud.google.com/gke-accelerator-count=4 \
cloud.google.com/gke-tpu-count=4 \
--overwrite
kubectl label node k8s-tpu-worker-2 \
cloud.google.com/gke-tpu-accelerator=tpu-v6e-slice \
cloud.google.com/gke-tpu-topology=2x2 \
cloud.google.com/gke-tpu-dra-driver=true \
cloud.google.com/gke-accelerator-count=4 \
cloud.google.com/gke-tpu-count=4 \
--overwrite
- Cloner et installer le pilote DRA TPU avec Helm
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
git clone https://github.com/kubernetes-sigs/dra-driver-google-tpu.git ~/dra-driver-google-tpu || true
cd ~/dra-driver-google-tpu
rm -f *.pack *.tgz
helm install dra-driver-google-tpu ./deployments/helm/dra-driver-google-tpu \
-n dra-driver-google-tpu \
--create-namespace \
--set 'kubeletPlugin.env[0].name=NODE_NAME' \
--set 'kubeletPlugin.env[0].valueFrom.fieldRef.fieldPath=spec.nodeName'
cd ~
- Valider la configuration du pilote DRA TPU
# Verify driver daemonset status (Pods should show as Running and Ready)
kubectl get pods -n dra-driver-google-tpu -o wide
# Verify TPU ResourceSlices are successfully published to the API server
kubectl get resourceslices
# Safely parse the ResourceSlices to show the Node Name and the number of TPU chips registered
kubectl get resourceslices -o json | jq -r '.items[] | select(.spec.driver=="tpu.google.com") | "Node: \(.spec.nodeName) | TPUs Registered: \(.spec.devices | length)"'
# Inspect driver logs to confirm the TPU hardware was initialized successfully
kubectl logs -n dra-driver-google-tpu -l app.kubernetes.io/name=dra-driver-google-tpu -c tpu-dra-plugin --tail=20
7. Déployer des classes d'appareils et DRANET Open Source
Dans cette section, vous allez revenir au plan de contrôle, installer le pilote DRANET Open Source, appliquer un correctif de filtre personnalisé pour exclure les interfaces virtuelles, et établir votre DeviceClass et votre ResourceClaimTemplate Kubernetes avec les préfixes réseau OSS correspondants.
- Reconnectez-vous de manière sécurisée à la VM
k8s-control-planedepuis Cloud Shell. Si vous êtes déjà connecté, passez cette étape.
gcloud compute ssh k8s-control-plane \
--zone=$ZONE \
--tunnel-through-iap
- Exécutez ces commandes dans votre session
k8s-control-planeSSH.
# Install the core components and patch
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/dranet/refs/heads/main/install.yaml
kubectl patch daemonset dranet -n kube-system --type='json' -p='[ { "op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "-filter=!(\"dra.net/type\" in attributes) || (attributes[\"dra.net/type\"].StringValue != \"veth\" && attributes[\"dra.net/type\"].StringValue != \"vxlan\" && attributes[\"dra.net/type\"].StringValue != \"bridge\")" } ]'
# Monitor rollout readiness
kubectl rollout status daemonset/dranet -n kube-system
# Verify running components and permissions
kubectl get pods -n kube-system -l app=dranet -o wide
kubectl get clusterrole,clusterrolebinding,sa dranet -n kube-system
# Interrogate logs for driver binding confirmation
kubectl logs -n kube-system -l app=dranet --tail=20
- Appliquer DeviceClass et ResourceClaimTemplate
# Apply DRANET DeviceClass and BOTH ResourceClaimTemplates (Network + Hardware)
cat << 'EOF' | kubectl apply -f -
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: dranet
spec:
selectors:
- cel:
expression: device.driver == "dra.net"
---
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: tpu-net-interfaces
namespace: default
spec:
spec:
devices:
requests:
- name: tpu-net-interface
exactly:
deviceClassName: dranet
count: 2
selectors:
- cel:
expression: device.attributes["gce.dra.net"].networkName.startsWith("oss-tpu-vpc")
config:
- opaque:
driver: dra.net
parameters:
interface:
mtu: 8896
gsoMaxSize: 65536
groMaxSize: 65536
gsoIPv4MaxSize: 65536
groIPv4MaxSize: 65536
disableEbpfPrograms: true
---
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: tpu-device-template
namespace: default
spec:
spec:
devices:
requests:
- name: tpu-devices
exactly:
deviceClassName: tpu.google.com
allocationMode: ExactCount
count: 4
EOF
- Vérifiez que vos modèles et vos classes sont correctement enregistrés dans l'API Kubernetes.
# Verify ResourceSlices exist and are actively serving both drivers
kubectl get resourceslices -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,DRIVER:.spec.driver | grep -E "dra.net|tpu.google.com"
# Verify the DRANET daemonset pods are Running across all nodes
kubectl get pods -n kube-system -l app=dranet -o wide
- Déployez le StatefulSet Parallel Neper.
cat << 'EOF' | kubectl apply -f -
---
apiVersion: v1
kind: Service
metadata:
name: neper
spec:
clusterIP: None
selector:
app: neper
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: neper
spec:
selector:
matchLabels:
app: neper
serviceName: neper
replicas: 2
template:
metadata:
labels:
app: neper
spec:
initContainers:
- name: "network-optimization-sysctls"
image: "busybox"
securityContext:
privileged: true
command:
- sh
- -c
- |
echo 5000 > /proc/sys/net/ipv4/tcp_rto_min_us
echo 1 > /proc/sys/net/ipv4/tcp_no_metrics_save
echo 0 > /proc/sys/net/ipv4/tcp_slow_start_after_idle
echo 131072 > /proc/sys/net/core/optmem_max
echo "4096 41943040 314572800" > /proc/sys/net/ipv4/tcp_rmem
containers:
- name: neper
image: ubuntu:22.04
command:
- /bin/bash
- -c
- |
apt-get update && apt-get install -y iproute2 build-essential git jq python3-pip &&
git clone https://github.com/google/neper.git /tmp/neper &&
cd /tmp/neper && make &&
cp tcp_stream /usr/local/bin/ &&
sleep infinity
securityContext:
privileged: true
resources:
requests:
cpu: "170"
memory: "650Gi"
limits:
cpu: "170"
memory: "650Gi"
claims:
- name: tpu-net-claim
- name: tpu-hardware-claim
resourceClaims:
- name: tpu-net-claim
resourceClaimTemplateName: tpu-net-interfaces
- name: tpu-hardware-claim
resourceClaimTemplateName: tpu-device-template
EOF
- Vérification de la validation
echo -e "\n=== Verifying StatefulSet Pod Status ==="
kubectl get pods -l app=neper -o wide
echo -e "\n=== Verifying Dynamic Resource Claims (DRCs) ==="
kubectl get resourceclaims
echo -e "\n=== Inspecting Device Claim Allocation ==="
# Using a safer JSONPath query to extract the allocated drivers and devices
kubectl get resourceclaims -o json | jq -r '.items[] | "Claim: \(.metadata.name) | Driver: \(.status.allocation.devices.results[0].driver // "Pending")"'
8. Effectuer le test
Exécutez la suite de validation matérielle et d'analyse comparative à double interface.
Phase 1 (analyse comparative du réseau) : elle attend que les deux pods Neper (neper-0 et neper-1) compilent les dépendances, extrait les adresses IP multi-NIC non définies par défaut liées via DRANET, lance des serveurs tcp_stream simultanés sur neper-1, génère une charge à haut débit à partir de neper-0 et analyse le débit agrégé en gigabits par seconde (Gbit/s).
Phase 2 (validation du matériel) : elle installe Google JAX dans neper-0 et exécute la multiplication matricielle (5 000 x 5 000) directement sur les puces TPU mappées via VFIO pour confirmer l'état opérationnel du silicium.
- Exécutez la commande suivante dans votre
k8s-control-planepour écrirerun_dual_neper_test.sh
cat << 'EOF' > run_dual_neper_test.sh
#!/bin/bash
set -e
SERVER_POD="neper-1"
CLIENT_POD="neper-0"
echo "================================================="
echo " PHASE 1: DUAL-INTERFACE HIGH-SPEED NETWORK TEST"
echo "================================================="
echo "=== Waiting for Pods to be Ready ==="
kubectl wait --for=condition=ready pod/$CLIENT_POD pod/$SERVER_POD --timeout=300s
echo "=== Waiting for neper compilation to finish inside Pods ==="
for POD in $SERVER_POD $CLIENT_POD; do
until kubectl exec $POD -c neper -- sh -c 'command -v jq >/dev/null 2>&1 && command -v tcp_stream >/dev/null 2>&1'; do
sleep 5
done
done
echo ""
echo "=== Step 1: Extract Target IPs from $SERVER_POD ==="
# Using jq to parse the network interfaces directly from Linux JSON output
IFACE1=$(kubectl exec $SERVER_POD -c neper -- sh -c "ip -j -4 addr show | jq -r '.[] | select(.ifname != \"lo\" and .ifname != \"eth0\") | .ifname' | sed -n '1p'")
IFACE2=$(kubectl exec $SERVER_POD -c neper -- sh -c "ip -j -4 addr show | jq -r '.[] | select(.ifname != \"lo\" and .ifname != \"eth0\") | .ifname' | sed -n '2p'")
IP1=$(kubectl exec $SERVER_POD -c neper -- sh -c "ip -j -4 addr show | jq -r '.[] | select(.ifname != \"lo\" and .ifname != \"eth0\") | .addr_info[0].local' | sed -n '1p'")
IP2=$(kubectl exec $SERVER_POD -c neper -- sh -c "ip -j -4 addr show | jq -r '.[] | select(.ifname != \"lo\" and .ifname != \"eth0\") | .addr_info[0].local' | sed -n '2p'")
echo " 📍 Target IP 1 ($IFACE1): $IP1"
echo " 📍 Target IP 2 ($IFACE2): $IP2"
echo ""
echo "=== Step 2: Initialize TCP Servers on $SERVER_POD ==="
kubectl exec $SERVER_POD -c neper -- sh -c '
for i in 0 1; do
nohup tcp_stream -C$((52279 + i)) --port=$((38339 + i)) --skip-rx-copy -rw -Z -B16384 \
--test-length=60 --suicide-length=120 -F100 --num-threads=16 --num-flows=32 -D0 \
--logtostderr > test${i}.log 2>&1 &
done
'
sleep 3
echo "=== Step 3: Generate Concurrent High-Throughput Load from $CLIENT_POD ==="
echo "Blasting Traffic via Interface 1 -> $IP1 ..."
kubectl exec $CLIENT_POD -c neper -- sh -c "nohup tcp_stream -C52279 --port=38339 --skip-rx-copy -rw -Z -B16384 \
--test-length=60 --suicide-length=70 -F100 --num-threads=16 --num-flows=32 \
--client -H $IP1 -D0 --logtostderr > test0.log 2>&1 &"
echo "Blasting Traffic via Interface 2 -> $IP2 ..."
kubectl exec $CLIENT_POD -c neper -- sh -c "nohup tcp_stream -C52280 --port=38340 --skip-rx-copy -rw -Z -B16384 \
--test-length=60 --suicide-length=70 -F100 --num-threads=16 --num-flows=32 \
--client -H $IP2 -D0 --logtostderr > test1.log 2>&1 &"
echo ""
echo "=== Testing in progress... Waiting 65 seconds for test completion ==="
sleep 65
echo ""
echo "=== Step 4: Evaluate Throughput Metrics ==="
RAW_BPS1=$(kubectl exec $CLIENT_POD -c neper -- grep -a "remote_throughput=" test0.log | cut -d= -f2 | tr -d '\r' || echo "0")
RAW_BPS2=$(kubectl exec $CLIENT_POD -c neper -- grep -a "remote_throughput=" test1.log | cut -d= -f2 | tr -d '\r' || echo "0")
GBPS1=$(awk -v bps="$RAW_BPS1" 'BEGIN { printf "%.2f", bps / 1000000000 }')
GBPS2=$(awk -v bps="$RAW_BPS2" 'BEGIN { printf "%.2f", bps / 1000000000 }')
TOTAL=$(awk -v b1="$RAW_BPS1" -v b2="$RAW_BPS2" 'BEGIN { printf "%.2f", (b1 + b2) / 1000000000 }')
echo "📊 --- NETWORK RESULTS ---"
echo "Interface 1 ($IFACE1) : ${GBPS1} Gbps"
echo "Interface 2 ($IFACE2) : ${GBPS2} Gbps"
echo "🔥 TOTAL AGGREGATE : ${TOTAL} Gbps"
echo "--------------------------"
echo ""
echo "================================================="
echo " PHASE 2: TPU HARDWARE VALIDATION TEST"
echo "================================================="
echo "⏳ Installing Python and Google JAX on $CLIENT_POD (Takes ~1 minute)..."
kubectl exec $CLIENT_POD -c neper -- bash -c "apt-get update > /dev/null 2>&1 && apt-get install -y python3-pip > /dev/null 2>&1 && pip3 install jax[tpu] -f https://storage.googleapis.com/jax-releases/libtpu_releases.html > /dev/null 2>&1"
echo "🧠 Running matrix math directly on the TPU chips..."
kubectl exec $CLIENT_POD -c neper -- python3 -c "
import jax
import jax.numpy as jnp
print(f'✅ TPU Hardware Detected: {jax.device_count()} chips mapped via vfio')
print('🚀 Executing 5000x5000 Matrix Multiplication on TPU silicon...')
x = jnp.ones((5000, 5000))
y = jnp.dot(x, x)
print('✅ Success! The TPU driver is fully operational and executing math.')
"
EOF
chmod +x run_dual_neper_test.sh
- Exécutez le test. Cette opération prendra deux minutes.
./run_dual_neper_test.sh
Une fois l'opération terminée, la sortie de votre terminal affichera les métriques validées pour la mise en réseau à haut débit et l'exécution des calculs matriciels TPU.
=== Step 4: Evaluate Throughput Metrics === 📊 --- NETWORK RESULTS --- Interface 1 (ens9) : 157.51 Gbps Interface 2 (ens10) : 167.04 Gbps 🔥 TOTAL AGGREGATE : 324.55 Gbps -------------------------- ================================================= PHASE 2: TPU HARDWARE VALIDATION TEST ================================================= ⏳ Installing Python and Google JAX on neper-0 (Takes ~1 minute)... 🧠 Running matrix math directly on the TPU chips... ✅ TPU Hardware Detected: 4 chips mapped via vfio 🚀 Executing 5000x5000 Matrix Multiplication on TPU silicon... ✅ Success! The TPU driver is fully operational and executing math.
9. Déployer Gemma 4 sur votre cluster
Dans cette section, vous allez configurer vos identifiants sécurisés de l'API Hugging Face en tant que secret Kubernetes, déployer votre moteur d'inférence vLLM en utilisant à la fois votre réseau d'allocation dynamique des ressources (DRA) et vos revendications matérielles, et exécuter une requête de test de bout en bout sur le modèle Gemma 4 de Google.
Assurez-vous d'être connecté à votre session SSH sécurisée sur k8s-control-plane :
- Reconnectez-vous de manière sécurisée à la VM du plan de contrôle depuis Cloud Shell. Si vous êtes déjà connecté, ignorez cette étape.
gcloud compute ssh k8s-control-plane \
--zone=$ZONE \
--tunnel-through-iap
- Nettoyer les déploiements précédents
# 1. Delete the StatefulSet to stop the benchmarking pods
kubectl delete statefulset neper
# 2. Wait for the pods to terminate fully and release the claims
kubectl wait --for=delete pod/neper-0 pod/neper-1 --timeout=60s
- Stockez votre jeton d'accès Hugging Face. Remplacez
<YOUR_ACTUAL_HUGGING_FACE_TOKEN>par votre jeton.
export HF_TOKEN="<YOUR_ACTUAL_HUGGING_FACE_TOKEN>"
- Créer un secret
kubectl create secret generic hf-token --from-literal=token="${HF_TOKEN}"
- Ce fichier manifeste planifie une seule instance répliquée de vLLM s'exécutant sur une VM TPU brute à quatre puces. Il utilise la norme DRA de Kubernetes pour monter à la fois vos revendications de réseau personnalisées (tpu-net-claim) et vos revendications matérielles (tpu-hardware-claim) afin d'accéder de manière sécurisée au matériel TPU brut sans nécessiter de montages de volumes hôtes non sécurisés. Enfin, il expose le serveur d'API compatible avec OpenAI sur le port 8080. Exécutez la commande suivante pour créer le fichier :
cat << 'EOF' > gemma-inference.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-gemma-4
labels:
app: gemma-server
spec:
replicas: 1
selector:
matchLabels:
app: gemma-server
template:
metadata:
labels:
app: gemma-server
spec:
hostIPC: true
containers:
- name: vllm-tpu
image: vllm/vllm-tpu:latest
securityContext:
privileged: true
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: token
- name: JAX_PLATFORMS
value: "tpu,cpu"
- name: TPU_ACCELERATOR_TYPE
value: "v6e-4"
- name: TPU_WORKER_HOSTNAMES
value: "127.0.0.1"
- name: TPU_WORKER_ID
value: "0"
- name: LIBTPU_INIT_ARGS
value: "--noenable_tpunetd_client"
- name: BARE_METAL_MODE
value: "true"
- name: BYPASS_VBAR_CONTROL_SERVICE
value: "1"
- name: TPU_SKIP_MDS_QUERY
value: "1"
- name: TPU_DEFAULT_NETWORK_TYPE
value: "loopback"
- name: CHIPS_PER_HOST_BOUNDS
value: "2,2,1"
- name: HOST_BOUNDS
value: "1,1,1"
- name: ALT
value: "false,false,false"
- name: WRAP
value: "false,false,false"
command:
- bash
- -c
- |
export PYTHONUNBUFFERED=1
sysctl -w net.ipv6.conf.all.disable_ipv6=0
sysctl -w net.ipv6.conf.default.disable_ipv6=0
sysctl -w net.ipv6.conf.lo.disable_ipv6=0
ip link set lo up || true
exec python3 -m vllm.entrypoints.openai.api_server \
--model google/gemma-4-E4B-it \
--tensor-parallel-size 4 \
--trust-remote-code \
--max-model-len 8192 \
--max-num-batched-tokens 4096 \
--host 0.0.0.0 \
--port 8080
ports:
- containerPort: 8080
resources:
requests:
cpu: "170"
memory: "650Gi"
limits:
cpu: "170"
memory: "650Gi"
claims:
- name: tpu-net-claim
- name: tpu-hardware-claim
volumeMounts:
- name: dshm
mountPath: /dev/shm
volumes:
- name: dshm
emptyDir:
medium: Memory
resourceClaims:
- name: tpu-net-claim
resourceClaimTemplateName: tpu-net-interfaces
- name: tpu-hardware-claim
resourceClaimTemplateName: tpu-device-template
---
apiVersion: v1
kind: Service
metadata:
name: vllm-gemma-service
spec:
selector:
app: gemma-server
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP
EOF
- Déployer la charge de travail d'inférence
kubectl apply -f gemma-inference.yaml
- Vérifiez l'état du déploiement. Cette configuration doit télécharger le modèle et charger
vLLM. This, ce qui peut prendre entre10 - 25 minutes..
kubectl get pods -l app=gemma-server
kubectl describe pods -l app=gemma-server
Vous pouvez également consulter les journaux du conteneur pour voir le processus. Appuyez sur CTRL+C pour quitter la vue du journal.
kubectl logs -l app=gemma-server -f
Vous saurez que le moteur est complètement initialisé lorsque les lignes
(APIServer pid=1) INFO: Started server process [1]
(APIServer pid=1) INFO: Waiting for application startup.
(APIServer pid=1) INFO: Application startup complete.
Appuyez sur CTRL+C pour quitter le flux de journaux avant de continuer.
- Vérifiez le rattachement d'interface. Vérifier les interfaces réseau liées dans le conteneur
kubectl exec deployment/vllm-gemma-4 -c vllm-tpu -- ls /sys/class/net
kubectl exec deployment/vllm-gemma-4 -c vllm-tpu -- cat /proc/net/fib_trie | grep -B 1 "32 host"
Éléments à rechercher : vous devriez voir ens9 et ens10 (ou des noms ensX similaires) à côté de votre interface CNI standard (eth0) et de la boucle locale (lo). Ils représentent les interfaces réseau PCI de l'hôte GCE physique liées dynamiquement à l'intérieur de votre pod par le pilote DRANET Open Source à l'aide de la convention de dénomination prévisible des emplacements de systemd.
kubectl exec deployment/vllm-gemma-4 -c vllm-tpu -- ls /sys/class/net
ens10
ens9
eth0
Lo
kubectl exec deployment/vllm-gemma-4 -c vllm-tpu -- cat /proc/net/fib_trie | grep -B 1 "32 host"
|-- 10.10.0.3
/32 host LOCAL
--
|-- 10.20.0.3
/32 host LOCAL
--
|-- 127.0.0.1
/32 host LOCAL
--
|-- 192.168.238.67
/32 host LOCAL
--
|-- 10.10.0.3
/32 host LOCAL
--
|-- 10.20.0.3
/32 host LOCAL
--
|-- 127.0.0.1
/32 host LOCAL
--
|-- 192.168.238.67
/32 host LOCAL
10. Tester le LLM
Une fois vos interfaces validées, lancez un conteneur de test léger dans votre cluster pour envoyer une requête d'inférence de streaming à Gemma 4.
- Exécutez la commande suivante dans la session
k8s-control-planepour lancer le client interactif :
kubectl run gemma-chat --rm -i --tty --image=alpine --restart=Never -- sh -c '
# 1. Silently install curl and jq
apk add --no-cache curl jq > /dev/null
echo -e "\n========================================================"
echo -e "💬 Welcome to the Gemma 4 Real-Time CLI Chat client!"
echo -e "========================================================"
echo -e " Type your prompt below. Type '\''exit'\'' or '\''quit'\'' to end."
echo -e "========================================================\n"
while true; do
# Read user input
echo -n -e "👤 \033[1;34mYou:\033[0m "
read -r USER_INPUT
# Handle exit conditions
if [ "$USER_INPUT" = "exit" ] || [ "$USER_INPUT" = "quit" ] || [ -z "$USER_INPUT" ]; then
echo -e "\n👋 Goodbye!"
break
fi
echo -n -e "🤖 \033[1;32mGemma:\033[0m "
# Use jq to safely escape double quotes and special characters in user input
JSON_PAYLOAD=$(jq -n --arg msg "$USER_INPUT" '\''{
model: "google/gemma-4-E4B-it",
messages: [{role: "user", content: $msg}],
temperature: 0.7,
stream: true
}'\'')
# Stream the tokens in real-time with a typewriter effect
curl -s -X POST http://vllm-gemma-service:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" | while read -r line; do
# Extract SSE data streams
if echo "$line" | grep -q "data:"; then
DATA_CLEAN=$(echo "$line" | sed "s/^data: //" | tr -d "\r")
if [ "$DATA_CLEAN" != "[DONE]" ] && [ -n "$DATA_CLEAN" ]; then
# Parse and print only the token content
TOKEN=$(echo "$DATA_CLEAN" | jq -r ".choices[0].delta.content // empty" 2>/dev/null)
echo -n "$TOKEN"
fi
fi
done
echo -e "\n"
done
'
Interactive chat

11. Effectuer un nettoyage
Commencez par supprimer toutes les charges de travail, tous les secrets et toutes les configurations de votre cluster.
Si vous êtes toujours connecté à votre session SSH sécurisée k8s-control-plane, exécutez directement la commande suivante. (Si vous avez déjà quitté la session, reconnectez-vous d'abord en SSH) :
- Reconnectez-vous de manière sécurisée à la VM du plan de contrôle depuis Cloud Shell. Si vous êtes déjà connecté à cette VM, ignorez cette étape.
gcloud compute ssh k8s-control-plane \
--zone=$ZONE \
--tunnel-through-iap
- Nettoyer les ressources Kubernetes
# 1. Delete the Gemma 4 deployment and service
kubectl delete -f gemma-inference.yaml --ignore-not-found=true
# 2. Delete the Hugging Face access secret
kubectl delete secret hf-token --ignore-not-found=true
# 3. Delete the open-source DRANET specs and drivers
kubectl delete deviceclass dranet --ignore-not-found=true
kubectl delete resourceclaimtemplate tpu-net-interfaces --ignore-not-found=true
kubectl delete -f https://raw.githubusercontent.com/kubernetes-sigs/dranet/refs/heads/main/install.yaml --ignore-not-found=true
# 4. Uninstall the OSS TPU Hardware Driver
helm uninstall dra-driver-google-tpu -n dra-driver-google-tpu --wait || true
- Maintenant, saisissez
exitet revenez à votre répertoire Cloud Shell actif où sont stockés vos fichiers Terraform, puis détruisez tous les nœuds, réseaux VPC et règles de pare-feu.
# 1. Create the teardown script
cat << 'EOF' > teardown.sh
#!/bin/bash
# The specific networks defined in your Terraform vpc.tf
NETWORKS=(
"oss-k8s-primary-vpc"
"oss-tpu-vpc-1"
"oss-tpu-vpc-2"
)
echo "=== Hunting down and deleting ALL firewall rules for OSS networks ==="
for NETWORK in "${NETWORKS[@]}"; do
echo "Searching for firewall rules attached to network: $NETWORK..."
# Query GCP for any firewall rule tied to this specific network
STUCK_RULES=$(gcloud compute firewall-rules list \
--filter="network:($NETWORK)" \
--format="value(name)" | tr '\n' ' ')
# Check if the string is not empty and contains more than just whitespace
if [ -n "$STUCK_RULES" ] && [ "$STUCK_RULES" != " " ]; then
echo "🔥 Found rules holding $NETWORK hostage: $STUCK_RULES"
echo "Deleting them now..."
gcloud compute firewall-rules delete $STUCK_RULES --quiet
else
echo "✅ No firewall rules found for $NETWORK."
fi
done
# Fallback: Explicitly delete the named rules from your Terraform file
# just in case the dynamic filter missed them due to caching delays
echo "=== Running fallback deletion for explicitly named Terraform rules ==="
gcloud compute firewall-rules delete \
oss-k8s-primary-allow-internal \
oss-k8s-allow-iap-ssh \
oss-tpu1-allow-internal \
oss-tpu2-allow-internal \
--quiet 2>/dev/null || true
echo "--------------------------------------------------------"
echo "✅ Firewall cleanup complete!"
echo "Your networks are now stripped of firewalls and ready to be deleted."
echo "--------------------------------------------------------"
echo "=== Destroying Infrastructure ==="
cd ~/oss-kube-dra || exit
terraform destroy -auto-approve
echo "--------------------------------------------------------"
echo "✅ Infrastructure successfully destroyed!"
echo "--------------------------------------------------------"
EOF
# 2. Make the script executable and run it
chmod +x teardown.sh
./teardown.sh
- Supprimez le dossier Terraform
oss-kube-dra.
cd
rm -r oss-kube-dra
12. Félicitations
Vous avez provisionné, amorcé et validé une infrastructure d'IA Kubernetes autogérée et hautes performances directement sur des instances de VM Google Compute Engine (GCE).
Vous avez désormais une compréhension approfondie au niveau du système de la façon dont Kubernetes utilise l'allocation dynamique de ressources (DRA) pour orchestrer les accélérateurs TPU bruts, lier les topologies d'hôte multi-NIC à haut débit et diffuser des modèles de langage volumineux de pointe.
Étapes suivantes et informations supplémentaires
Pour en savoir plus sur la mise en réseau GKE
Atelier suivant
Continuez sur votre lancée avec Google Cloud et découvrez ces autres ateliers Google Cloud :