Panoramica
Questo documento fornisce istruzioni passo passo per il deployment di modelli linguistici di grandi dimensioni (LLM) open weight come Gemma, Llama e DeepSeek negli ambienti con air gap di Google Distributed Cloud (GDC). Illustra l'utilizzo di vLLM per l'erogazione ad alto rendimento e di Ollama per la facilità d'uso, sfruttando le funzionalità della piattaforma GDC, tra cui Kubernetes, Harbor e le risorse GPU.
Architettura
La soluzione prevede il deployment di backend di gestione LLM containerizzati (vLLM, Ollama) come deployment all'interno di un cluster utente. I pesi del modello vengono archiviati nei volumi permanenti, compilati dalle immagini nel registro Harbor. I servizi Kubernetes di tipo LoadBalancer espongono le API dei backend. Le policy di rete del progetto proteggono l'accesso a questi servizi.

Prima di iniziare
Assicurati che siano soddisfatti i seguenti prerequisiti:
- GDC con air gap versione 1.15.1 o successive.
- Cluster utente creato con risorse sufficienti (CPU, memoria, GPU).
- È richiesta almeno una GPU NVIDIA A100.
- L'istanza Harbor è disponibile e accessibile.
- Interfacce a riga di comando
kubectlegdcloudconfigurate per accedere al cluster utente. - Il client Docker è installato e configurato per il push su Harbor.
- Autorizzazioni IAM necessarie concesse (ad esempio, Amministratore spazio dei nomi, Sviluppatore cluster**).
- Account Hugging Face e autenticazione configurati se utilizzi modelli con accesso limitato.
Sezione 1: configurazione comune
1.1 Crea il secret di pull dell'immagine
Per configurare un secret di pull di immagini per un workload container in GDC con air gap, devi creare un secret docker-registry di Kubernetes contenente le credenziali per accedere al tuo progetto Harbor privato. Questo secret viene quindi referenziato nella specifica del deployment.
Devi utilizzare un account robot Harbor per l'accesso programmatico alle immagini nei progetti Harbor privati.
Segui questi passaggi per configurare il secret di pull dell'immagine:
Crea un account robot Harbor:
- Vai all'interfaccia utente dell'istanza Harbor.
- Vai al tuo progetto Harbor.
- Seleziona la scheda Account robot.
- Fai clic su Nuovo account robot.
- Assegna un nome (ad esempio,
oss-llm-puller) e concedi le autorizzazioni necessarie (almeno l'accesso pull) fino a una data di scadenza. - Memorizza in modo sicuro il nome dell'account robot (ad esempio
robot$oss-llm-puller) e il token segreto fornito.
Autentica Docker in Harbor:
Sulla macchina su cui è installato Docker e con accesso alla rete al registro Harbor, accedi utilizzando le credenziali dell'account 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}
Crea il secret di pull dell'immagine Kubernetes:
Utilizza kubectl per creare un secret di tipo docker-registry nello spazio dei nomi del tuo progetto, utilizzando il file di configurazione Docker aggiornato nel passaggio precedente:
# 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}
Sezione 2: deployment con vLLM
2.1 Scarica l'immagine Docker vLLM
Su una macchina con accesso a internet, esegui il pull dell'immagine Docker vLLM e poi trasferiscila al tuo progetto 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
Sostituisci HARBOR_URL e PROJECT
con l'URL dell'istanza Harbor e il nome del progetto.
2.2 Prepara i pesi del modello nel PVC
Crea un file YAML (ad esempio, model-pvc.yaml):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
storageClassName: standard-rwo
volumeMode: Filesystem
Applica il PVC: kubectl apply -f model-pvc.yaml
Scarica i pesi da Hugging Face:
hf auth login
hf download google/gemma-3-4b-it
Utilizza un pod helper (ad esempio helper-pod.yaml) per trasferire i pesi al PVC.
Assicurati di avere un'immagine busybox in 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
Applica il pod e copia i file:
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 Esegui il deployment del backend vLLM
Crea il file di deployment 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
Crea il file di servizio 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
Applica le configurazioni:
kubectl apply -f vllm-gemma-3-4b-it-deployment.yaml
kubectl apply -f vllm-gemma-3-4b-it-service.yaml
2.4 Configura la policy di rete
Applica una risorsa ProjectNetworkPolicy per consentire il traffico in entrata alla porta del servizio vLLM (8000). Crea 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
Applica la policy: kubectl apply -f vllm-netpol.yaml
Sezione 3: deployment con Ollama
3.1 Prepara Dockerfile
Crea un Dockerfile per creare l'immagine Ollama con i tuoi modelli precaricati:
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 Crea ed esegui il push dell'immagine
Crea ed esegui il push dell'immagine:
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 Esegui il deployment del backend Ollama
Crea 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
Applica il manifest: kubectl apply -f ollama-gemma3.yaml
3.4 Configura la policy di rete
Crea 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
Applica la policy: kubectl apply -f ollama-netpol.yaml
Sezione 4: convalida
Verifica i deployment controllando gli stati dei pod, gli indirizzi IP del servizio e
inviando richieste di inferenza di test utilizzando curl agli indirizzi IP LoadBalancer
sia per vLLM che per Ollama.
Verifica che tutti i container e i servizi siano 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
Testa 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
}'
Testa 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
}'
Sezione 5: Operazioni e risoluzione dei problemi
5.1 Operazioni vLLM
Controlla i log: kubectl logs -f -n NAMESPACE
Esegui query sullo stato interno (da un pod di debug):
wget -qO- http://gemma-3-4b-it/v1/models
wget -qO- http://gemma-3-4b-it/health
5.2 Operazioni Ollama
Accedi alla CLI: kubectl exec -it -n NAMESPACE -- sh
All'interno del pod: ollama list, ollama ps
5.3 Scalabilità
Scalare la soluzione con un backend Ollama
Verticalmente
- Alloca una fetta di GPU più grande per il tuo LLM che è diventato un collo di bottiglia finché non utilizzi una GPU completa.
- Se vuoi utilizzare un LLM più grande per una migliore precisione della risposta, ad esempio un LLM con 405 miliardi di parametri anziché uno con 7 miliardi, potresti aver bisogno di più di una GPU per eseguirlo senza problemi.
- I modelli che rientrano in più di una GPU subiscono una latenza correlata alla comunicazione tra GPU.
Orizzontalmente
- Esegui il deployment di tutti i pod Ollama necessari per raggiungere il throughput target su un LLM specifico.
- Per farlo, aumenta il numero di repliche nel file YAML di deployment di Ollama corrispondente.
- Il servizio Kubernetes, di tipo LoadBalancer, distribuirà le richieste di assistenza per il codice tra gli endpoint (ovvero i pod) e restituirà le rispettive risposte tramite l'IP esterno esposto.
- Ricorda che il plug-in Continua punta a un solo indirizzo IP per funzionalità.

Per scalare il backend vLLM all'interno dell'ambiente GDC air-gap, puoi seguire una strategia simile a quella utilizzata per Ollama, concentrandoti sia sull'allocazione delle risorse hardware sia sulla replica dei pod.
Scalare la soluzione con un backend vLLM
Verticalmente
- Esegui l'upgrade dell'allocazione della GPU:se la velocità effettiva di inferenza (token/sec) diventa un collo di bottiglia, alloca una porzione di GPU più grande finché non utilizzi una GPU NVIDIA A100 completa.
- Configurazioni multi-GPU: per modelli di grandi dimensioni (ad esempio, da 70 miliardi a 405 miliardi di parametri) che non rientrano nella memoria di una singola A100 da 80 GB, devi scalare a più GPU utilizzando il parallelismo dei tensori.
- Considerazione della latenza: tieni presente che i modelli che si estendono su più GPU potrebbero riscontrare un leggero sovraccarico correlato alla comunicazione tra GPU (ad esempio, la sincronizzazione NCCL).
Orizzontalmente
- Aumenta la velocità effettiva tramite le repliche: per gestire un volume maggiore di richieste simultanee degli utenti per lo stesso modello, aumenta il conteggio delle repliche nel file YAML del deployment vLLM.
- Istanze di modelli dedicate: poiché vLLM è progettato come motore di servizio di un singolo modello e blocca la memoria della cache KV all'inizializzazione, devi eseguire il deployment di un insieme separato di pod per ogni LLM diverso che vuoi ospitare.
- Bilanciamento del carico: il servizio GDC Kubernetes (tipo LoadBalancer) distribuirà automaticamente le richieste di inferenza in entrata tra tutti gli endpoint pod vLLM integri associati a quel servizio.
5.4 Risoluzione dei problemi
Errori comuni e mitigazioni.
| Errore | Mitigazione |
|---|---|
| FIPS SELFTEST FAILURE | Si verifica quando le librerie come BoringSSL non hanno firme di integrità. Correzione impostando BORINGSSL_FIPS=0 e utilizzando immagini vLLM ufficiali |
| Caricamento del peso bloccato | Verifica la presenza di limitazione degli IOPS sui PVC di piccole dimensioni. Per l'inizializzazione del modello di prestazioni è necessario un volume di 500 GiB. |
| Connessione rifiutata | Verifica che PNP consenta esplicitamente targetPort (8000/8080). I firewall GDC non concedono automaticamente l'accesso alle porte di backend per gli indirizzi IP virtuali del bilanciatore del carico. |