Présentation
Ce document fournit des instructions détaillées pour déployer des grands modèles de langage (LLM) open source tels que Gemma, Llama et DeepSeek dans des environnements Google Distributed Cloud (GDC) air-gapped. Il explique comment utiliser vLLM pour le service à haut débit et Ollama pour la facilité d'utilisation, en exploitant les capacités de la plate-forme GDC, y compris Kubernetes, Harbor et les ressources GPU.
Architecture
La solution consiste à déployer des backends de diffusion LLM conteneurisés (vLLM, Ollama) en tant que déploiements dans un cluster d'utilisateur. Les pondérations du modèle sont stockées dans des volumes persistants, qui sont remplis à partir d'images du registre Harbor. Les services Kubernetes de type LoadBalancer exposent les API des backends. Les règles de réseau du projet sécurisent l'accès à ces services.

Avant de commencer
Assurez-vous de remplir les conditions préalables suivantes :
- GDC sous air gap version 1.15.1 ou ultérieure.
- Cluster utilisateur créé avec des ressources suffisantes (CPU, mémoire, GPU).
- Un GPU NVIDIA A100 minimum est requis.
- L'instance Harbor est disponible et accessible.
- Les CLI
kubectletgdcloudsont configurées pour accéder au cluster d'utilisateur. - Le client Docker est installé et configuré pour envoyer des données à Harbor.
- Les autorisations IAM nécessaires sont accordées (par exemple, Administrateur de l'espace de noms, développeur de cluster**).
- Un compte Hugging Face et une authentification configurés si vous utilisez des modèles avec accès restreint.
Section 1 : Configuration courante
1.1 Créer un secret d'extraction d'image
Pour configurer un secret d'extraction d'image pour une charge de travail de conteneur dans GDC sous air gap, vous devez créer un secret Kubernetes docker-registry contenant les identifiants permettant d'accéder à votre projet Harbor privé. Ce secret est ensuite référencé dans votre spécification de déploiement.
Vous devez utiliser un compte robot Harbor pour accéder de manière programmatique aux images dans les projets Harbor privés.
Pour configurer le secret d'extraction d'image, procédez comme suit :
Créez un compte robot Harbor :
- Accédez à l'UI de votre instance Harbor.
- Accédez à votre projet Harbor.
- Sélectionnez l'onglet Comptes robot.
- Cliquez sur Nouveau compte robot.
- Donnez-lui un nom (par exemple,
oss-llm-puller) et accordez-lui les autorisations nécessaires (au moins l'accès pull) jusqu'à une heure d'expiration. - Stockez de manière sécurisée le nom du compte robot (par exemple,
robot$oss-llm-puller) et le jeton secret fourni.
Authentifiez Docker auprès de Harbor :
Sur votre machine sur laquelle Docker est installé et qui a accès au réseau du registre Harbor, connectez-vous à l'aide des identifiants du compte robot :
export INSTANCE_URL="HARBOR_INSTANCE_URL"
# for example, harbor1-project1.org1.zone1.google.gdc.com
export ROBOT_NAME="ROBOT_ACCOUNT_NAME"
# for example, robot\$oss-llm-puller (note how we escape the $ character)
export ROBOT_SECRET="ROBOT_ACCOUNT_SECRET"
docker login ${INSTANCE_URL} --username ${ROBOT_NAME} --password ${ROBOT_SECRET}
Créez le secret Kubernetes pour l'extraction d'images :
Utilisez kubectl pour créer un secret de type docker-registry dans l'espace de noms de votre projet, à l'aide du fichier de configuration Docker mis à jour à l'étape précédente :
# Log in into GDC environment using the next commands
gdcloud auth login --login-config-cert WEB_TLS_CERT_PATH
gdcloud clusters get-credentials KUBERNETES_CLUSTER
kubectl config set-context --current --namespace=NAMESPACE
export SECRET_NAME="OSS_LLM_PULL_SECRET"
export NAMESPACE="PROJECT_NAMESPACE"
# Assuming default Docker config path. Adjust if necessary.
export DOCKER_CONFIG_PATH="$HOME/.docker/config.json"
kubectl create secret docker-registry ${SECRET_NAME} \
--from-file=.dockerconfigjson=${DOCKER_CONFIG_PATH} \
-n ${NAMESPACE}
Section 2 : Déployer avec vLLM
2.1 Obtenir l'image Docker vLLM
Sur une machine ayant accès à Internet, extrayez l'image Docker vLLM, puis transférez-la vers votre projet Harbor :
# Pull and Tag vLLM (v0.13.0 recommended for stability)
docker pull vllm/vllm-openai:v0.13.0
docker tag vllm/vllm-openai:v0.13.0 HARBOR_URL/PROJECT/vllm-openai:v0.13.0
docker push HARBOR_URL/PROJECT/vllm-openai:v0.13.0
Remplacez HARBOR_URL et PROJECT par l'URL de votre instance Harbor et le nom du projet.
2.2 Préparer les pondérations du modèle dans un PVC
Créez un fichier YAML (par exemple, model-pvc.yaml) :
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
storageClassName: standard-rwo
volumeMode: Filesystem
Appliquez la PVC : kubectl apply -f model-pvc.yaml
Téléchargez les pondérations depuis Hugging Face :
hf auth login
hf download google/gemma-3-4b-it
Utilisez un pod d'assistance (par exemple, helper-pod.yaml) pour transférer les poids vers la PVC.
Assurez-vous d'avoir une image busybox dans Harbor.
# Push busybox if not present
docker pull busybox:latest
docker tag busybox HARBOR_URL/PROJECT/busybox:latest
docker push HARBOR_URL/PROJECT/busybox:latest
# Contents of helper-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: model-uploader
spec:
containers:
- name: uploader
image: HARBOR_URL/PROJECT/busybox:latest
command: ["sleep", "3600"]
volumeMounts:
- name: model-data
mountPath: /data
imagePullSecrets:
- name: oss-llm-pull-secret
volumes:
- name: model-data
persistentVolumeClaim:
claimName: model-pvc
Appliquer le pod et copier les fichiers :
kubectl apply -f helper-pod.yaml
# Wait for pod to be Running
kubectl cp ~/.cache/huggingface/hub/ NAMESPACE/model-uploader:/data/
kubectl delete pod model-uploader
2.3 Déployer le backend vLLM
Créez le fichier de déploiement vllm-gemma-3-4b-it-deployment.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: gemma-3-4b-it
labels:
app: gemma-3-4b-it
spec:
replicas: 1
selector:
matchLabels:
app: gemma-3-4b-it
template:
metadata:
labels:
app: gemma-3-4b-it
spec:
volumes:
- name: cache-volume
persistentVolumeClaim:
claimName: model-pvc
- name: shm
emptyDir:
medium: Memory
sizeLimit: "16Gi"
containers:
- name: gemma-3-4b-it
image: HARBOR_URL/PROJECT/vllm-openai:v0.13.0
command: ["python3"]
args: [
"-m",
"vllm.entrypoints.openai.api_server",
"--model",
"google/gemma-3-4b-it",
"--max-model-len",
"32768",
"--enforce-eager"
]
env:
- name: HF_HUB_OFFLINE
value: "1"
- name: HF_HOME
value: "/model"
- name: NCCL_P2P_DISABLE
value: "1"
- name: NCCL_IB_DISABLE
value: "1"
- name: BORINGSSL_FIPS
value: "0"
- name: OPENSSL_FIPS
value: "0"
- name: OPENSSL_CONF
value: "/dev/null"
- name: FIPS_SIG
value: "off"
ports:
- containerPort: 8000
securityContext:
privileged: true
runAsUser: 0
resources:
limits:
nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
cpu: "8"
memory: "64Gi"
requests:
nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
cpu: "8"
memory: "32Gi"
volumeMounts:
- name: cache-volume
mountPath: /model
- name: shm
mountPath: /dev/shm
imagePullSecrets:
- name: oss-llm-pull-secret
Créez le fichier de service vllm-gemma-3-4b-it-service.yaml :
apiVersion: v1
kind: Service
metadata:
name: gemma-3-4b-it
namespace: NAMESPACE
spec:
ports:
- name: http-gemma-3-4b-it
port: 80
protocol: TCP
targetPort: 8000
selector:
app: gemma-3-4b-it
sessionAffinity: None
type: LoadBalancer
Appliquez les configurations :
kubectl apply -f vllm-gemma-3-4b-it-deployment.yaml
kubectl apply -f vllm-gemma-3-4b-it-service.yaml
2.4 Configurer une règle de réseau
Appliquez une ressource ProjectNetworkPolicy pour autoriser le trafic entrant vers le port du service vLLM (8000). Créez vllm-netpol.yaml :
apiVersion: networking.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
name: allow-vllm-ingress
namespace: NAMESPACE
spec:
subject:
subjectType: UserWorkload
policyType: Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0 # Restrict this in production
ports:
- protocol: TCP
port: 8000
Appliquez la règle : kubectl apply -f vllm-netpol.yaml
Section 3 : Déployer avec Ollama
3.1 Préparer le fichier Dockerfile
Créez un Dockerfile pour générer l'image Ollama avec vos modèles préchargés :
FROM ubuntu
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates zstd
RUN curl -fsSL https://ollama.com/install.sh -o install.sh
RUN chmod +x install.sh
RUN ./install.sh && \
rm -rf /var/lib/apt/lists/*
# Pre-pull gemma3 model
RUN ollama serve & \
sleep 5 && \
curl --retry 10 --retry-connrefused -s http://localhost:11434 || true && \
ollama pull gemma3:latest && \
pkill ollama || true
EXPOSE 11434
CMD ["ollama", "serve"]
3.2 Créer et transférer l'image
Créez et transférez l'image:
docker build -t ollama-gemma3 .
docker tag ollama-gemma3 HARBOR_URL/PROJECT/ollama-gemma3:latest
docker push HARBOR_URL/PROJECT/ollama-gemma3:latest
3.3 Déployer le backend Ollama
Créez ollama-gemma3.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama-gemma3
namespace: NAMESPACE
labels:
app: ollama-gemma3
spec:
replicas: 1
selector:
matchLabels:
app: ollama-gemma3
template:
metadata:
labels:
app: ollama-gemma3
spec:
containers:
- name: ollama-gemma3
image: HARBOR_URL/PROJECT/ollama-gemma3:latest
env:
- name: OLLAMA_HOST
value: "0.0.0.0"
imagePullPolicy: Always
ports:
- containerPort: 11434
securityContext:
privileged: true
runAsUser: 0
resources:
limits:
nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
requests:
nvidia.com/gpu-pod-NVIDIA_A100_80GB_PCIE: 1
imagePullSecrets:
- name: oss-llm-pull-secret
---
apiVersion: v1
kind: Service
metadata:
name: ollama-gemma3
namespace: NAMESPACE
spec:
type: LoadBalancer
selector:
app: ollama-gemma3
ports:
- name: ollama-gemma3-port
port: 11434
protocol: TCP
targetPort: 11434
Appliquez le fichier manifeste : kubectl apply -f ollama-gemma3.yaml
3.4 Configurer une règle de réseau
Créez ollama-netpol.yaml :
apiVersion: networking.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
name: allow-ollama-ingress
namespace: NAMESPACE
spec:
subject:
subjectType: UserWorkload
policyType: Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0 # Restrict this for production.
ports:
- protocol: TCP
port: 11434
Appliquez la règle : kubectl apply -f ollama-netpol.yaml
Section 4 : Validation
Vérifiez les déploiements en vérifiant l'état des pods et les adresses IP des services, et en envoyant des requêtes d'inférence de test à l'aide de curl aux adresses IP LoadBalancer pour vLLM et Ollama.
Vérifiez que tous les conteneurs et services sont Running :
# Login into GDC air-gapped using the next commands
gdcloud auth login --login-config-cert WEB_TLS_CERT_PATH
gdcloud clusters get-credentials KUBERNETES_CLUSTER
kubectl config set-context --current --namespace=NAMESPACE
# Pods
kubectl get pods
# Services
kubectl get services
Tester vLLM :
export VLLM_IP=$(kubectl get service gemma-3-4b-it -n NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[*].ip}')
curl http://${VLLM_IP}/v1/chat/completion \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-3-4b-it",
"messages": [
{"role": "user", "content": "What is Google Distributed Cloud air-gapped?"}
],
"max_tokens": 100
}'
Tester Ollama :
export OLLAMA_IP=$(kubectl get service ollama-gemma3 -n NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[*].ip}')
# Check if Ollama is running
curl http://${OLLAMA_IP}:11434
# Send a completion request
curl -X POST http://${OLLAMA_IP}:11434/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gemma3:latest",
"prompt": "Google Distributed Cloud air-gapped is a",
"max_tokens": 128,
"temperature": 0.90,
"stream": false
}'
Section 5 : Opérations et dépannage
5.1 Opérations vLLM
Vérifiez les journaux : kubectl logs -f -n NAMESPACE
Interrogez l'état interne (à partir d'un pod de débogage) :
wget -qO- http://gemma-3-4b-it/v1/models
wget -qO- http://gemma-3-4b-it/health
5.2 Opérations Ollama
Accéder à la CLI : kubectl exec -it -n NAMESPACE -- sh
Dans le pod : ollama list, ollama ps
5.3 Mise à l'échelle
Faire évoluer la solution avec un backend Ollama
Verticalement
- Allouez une tranche de GPU plus grande à votre LLM devenu un goulot d'étranglement jusqu'à ce que vous utilisiez un GPU complet.
- Si vous souhaitez utiliser un LLM plus grand pour améliorer la précision des réponses (par exemple, un LLM à 405 milliards de paramètres au lieu d'un LLM à 7 milliards), vous aurez peut-être besoin de plusieurs GPU pour l'exécuter correctement.
- Les modèles qui tiennent dans plusieurs GPU subissent une certaine latence liée à la communication entre les GPU.
Horizontalement
- Déployez autant de pods Ollama que nécessaire pour atteindre le débit cible sur un LLM donné.
- Pour ce faire, augmentez le nombre de réplicas dans le fichier YAML de déploiement Ollama correspondant.
- Le service Kubernetes, de type LoadBalancer, distribuera les demandes d'assistance au code entre les points de terminaison (c'est-à-dire les pods) et renverra leurs réponses respectives via l'adresse IP externe exposée.
- N'oubliez pas que le plug-in Continue ne pointe que vers une seule adresse IP par fonctionnalité.

Pour mettre à l'échelle le backend vLLM dans votre environnement GDC isolé, vous pouvez suivre une stratégie similaire à celle utilisée pour Ollama, en vous concentrant à la fois sur l'allocation des ressources matérielles et sur la réplication des pods.
Mettre à l'échelle la solution avec un backend vLLM
Verticalement
- Mettre à niveau l'allocation de GPU : si le débit d'inférence (jetons/s) devient un goulot d'étranglement, allouez une tranche de GPU plus importante jusqu'à ce que vous utilisiez un GPU NVIDIA A100 complet.
- Configurations multi-GPU : pour les modèles massifs (par exemple, de 70 milliards à 405 milliards de paramètres) qui ne tiennent pas dans la mémoire d'un seul GPU A100 de 80 Go, vous devez passer à plusieurs GPU à l'aide du parallélisme de tenseur.
- Latence : notez que les modèles s'étendant sur plusieurs GPU peuvent entraîner une légère surcharge liée à la communication entre les GPU (par exemple, la synchronisation NCCL).
Horizontalement
- Augmenter le débit grâce aux répliques : pour gérer un volume plus élevé de requêtes utilisateur simultanées pour le même modèle, augmentez le nombre de répliques dans le fichier YAML de votre déploiement vLLM.
- Instances de modèle dédiées : comme vLLM est conçu comme un moteur de diffusion de modèle unique et épingle sa mémoire cache KV lors de l'initialisation, vous devez déployer un ensemble distinct de pods pour chaque LLM différent que vous souhaitez héberger.
- Équilibrage de charge : le service GDC Kubernetes (type LoadBalancer) distribue automatiquement les requêtes d'inférence entrantes entre tous les points de terminaison de pod vLLM sains associés à ce service.
5.4 Dépannage
Erreurs courantes et mesures d'atténuation
| Erreur | Atténuation |
|---|---|
| ÉCHEC DE L'AUTOTEST FIPS | Se produit lorsque des bibliothèques telles que BoringSSL ne disposent pas de signatures d'intégrité. Corriger en définissant BORINGSSL_FIPS=0 et en utilisant les images vLLM officielles |
| Chargement de poids bloqué | Vérifiez si les IOPS sont limitées sur les petits PVC. Un volume de 500 Gio est requis pour l'initialisation du modèle de performances. |
| Connexion refusée | Vérifiez que le PNP autorise explicitement le targetPort (8000/8080). Les pare-feu GDC n'accordent pas automatiquement l'accès aux ports de backend pour les adresses IP virtuelles de l'équilibreur de charge. |