Visão geral
Este documento fornece instruções detalhadas para implantar modelos de linguagem grandes (LLMs) de peso aberto, como Gemma, Llama e DeepSeek, em ambientes isolados do Google Distributed Cloud (GDC). Ele aborda o uso do vLLM para veiculação de alta capacidade e do Ollama para facilitar o uso, aproveitando os recursos da plataforma GDC, incluindo Kubernetes, Harbor e recursos de GPU.
Arquitetura
A solução envolve a implantação de back-ends de disponibilização de LLM em contêineres (vLLM, Ollama) como implantações em um cluster de usuário. Os pesos do modelo são armazenados em volumes permanentes, preenchidos com imagens do registro do Harbor. Os serviços do Kubernetes do tipo LoadBalancer expõem as APIs dos back-ends. As políticas de rede do projeto protegem o acesso a esses serviços.

Antes de começar
Verifique se os pré-requisitos a seguir foram atendidos:
- GDC com isolamento físico versão 1.15.1 ou mais recente.
- Cluster de usuário criado com recursos suficientes (CPU, memória, GPU).
- É necessário ter no mínimo uma GPU NVIDIA A100.
- Uma instância do Harbor disponível e acessível.
- CLIs
kubectlegdcloudconfiguradas para acessar o cluster de usuário. - O cliente Docker está instalado e configurado para enviar ao Harbor.
- Permissões necessárias do IAM concedidas (por exemplo, Administrador do namespace, desenvolvedor de cluster**).
- Conta e autenticação do Hugging Face configuradas se você estiver usando modelos restritos.
Seção 1: configuração comum
1.1 Criar um secret de pull de imagem
Para configurar um secret de extração de imagem para uma carga de trabalho de contêiner no GDC com isolamento físico, crie um secret do docker-registry do Kubernetes com credenciais para acessar seu projeto privado do Harbor. Esse secret é referenciado na especificação de implantação.
Use uma conta de robô do Harbor para acesso programático a imagens em projetos privados do Harbor.
Siga estas etapas para configurar o segredo de extração de imagens:
Criar uma conta de robô do Harbor:
- Acesse a interface da instância do Harbor.
- Acesse seu projeto do Harbor.
- Selecione a guia Contas de robô.
- Clique em Nova conta de robô.
- Dê um nome a ele (por exemplo,
oss-llm-puller) e conceda as permissões necessárias (pelo menos acesso de pull) até um horário de expiração. - Armazene com segurança o nome da conta do robô (por exemplo,
robot$oss-llm-puller) e o token secreto fornecido.
Autentique o Docker no Harbor:
Na máquina com o Docker instalado e acesso à rede do registro do Harbor, faça login usando as credenciais da conta de robô:
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}
Crie o secret de extração de imagens do Kubernetes:
Use kubectl para criar um secret do tipo docker-registry no namespace do projeto usando o arquivo de configuração do Docker atualizado na etapa 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}
Seção 2: implantação com vLLM
2.1 Obter a imagem Docker vLLM
Em uma máquina com acesso à internet, extraia a imagem Docker vLLM e transfira para seu projeto do 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
Substitua HARBOR_URL e PROJECT
pelo URL da instância do Harbor e pelo nome do projeto.
2.2 Preparar os pesos do modelo no PVC
Crie um arquivo YAML (por exemplo, model-pvc.yaml):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
storageClassName: standard-rwo
volumeMode: Filesystem
Aplique o PVC: kubectl apply -f model-pvc.yaml
Faça o download dos pesos do Hugging Face:
hf auth login
hf download google/gemma-3-4b-it
Use um pod auxiliar (por exemplo, helper-pod.yaml) para transferir pesos para a PVC.
Verifique se você tem uma imagem busybox no 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
Aplique o pod e copie os arquivos:
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 Implantar o back-end do vLLM
Crie o arquivo de implantação 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
Crie o arquivo de serviço 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
Aplique as configurações:
kubectl apply -f vllm-gemma-3-4b-it-deployment.yaml
kubectl apply -f vllm-gemma-3-4b-it-service.yaml
2.4 Configurar a política de rede
Aplique um recurso ProjectNetworkPolicy para permitir o tráfego de entrada à porta do serviço vLLM (8000). Crie 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
Aplique a política: kubectl apply -f vllm-netpol.yaml
Seção 3: implantação com o Ollama
3.1 Prepare o Dockerfile
Crie um Dockerfile para criar a imagem do Ollama com os modelos pré-carregados:
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 Criar e enviar a imagem
Crie e envie a imagem:
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 Implantar o back-end do Ollama
Crie 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
Aplique o manifesto: kubectl apply -f ollama-gemma3.yaml
3.4 Configurar a política de rede
Crie 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
Aplique a política: kubectl apply -f ollama-netpol.yaml
Seção 4: validação
Verifique os implantes conferindo os status dos pods, os endereços IP dos serviços e
enviando solicitações de inferência de teste usando curl para os endereços IP LoadBalancer
do vLLM e do Ollama.
Verifique se todos os contêineres e serviços estão 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
Teste de 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
}'
Teste do 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
}'
Seção 5: operações e solução de problemas
5.1 Operações do vLLM
Verificar registros: kubectl logs -f -n NAMESPACE
Consultar status interno (de um pod de depuração):
wget -qO- http://gemma-3-4b-it/v1/models
wget -qO- http://gemma-3-4b-it/health
5.2 Operações do Ollama
Acessar a CLI: kubectl exec -it -n NAMESPACE -- sh
Dentro do pod: ollama list, ollama ps
5.3 Escalonamento
Escalonar a solução com um back-end do Ollama
Verticalmente
- Alocar uma fração maior de GPU para o LLM que se tornou um gargalo até usar uma GPU completa.
- Se você quiser usar um LLM maior para melhorar a precisão da resposta, por exemplo, um LLM de 405 bilhões de parâmetros em vez de um de 7 bilhões, talvez seja necessário mais de uma GPU para executá-lo sem problemas.
- Os modelos que se encaixam em mais de uma GPU têm alguma latência relacionada à comunicação entre GPUs.
Horizontalmente
- Implante quantos pods do Ollama forem necessários para alcançar a capacidade de processamento desejada em um determinado LLM.
- Para isso, aumente o número de réplicas no arquivo YAML de implantação do Ollama correspondente.
- O serviço do Kubernetes, que é do tipo LoadBalancer, distribui solicitações de assistência de código entre endpoints (ou seja, pods) e retorna as respostas respectivas pelo IP externo exposto.
- O plug-in Continuar aponta para apenas um endereço IP por funcionalidade.

Para escalonar o back-end vLLM no ambiente isolado do GDC, siga uma estratégia semelhante à usada para o Ollama, focando na alocação de recursos de hardware e na replicação de pods.
Escalonar a solução com um back-end vLLM
Verticalmente
- Fazer upgrade da alocação de GPU:se a capacidade de processamento de inferência (tokens/segundo) se tornar um gargalo, aloque uma fração maior da GPU até usar uma GPU NVIDIA A100 completa.
- Configurações de várias GPUs: para modelos enormes (por exemplo, de 70 bilhões a 405 bilhões de parâmetros) que não cabem na memória de uma única A100 de 80 GB, é necessário escalonar para várias GPUs usando o paralelismo de tensor.
- Consideração sobre a latência: modelos que abrangem mais de uma GPU podem ter uma pequena sobrecarga relacionada à comunicação entre GPUs (por exemplo, sincronização do NCCL).
Horizontalmente
- Aumentar a capacidade de processamento usando réplicas: para lidar com um volume maior de solicitações simultâneas de usuários para o mesmo modelo, aumente a contagem de réplicas no YAML de implantação do vLLM.
- Instâncias de modelo dedicadas: como o vLLM foi projetado como um mecanismo de serviço de modelo único e fixa a memória do cache KV na inicialização, é necessário implantar um conjunto separado de pods para cada LLM diferente que você quer hospedar.
- Balanceamento de carga: o serviço do Kubernetes do GDC (tipo LoadBalancer) distribui automaticamente as solicitações de inferência recebidas entre todos os endpoints de pod vLLM íntegros associados a esse serviço.
5.4 Solução de problemas
Erros comuns e mitigações.
| Erro | Mitigação |
|---|---|
| FALHA NO AUTOTESTE DO FIPS | Ocorre quando bibliotecas como o BoringSSL não têm assinaturas de integridade. Corrigir definindo BORINGSSL_FIPS=0 e usando imagens oficiais do vLLM |
| Carregamento de peso parado | Verifique se há limitação de IOPS em PVCs pequenos. Um volume de 500 GiB é necessário para a inicialização do modelo de desempenho. |
| Conexão recusada | Confirme se o PNP permite explicitamente o targetPort (8000/8080). Os firewalls do GDC não concedem acesso automático às portas de back-end para VIPs do balanceador de carga. |