Descripción general
En este documento, se proporcionan instrucciones paso a paso para implementar modelos de lenguaje grandes (LLM) de código abierto, como Gemma, Llama y DeepSeek, en entornos aislados de Google Distributed Cloud (GDC). En él, se explica cómo usar vLLM para la entrega de alto rendimiento y Ollama para facilitar el uso, aprovechando las capacidades de la plataforma de GDC, incluidos los recursos de Kubernetes, Harbor y GPU.
Arquitectura
La solución implica implementar backends de servicio de LLM en contenedores (vLLM, Ollama) como implementaciones dentro de un clúster de usuario. Los pesos del modelo se almacenan en volúmenes persistentes, que se completan a partir de imágenes en el registro de Harbor. Los servicios de Kubernetes de tipo LoadBalancer exponen las APIs de los backends. Las políticas de red del proyecto protegen el acceso a estos servicios.

Antes de comenzar
Asegúrate de que se cumplan los siguientes requisitos:
- Versión 1.15.1 o posterior de GDC aislado
- Se creó un clúster de usuario con recursos suficientes (CPU, memoria y GPU).
- Se requiere un mínimo de 1 GPU NVIDIA A100.
- La instancia de Harbor está disponible y se puede acceder a ella.
- CLIs de
kubectlygdcloudconfiguradas para acceder al clúster de usuario - El cliente de Docker está instalado y configurado para enviar a Harbor.
- Se otorgaron los permisos de IAM necesarios (por ejemplo, Administrador de espacio de nombres, desarrollador de clústeres**).
- Cuenta de Hugging Face y autenticación configuradas si se usan modelos con acceso restringido
Sección 1: Configuración común
1.1 Crea un Secret de extracción de imagen
Para configurar un secreto de extracción de imágenes para una carga de trabajo de contenedor en GDC aislado, debes crear un secreto de registro de Docker de Kubernetes que contenga credenciales para acceder a tu proyecto privado de Harbor. Luego, se hace referencia a este secreto en la especificación de implementación.
Debes usar una cuenta de robot de Harbor para acceder de forma programática a las imágenes en proyectos privados de Harbor.
Sigue estos pasos para configurar el secreto de extracción de imágenes:
Crea una cuenta de robot de Harbor:
- Navega a la IU de tu instancia de Harbor.
- Ve a tu proyecto de Harbor.
- Selecciona la pestaña Cuentas de robot.
- Haz clic en New Robot Account.
- Asigna un nombre (por ejemplo,
oss-llm-puller) y otórgale los permisos necesarios (al menos acceso de extracción) hasta una hora de vencimiento. - Almacena de forma segura el nombre de la cuenta del robot (por ejemplo,
robot$oss-llm-puller) y el token secreto proporcionado.
Autentica Docker en Harbor:
En tu máquina con Docker instalado y acceso de red al registro de Harbor, accede con las credenciales de la cuenta de 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 el secreto de extracción de imágenes de Kubernetes:
Usa kubectl para crear un secreto de tipo docker-registry en el espacio de nombres de tu proyecto con el archivo de configuración de Docker que actualizaste en el paso anterior:
# 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}
Sección 2: Implementación con vLLM
2.1 Obtén la imagen de Docker de vLLM
En una máquina con acceso a Internet, extrae la imagen de Docker de vLLM y, luego, transfiérela a tu proyecto de 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
Reemplaza HARBOR_URL y PROJECT por la URL de tu instancia de Harbor y el nombre del proyecto.
2.2 Prepara las ponderaciones del modelo en el PVC
Crea un archivo YAML (por ejemplo, model-pvc.yaml):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
storageClassName: standard-rwo
volumeMode: Filesystem
Aplica el PVC: kubectl apply -f model-pvc.yaml
Descarga los pesos de Hugging Face:
hf auth login
hf download google/gemma-3-4b-it
Usa un pod auxiliar (por ejemplo, helper-pod.yaml) para transferir pesos al PVC.
Asegúrate de tener una imagen de busybox en 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
Aplica el pod y copia los archivos:
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 Implementa el backend de vLLM
Crea el archivo de implementación 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 el archivo de servicio 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
Aplica la configuración:
kubectl apply -f vllm-gemma-3-4b-it-deployment.yaml
kubectl apply -f vllm-gemma-3-4b-it-service.yaml
2.4 Configura la política de red
Aplica un recurso ProjectNetworkPolicy para permitir el tráfico de entrada al puerto de servicio de 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
Aplica la política: kubectl apply -f vllm-netpol.yaml
Sección 3: Implementación con Ollama
3.1 Prepara el Dockerfile
Crea un Dockerfile para compilar la imagen de Ollama con los modelos precargados:
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 Compila y envía la imagen
Compila y envía la imagen:
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 Implementa el backend de 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
Aplica el manifiesto: kubectl apply -f ollama-gemma3.yaml
3.4 Configura la política de red
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
Aplica la política: kubectl apply -f ollama-netpol.yaml
Sección 4: Validación
Verifica las implementaciones comprobando los estados de los Pods, las direcciones IP de los servicios y enviando solicitudes de inferencia de prueba con curl a las direcciones IP de LoadBalancer para vLLM y Ollama.
Verifica que todos los contenedores y servicios sean 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
Prueba 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
}'
Prueba 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
}'
Sección 5: Operaciones y solución de problemas
5.1 Operaciones de vLLM
Verifica los registros: kubectl logs -f -n NAMESPACE
Estado interno de la búsqueda (desde un pod de depuración):
wget -qO- http://gemma-3-4b-it/v1/models
wget -qO- http://gemma-3-4b-it/health
5.2 Operaciones de Ollama
Accede a la CLI: kubectl exec -it -n NAMESPACE -- sh
Dentro del pod: ollama list, ollama ps
5.3 Escalabilidad
Cómo escalar la solución con un backend de Ollama
Verticalmente
- Asigna una porción de GPU más grande a tu LLM, que se convirtió en un cuello de botella, hasta que uses una GPU completa.
- Si quieres usar un LLM más grande para obtener una mejor precisión en las respuestas, por ejemplo, un LLM con 405 mil millones de parámetros en lugar de uno con 7 mil millones, es posible que necesites más de una GPU para ejecutarlo sin problemas.
- Los modelos que se ajustan a más de una GPU experimentan cierta latencia relacionada con la comunicación entre GPU.
Horizontalmente
- Implementa tantos Pods de Ollama como necesites para alcanzar el rendimiento objetivo en un LLM determinado.
- Para lograr esto, aumenta la cantidad de replicas en el archivo YAML de implementación de Ollama correspondiente.
- El servicio de Kubernetes, que es de tipo LoadBalancer, distribuirá las solicitudes de asistencia de código entre los extremos (es decir, los Pods) y devolverá sus respuestas respectivas a través de la IP externa expuesta.
- Recuerda que el complemento Continue apunta a una sola dirección IP por funcionalidad.

Para escalar el backend de vLLM en tu entorno aislado de GDC, puedes seguir una estrategia similar a la que se usa para Ollama, enfocándote en la asignación de recursos de hardware y la replicación de pods.
Cómo escalar la solución con un backend de vLLM
Verticalmente
- Actualiza la asignación de GPU: Si la capacidad de procesamiento de la inferencia (tokens/s) se convierte en un cuello de botella, asigna una porción de GPU más grande hasta que uses una GPU NVIDIA A100 completa.
- Configuraciones de varias GPUs: Para modelos masivos (por ejemplo, de 70 mil millones a 405 mil millones de parámetros) que no caben en la memoria de una sola GPU A100 de 80 GB, debes escalar a varias GPUs con el paralelismo de tensor.
- Consideración sobre la latencia: Ten en cuenta que los modelos que abarcan más de una GPU pueden experimentar una ligera sobrecarga relacionada con la comunicación entre GPU (por ejemplo, la sincronización de NCCL).
Horizontalmente
- Aumenta la capacidad de procesamiento a través de réplicas: Para controlar un mayor volumen de solicitudes de usuarios simultáneas para el mismo modelo, aumenta el recuento de réplicas en el archivo YAML de implementación de vLLM.
- Instancias de modelos dedicadas: Dado que vLLM está diseñado como un motor de servicio de un solo modelo y fija su memoria caché de KV tras la inicialización, debes implementar un conjunto separado de pods para cada LLM diferente que desees alojar.
- Balanceo de cargas: El servicio de Kubernetes de GDC (tipo LoadBalancer) distribuirá automáticamente las solicitudes de inferencia entrantes entre todos los extremos de Pod de vLLM en buen estado asociados con ese servicio.
5.4 Solución de problemas
Errores comunes y mitigaciones
| Error | Mitigación |
|---|---|
| FALLA EN LA AUTOPRUEBA DE FIPS | Ocurre cuando bibliotecas como BoringSSL no tienen firmas de integridad. Soluciona el problema configurando BORINGSSL_FIPS=0 y utilizando imágenes oficiales de vLLM |
| Carga de peso detenida | Verifica si hay regulación de IOPS en los PVC pequeños. Se requiere un volumen de 500 GiB para la inicialización del modelo de rendimiento. |
| Se rechazó la conexión | Confirma que el PNP permite de forma explícita el targetPort (8000/8080). Los firewalls de GDC no otorgan acceso automáticamente a los puertos de backend para las VIP del balanceador de cargas. |