Observação:
- Este tutorial requer acesso ao Oracle Cloud. Para se inscrever em uma conta gratuita, consulte Conceitos básicos do Oracle Cloud Infrastructure Free Tier.
- Ele usa valores de exemplo para credenciais, tenancy e compartimentos do Oracle Cloud Infrastructure. Ao concluir seu laboratório, substitua esses valores por valores específicos do seu ambiente de nuvem.
Implantar Pilha de Produção do vLLM OpenAI no Oracle Kubernetes Engine (OKE)
Introdução
As organizações que adotam grandes modelos de linguagem (LLMs) para cargas de trabalho de produção enfrentam uma decisão crítica de infraestrutura: confie em APIs de inferência de terceiros ou implante uma pilha de inferência auto-hospedada. As implementações auto-hospedadas oferecem vantagens significativas: total privacidade de dados e controle de conformidade, latência de inferência de menos de 100 milissegundos, eliminando viagens de ida e volta da rede, custo previsível em escala e a liberdade de ajustar e atender a qualquer modelo de código aberto sem restrição de fornecedor.
No entanto, a criação de uma pilha de inferência de LLM de nível de produção do zero é complexa. Ele requer orquestração de contêineres com reconhecimento de GPU, roteamento inteligente de solicitações em várias réplicas de modelo, armazenamento persistente para grandes pesos de modelo e monitoramento contínuo — tudo integrado e executado de forma confiável.
A Oracle Cloud Infrastructure oferece vários caminhos para inferência de IA. O Serviço OCI Generative AI fornece uma experiência totalmente gerenciada com clusters de IA dedicados isolados para sua tenancy, ideal para equipes que desejam começar rapidamente com modelos suportados. Este tutorial adota a abordagem alternativa: implantar sua própria pilha de inferência no OKE. Esse caminho é projetado para equipes que precisam de controle preciso sobre drivers de GPU, versões de CUDA, configurações de modelos e parâmetros de atendimento, ou equipes que estão treinando e ajustando modelos personalizados e desejam atendê-los diretamente. A OCI fornece instâncias de GPU bare metal com GPUs NVIDIA A10, A100 e H100, conectadas por rede de cluster RDMA de latência ultrabaixa, oferecendo o mesmo nível de controle de hardware que você teria on-premises e se beneficiando da elasticidade da nuvem.
A pilha de produção vLLM resolve a complexidade da inferência auto-hospedada, fornecendo uma plataforma nativa de Kubernetes de código aberto criada no vLLM, o mecanismo de inferência de alto rendimento usado na produção por organizações como Meta, Mistral AI e IBM. Ele oferece uma taxa de transferência até 24x maior em comparação com estruturas de serviço padrão por meio de gerenciamento eficiente de memória de GPU e otimização de cache KV. Combinado com formas de OKE e OCI GPU, você obtém uma plataforma de inferência pronta para produção com rede, armazenamento e segurança de nível empresarial. Os scripts de implantação do OCI usados neste tutorial são contribuídos e mantidos no repositório oficial de pilha de produção vLLM.
Este tutorial orienta você na implantação da Pilha de Produção vLLM no OKE, desde o provisionamento da infraestrutura até a execução da sua primeira solicitação de inferência.
Observação: Este tutorial provisiona recursos passo a passo usando a CLI do OCI para ajudar você a entender o fluxo completo de recursos de nuvem do OCI necessários para uma implantação de inferência de GPU. Para ambientes de produção, é recomendável codificar essa infraestrutura usando o Terraform ou o OCI Resource Manager (Shepherd) para implantações repetíveis e controladas por versão.

Os seguintes serviços do OCI são usados neste tutorial:
| Serviço | Objetivo |
|---|---|
| Oracle Kubernetes Engine (OKE) | Cluster Kubernetes gerenciado para orquestração de contêineres e programação de carga de trabalho de GPU |
| OCI Compute (Formas GPU) | Instâncias de GPU NVIDIA A10 (24 GB) e A100 (80 GB) para inferência de modelo |
| Volumes em Blocos do OCI | Armazenamento persistente para pesos de modelo com camadas de desempenho configuráveis |
| VCN (Rede Virtual na Nuvem) do OCI | Infraestrutura de rede, incluindo sub-redes, gateways e listas de segurança |
| Balanceador de Carga do OCI | Acesso externo a pontos finais de inferência |
| OCI Bastion | Túneis SSH gerenciados para acesso ao cluster privado |
| OCI Object Storage | Origem de modelo alternativa usando URLs de Solicitação Pré-Autenticada (PAR) |
Objetivos
Neste tutorial, você:
- Implantar um cluster do OKE com pools de nós ativados para GPU usando a CLI do OCI
- Configurar a rede do OCI (VCN, sub-redes, gateways) para cargas de trabalho do Kubernetes
- Instalar e configurar o plug-in de dispositivo NVIDIA para agendamento de GPU
- Expanda o sistema de arquivos do nó GPU para usar a capacidade total do volume de inicialização
- Implante a Pilha de Produção vLLM com um ponto final de inferência compatível com OpenAI
- Teste a inferência do LLM com solicitações de API no modelo OpenAI GPT-OSS-20B
- Configurar o paralelismo do tensor de vários GPUs para modelos maiores em formas bare metal
- Usar o OCI Object Storage como origem de modelo alternativa
- Limpe todos os recursos da OCI para evitar cobranças contínuas
Pré-requisitos
- Uma conta do Oracle Cloud Infrastructure com o seguinte:
- Um compartimento com permissões para criar e gerenciar clusters do OKE
- Cota de computação de GPU para a forma desejada (por exemplo,
VM.GPU.A10.1ouBM.GPU.A100-v2.8). Se você não tiver cota de GPU, solicite-a por meio de um ticket de suporte - Permissões para criar VCN, sub-redes, gateways de internet e balanceadores de carga
- Acesso ao OCI Block Volumes para armazenamento persistente
- Acesso ao OCI Object Storage (para a seção de carregamento de modelo avançado)
Observação: os exemplos de saídas e capturas de tela neste tutorial usam us-chicago-1. Você pode implantar em qualquer região suportada definindo
OCI_REGION. A capacidade da GPU varia de acordo com a região e o domínio de disponibilidade; portanto, confirme se a sua forma de GPU de destino está disponível antes da implantação. Verifique a disponibilidade da forma de GPU por região e prepare-se para experimentar outro domínio de disponibilidade (GPU_AD_INDEX) se você encontrar erros de capacidade.
Observação: Este tutorial provisiona recursos de GPU pagos (por exemplo,
VM.GPU.A10.1). Não é uma carga de trabalho Always Free da OCI. Sempre execute as etapas de limpeza quando terminar para evitar cobranças contínuas.
-
CLI do OCI instalado e configurado com o
oci setup config -
jq instalado para análise JSON
-
kubectl instalado
-
Helm instalado
-
Um par de chaves SSH (por exemplo,
~/.ssh/id_rsae~/.ssh/id_rsa.pub) para acesso ao bastion. Gere um comssh-keygen -t rsa -b 4096, se necessário -
Familiaridade com conceitos do Kubernetes (pods, serviços, implantações, pools de nós)
Observação: Este tutorial implanta o
openai/gpt-oss-20b, um modelo licenciado do Apache 2.0 do OpenAI. Nenhum token Hugging Face é necessário. Se você quiser implantar modelos fechados, como o Meta Llama 3.1, precisará de uma conta do Hugging Face com um token de API.
Tarefa 1: Configurar Variáveis de Ambiente
Defina a configuração do OCI necessária antes de implantar a infraestrutura.
-
Localize o OCID do compartimento na Console do OCI. Navegue até Identity & Security > Compartments e clique no compartimento de destino e copie o OCID.
oci iam compartment list --query 'data[].{name:name,id:id}' --output table
-
Exporte a variável de ambiente necessária.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Opcionalmente, substitua a configuração padrão definindo qualquer uma das variáveis de ambiente a seguir.
Variável Padrão Descrição OCI_REGIONus-ashburn-1Região da OCI para implantação OCI_PROFILEDEFAULTPerfil de configuração da CLI do OCI CLUSTER_NAMEproduction-stackNome do cluster do OKE GPU_SHAPEVM.GPU.A10.1Forma de computação GPU para o pool de nós GPU_NODE_COUNT1Número de nós de GPU no pool GPU_BOOT_VOLUME_GB200Tamanho do volume de inicialização em GB para nós de GPU CPU_BOOT_VOLUME_GB100Tamanho do volume de inicialização em GB para nós da CPU GPU_AD_INDEX1Índice de domínio de disponibilidade (com base em 0) para posicionamento de GPU PRIVATE_CLUSTERtrueDefinir como falsepara um ponto final público da API do KubernetesKUBERNETES_VERSIONv1.31.10Versão do Kubernetes para o cluster do OKE Por exemplo, para implantar com dois nós de GPU A100:
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" export GPU_SHAPE="BM.GPU.A100-v2.8" export GPU_NODE_COUNT="2" -
Revise as formas de GPU disponíveis e selecione uma com base nos requisitos de tamanho do modelo.
Forma GPUs Tipo de GPU Memória de GPU Recomendado Para VM.GPU.A10.11 NVIDIA A10 24 GB Modelos de parâmetros 7B–13B VM.GPU.A10.22 NVIDIA A10 48 GB Tensor paralelo com modelos pequenos BM.GPU4.88 NVIDIA A100 40 GB 320 GB Modelos 70B, econômicos BM.GPU.A100-v2.88 NVIDIA A100 80 GB 640 GB Mais de 70B modelos de parâmetros BM.GPU.H100.88 NVIDIA H100 640 GB Maiores modelos, suporte a RDMA Observação: As formas bare metal (
BM.*) fornecem hardware dedicado sem sobrecarga de virtualização e suportam paralelismo de tensor de vários GPUs. As formas de máquina virtual (VM.*) são mais econômicas para modelos menores.Observação: Este tutorial usa o
VM.GPU.A10.1(único NVIDIA A10 com memória de GPU de 24 GB) para implantar oopenai/gpt-oss-20b, um modelo de Mistura de Especialistas (MoE) com parâmetros ativos 3.6B que geralmente se encaixa em uma única GPU A10. As seções avançadas demonstram configurações de vários GPUs usandoBM.GPU.H100.8para modelos maiores, como Llama 3.1 70B.
Tarefa 2: Implantar Usando o Script Automatizado (Início Rápido)
A Pilha de Produção vLLM inclui um script de implantação automatizada que provisiona todos os recursos da OCI e implanta a pilha de inferência com um único comando. Use essa abordagem para uma implantação rápida. As tarefas de 3 a 10 abrangem cada etapa individualmente para os usuários que desejam personalizar o processo.
-
Clone o repositório da Pilha de Produção vLLM.
git clone https://github.com/vllm-project/production-stack.git cd production-stack/deployment_on_cloud/oci -
Exporte o OCID do compartimento.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Execute o script de implantação.
./entry_point.sh setup
Para clusters públicos (
PRIVATE_CLUSTER=false), a configuração cria toda a infraestrutura e implanta a pilha vLLM em um único comando. Informe o arquivo de valores do Helm como segundo argumento:PRIVATE_CLUSTER=false ./entry_point.sh setup ./production_stack_specification.yamlPara clusters privados (o padrão), a configuração cria a infraestrutura, mas não pode acessar a API do Kubernetes diretamente. Abra um terminal separado e inicie o túnel; em seguida, implante:
# In a separate terminal, start the SSH tunnel (auto-reconnects on drops): ./entry_point.sh tunnel # Back in the first terminal, deploy vLLM: ./entry_point.sh deploy-vllm ./production_stack_specification.yaml -
Verifique se a implantação está em execução.
kubectl get podsSaída esperada:
NAME READY STATUS RESTARTS AGE vllm-deployment-router-xxxxxxxxxx-xxxxx 1/1 Running 0 5m vllm-gpt-oss-deployment-vllm-xxxxxxxxxx-xxxxx 1/1 Running 0 5m
Observação: Se ambos os pods mostrarem o status
Running, sua implantação estará pronta. Vá para a Tarefa 10: Testar o Ponto Final de Inferência.
Observação: as instâncias de GPU estão sujeitas a restrições de capacidade do OCI. Se o script permanecer no loop "Aguardando o nó da GPU" por mais de 15 minutos, a forma da GPU poderá não estar disponível no domínio de disponibilidade selecionado. Verifique o status do pool de nós com
oci ce node-pool gete procure erros de "Capacidade insuficiente do host". Para resolver isso, faça a limpeza com./entry_point.sh cleanupe reimplante com outro domínio de disponibilidade (por exemplo,GPU_AD_INDEX=0ouGPU_AD_INDEX=2) ou outra forma de GPU (por exemplo,GPU_SHAPE=VM.GPU.A10.2).
Observação: O script de implantação usa instâncias de GPU que incorrem em custos significativos (aproximadamente US$ 50/dia para uma única GPU A10). Sempre execute
./entry_point.sh cleanupquando terminar de evitar cobranças contínuas.
Tarefa 3: Criar VCN e Rede
Criar a infraestrutura de rede do OCI necessária para o cluster do OKE. Isso inclui uma VCN (Rede Virtual na Nuvem), gateways, tabelas de roteamento, listas de segurança e sub-redes. Cada recurso de rede é criado em alguns segundos; o conjunto completo de comandos é concluído em menos de 2 minutos.
-
Crie uma VCN com um bloco CIDR
10.0.0.0/16.VCN_ID=$(oci network vcn create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --display-name "${CLUSTER_NAME}-vcn" \ --cidr-blocks '["10.0.0.0/16"]' \ --dns-label "prodstack" \ --query "data.id" \ --raw-output) -
Criar um Gateway de Internet para roteamento de sub-rede pública.
IGW_ID=$(oci network internet-gateway create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-igw" \ --is-enabled true \ --query "data.id" \ --raw-output) -
Criar um Gateway NAT para tráfego de saída de sub-redes privadas.
NAT_ID=$(oci network nat-gateway create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-nat" \ --query "data.id" \ --raw-output) -
Criar um Gateway de Serviço para acesso ao Oracle Services Network. O controlador de nuvem do OKE usa o Oracle Services para inicializar nós de trabalho (definir labels de domínio de disponibilidade, remover taints de inicialização). Sem um Gateway de Serviço, os nós de GPU podem permanecer em um estado não inicializado e o provisionamento do volume em blocos falhará.
SGW_SERVICE_ID=$(oci network service list \ --query "data[?contains(name, 'All') && contains(name, 'Services')].id | [0]" \ --raw-output) SGW_SERVICE_NAME=$(oci network service list \ --query "data[?contains(name, 'All') && contains(name, 'Services')].\"cidr-block\" | [0]" \ --raw-output) SGW_ID=$(oci network service-gateway create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-sgw" \ --services "[{\"serviceId\": \"${SGW_SERVICE_ID}\"}]" \ --query "data.id" \ --raw-output) -
Crie tabelas de roteamento para sub-redes públicas e privadas.
PRIVATE_RT_ID=$(oci network route-table create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-private-rt" \ --route-rules "[ {\"cidrBlock\": \"0.0.0.0/0\", \"networkEntityId\": \"${NAT_ID}\"}, {\"destination\": \"${SGW_SERVICE_NAME}\", \"destinationType\": \"SERVICE_CIDR_BLOCK\", \"networkEntityId\": \"${SGW_ID}\"} ]" \ --query "data.id" \ --raw-output) PUBLIC_RT_ID=$(oci network route-table create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-public-rt" \ --route-rules "[{\"cidrBlock\": \"0.0.0.0/0\", \"networkEntityId\": \"${IGW_ID}\"}]" \ --query "data.id" \ --raw-output)Observação: A tabela de roteamento privada tem duas regras: uma rota do Gateway NAT para acesso geral à internet (extraindo imagens de contêiner, fazendo download de modelos) e uma rota do Gateway de Serviço para acesso direto ao Oracle Services Network. A rota do Gateway de Serviço é crítica. Sem ela, o controlador de nuvem do OKE não poderá inicializar nós de trabalho, o que impede o provisionamento do volume em blocos. A tabela de roteamento pública usa o Gateway de Internet para acesso do balanceador de carga.
-
Crie uma lista de segurança com as regras de entrada e saída necessárias para o OKE.
SL_ID=$(oci network security-list create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-sl" \ --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \ --ingress-security-rules '[ {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}, "description": "SSH access"}, {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"}, {"source": "10.244.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes pods CIDR"}, {"source": "10.96.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes services CIDR"}, {"source": "0.0.0.0/0", "protocol": "1", "isStateless": false, "icmpOptions": {"type": 3, "code": 4}, "description": "Path MTU discovery"} ]' \ --query "data.id" \ --raw-output)Nota de Segurança: Este exemplo de lista de segurança é intencionalmente amplo para simplificar. Para produção, restrinja o SSH à sub-rede bastion e à sua faixa de IPs e prefira listas de segurança separadas ou NSGs por sub-rede para que a sub-rede do balanceador de carga não permita o SSH de
0.0.0.0/0.Padrão seguro: Comece limitando o SSH ao seu IP público e anexando regras SSH somente à sub-rede bastion. Você pode manter os CIDRs de pod/serviço do Kubernetes na sub-rede de trabalho e omitir o SSH inteiramente da sub-rede do balanceador de carga.
Divisão opcional (recomendada): Crie uma pequena lista de segurança somente SSH para a sub-rede bastion e uma lista separada para sub-redes worker/LB.
BASTION_SL_ID=$(oci network security-list create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-bastion-sl" \ --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \ --ingress-security-rules '[ {"source": "YOUR_PUBLIC_IP/32", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 22, "max": 22}}, "description": "SSH from your IP"} ]' \ --query "data.id" \ --raw-output) WORKER_SL_ID=$(oci network security-list create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-worker-sl" \ --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \ --ingress-security-rules '[ {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"}, {"source": "10.244.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes pods CIDR"}, {"source": "10.96.0.0/16", "protocol": "all", "isStateless": false, "description": "Kubernetes services CIDR"}, {"source": "0.0.0.0/0", "protocol": "1", "isStateless": false, "icmpOptions": {"type": 3, "code": 4}, "description": "Path MTU discovery"} ]' \ --query "data.id" \ --raw-output) LB_SL_ID=$(oci network security-list create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-lb-sl" \ --egress-security-rules '[{"destination": "0.0.0.0/0", "protocol": "all", "isStateless": false}]' \ --ingress-security-rules '[ {"source": "10.0.0.0/16", "protocol": "all", "isStateless": false, "description": "VCN internal traffic"}, {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 80, "max": 80}}, "description": "HTTP (public LB)"}, {"source": "0.0.0.0/0", "protocol": "6", "isStateless": false, "tcpOptions": {"destinationPortRange": {"min": 443, "max": 443}}, "description": "HTTPS (public LB)"} ]' \ --query "data.id" \ --raw-output)Observação: Se você usar apenas um balanceador de carga interno, substitua as origens
0.0.0.0/0acima por10.0.0.0/16(ou seu CIDR da VCN). Uso: AnexeBASTION_SL_IDà sub-rede bastion,WORKER_SL_IDàs sub-redes de API/trabalhador eLB_SL_IDà sub-rede do balanceador de carga.Observação: As regras CIDR de pods do Kubernetes (
10.244.0.0/16) e CIDR de serviços (10.96.0.0/16) são necessárias para que os nós de trabalho de GPU se registrem no cluster. A regra ICMP tipo 3 código 4 permite a descoberta de MTU do caminho, o que impede problemas de fragmentação do pacote. -
Crie as sub-redes. O cluster requer quatro sub-redes: uma para o ponto final da API do Kubernetes, uma para nós de trabalho, uma para balanceadores de carga e outra para o bastion host usado para acessar o cluster privado.
API_SUBNET_ID=$(oci network subnet create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-api-subnet" \ --cidr-block "10.0.0.0/28" \ --route-table-id "${PRIVATE_RT_ID}" \ --security-list-ids "[\"${SL_ID}\"]" \ --dns-label "kubeapi" \ --prohibit-public-ip-on-vnic true \ --query "data.id" \ --raw-output) WORKER_SUBNET_ID=$(oci network subnet create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-worker-subnet" \ --cidr-block "10.0.10.0/24" \ --route-table-id "${PRIVATE_RT_ID}" \ --security-list-ids "[\"${SL_ID}\"]" \ --dns-label "workers" \ --prohibit-public-ip-on-vnic true \ --query "data.id" \ --raw-output) LB_SUBNET_ID=$(oci network subnet create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-lb-subnet" \ --cidr-block "10.0.20.0/24" \ --route-table-id "${PUBLIC_RT_ID}" \ --security-list-ids "[\"${SL_ID}\"]" \ --dns-label "loadbalancers" \ --query "data.id" \ --raw-output) BASTION_SUBNET_ID=$(oci network subnet create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --vcn-id "${VCN_ID}" \ --display-name "${CLUSTER_NAME}-bastion-subnet" \ --cidr-block "10.0.30.0/24" \ --route-table-id "${PUBLIC_RT_ID}" \ --security-list-ids "[\"${SL_ID}\"]" \ --dns-label "bastion" \ --query "data.id" \ --raw-output)Sub-rede CIDR Visibilidade Objetivo Ponto final da API 10.0.0.0/28Privado(a) Servidor de API do Kubernetes nós de trabalho 10.0.10.0/24Privado(a) Nós de computação GPU Balanceadores de carga 10.0.20.0/24Público(a) Acesso ao serviço externo Bastion 10.0.30.0/24Público(a) Túnel SSH para acesso ao cluster privado
Tarefa 4: Criar o Cluster do OKE
Implantar um cluster gerenciado do Kubernetes no OKE usando os recursos de rede criados na Tarefa 3. O provisionamento do cluster leva aproximadamente 10 minutos. Este tutorial cria um cluster privado (o padrão de script), que não consome um IP público reservado para o ponto final da API do Kubernetes. Os clusters privados são a abordagem recomendada para cargas de trabalho de produção porque o servidor de API não está exposto à internet pública.
-
Crie o cluster do OKE com um ponto final privado.
oci ce cluster create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --name "${CLUSTER_NAME}" \ --vcn-id "${VCN_ID}" \ --kubernetes-version "${KUBERNETES_VERSION}" \ --endpoint-subnet-id "${API_SUBNET_ID}" \ --service-lb-subnet-ids "[\"${LB_SUBNET_ID}\"]" \ --endpoint-public-ip-enabled falseO comando retorna um ID de solicitação de serviço. Obtenha o ID do cluster na lista de clusters.
CLUSTER_ID=$(oci ce cluster list \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --lifecycle-state CREATING \ --query 'data[0].id' \ --raw-output) echo "CLUSTER_ID=${CLUSTER_ID}"Observação: Os clusters privados não exigem um IP público reservado. Os nós de trabalho ainda acessam a internet por meio do Gateway NAT para extrair imagens de contêiner e fazer download de modelos. Somente o acesso
kubectlrequer um túnel SSH por meio do bastion (configurado nas próximas etapas). -
Aguarde o cluster se tornar ATIVO. Esta etapa leva aproximadamente 10 minutos.
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputVerifique o comando até que a saída retorne
ACTIVE.Opcional: exiba um resumo conciso do status (incluindo o ponto final da API privada).
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query 'data.{name:name,state:"lifecycle-state","k8s-version":"kubernetes-version",endpoint:endpoints."private-endpoint"}' \ --output table
kubectl get nodes -o wide
-
Crie um Bastion do OCI para acessar o cluster privado. O bastion fornece um túnel SSH gerenciado para o ponto final privado da API do Kubernetes.
BASTION_ID=$(oci bastion bastion create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --bastion-type STANDARD \ --target-subnet-id "${BASTION_SUBNET_ID}" \ --name "${CLUSTER_NAME}-bastion" \ --client-cidr-list '["YOUR_PUBLIC_IP/32"]' \ --query "data.id" \ --raw-output)Observação: Substitua
YOUR_PUBLIC_IP/32pelo seu IP público atual. Para redes compartilhadas, use seu bloco CIDR corporativo.Espere que o bastion se torne ATIVO (aproximadamente 1 minuto).
oci bastion bastion get \ --bastion-id "${BASTION_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputNota de Segurança: Para produção, não use
0.0.0.0/0. Restrinja o--client-cidr-listao seu IP público ou CIDR corporativo (por exemplo,"YOUR_PUBLIC_IP/32"); caso contrário, qualquer pessoa na internet poderá tentar uma sessão bastion. -
Faça download do kubeconfig usando o ponto final privado.
oci ce cluster create-kubeconfig \ --cluster-id "${CLUSTER_ID}" \ --file "${HOME}/.kube/config" \ --region "${OCI_REGION}" \ --token-version 2.0.0 \ --kube-endpoint PRIVATE_ENDPOINT -
Obtenha o endereço IP do ponto final privado para o túnel SSH.
PRIVATE_ENDPOINT=$(oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.endpoints.\"private-endpoint\"" \ --raw-output) PRIVATE_IP="${PRIVATE_ENDPOINT%:*}" echo "Private endpoint IP: ${PRIVATE_IP}" -
Criar uma sessão de encaminhamento de porta bastion. Você precisará de um arquivo de chave pública SSH.
SESSION_ID=$(oci bastion session create-port-forwarding \ --bastion-id "${BASTION_ID}" \ --target-private-ip "${PRIVATE_IP}" \ --target-port 6443 \ --session-ttl 10800 \ --display-name "kubectl-tunnel" \ --ssh-public-key-file ~/.ssh/id_rsa.pub \ --query "data.id" \ --raw-output)Aguarde a sessão se tornar ACTIVE e, em seguida, obtenha o comando SSH.
oci bastion session get \ --session-id "${SESSION_ID}" \ --query "data.{state:\"lifecycle-state\", ssh:\"ssh-metadata\".command}" 2>&1 -
Abra um terminal separado e inicie o túnel SSH usando o comando da etapa anterior. O túnel encaminha a porta local 6443 para a API privada do Kubernetes.
ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -N -L 6443:<PRIVATE_IP>:6443 \ -p 22 -o ServerAliveInterval=30 \ <SESSION_OCID>@host.bastion.<REGION>.oci.oraclecloud.comObservação: Substitua
<PRIVATE_IP>,<SESSION_OCID>e<REGION>pelos valores da etapa anterior. Mantenha este terminal aberto durante a sessão. O flag-o IdentitiesOnly=yesimpede erros de "muitas falhas de autenticação" quando seu agente SSH tem várias chaves carregadas. -
Atualize o kubeconfig para estabelecer conexão por meio do túnel local.
CLUSTER_NAME_KUBE=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}') kubectl config set-cluster "${CLUSTER_NAME_KUBE}" \ --server=https://127.0.0.1:6443 \ --insecure-skip-tls-verify=trueObservação: O flag
--insecure-skip-tls-verifyé obrigatório porque o certificado do cluster foi emitido para o IP do ponto final privado, não para127.0.0.1. Isso é seguro porque o tráfego é criptografado por meio do túnel SSH. -
Se você estiver usando um perfil da CLI do OCI não padrão (por exemplo,
API_KEY_AUTH), atualize o kubeconfig para usá-lo. O kubeconfig gerado usa como padrão o perfilDEFAULTpara geração de token.kubectl config set-credentials \ $(kubectl config view --minify -o jsonpath='{.users[0].name}') \ --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}Dica: As etapas de 6 a 9 são automatizadas pelo
./entry_point.sh tunnel, que também se conecta automaticamente se o túnel SSH cair durante operações de longa execução, como expansão de disco. Execute-o em um terminal separado e deixe-o em execução durante a sessão. -
Verifique o acesso ao cluster.
kubectl get nodes
Neste ponto, a saída não mostrará nós, já que o pool de nós de GPU ainda não foi adicionado.
No resources found
Tarefa 5: Adicionar Pool de Nós de GPU
Adicione um pool de nós com instâncias de computação de GPU ao cluster do OKE.
-
Localize a imagem mais recente do nó do OKE compatível com GPU. O OKE requer imagens específicas com componentes de registro de nó e kubelet pré-instalados. Use a API
node-pool-optionspara localizar a imagem correta para sua versão do Kubernetes.GPU_IMAGE_ID=$(oci ce node-pool-options get \ --node-pool-option-id all \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --query "data.sources[?contains(\"source-name\", 'GPU') && contains(\"source-name\", 'OKE-${KUBERNETES_VERSION#v}') && contains(\"source-name\", '8.10')].\"image-id\" | [0]" \ --raw-output) echo "GPU Image: ${GPU_IMAGE_ID}"Observação: Os filtros de consulta para imagens de GPU do Oracle Linux 8.10 correspondentes à sua versão do Kubernetes (por exemplo,
OKE-1.31.10). Se você precisar de imagens baseadas no ARM, substitua8.10pelo filtro apropriado. -
Determine o domínio de disponibilidade que tem formas de GPU. Nem todos os domínios de disponibilidade têm capacidade de GPU.
AD=$(oci iam availability-domain list \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --query "data[${GPU_AD_INDEX}].name" \ --raw-output) echo "Availability Domain: ${AD}"Observação: A capacidade da GPU varia de acordo com a região e o domínio de disponibilidade. Se a criação do pool de nós falhar com um erro "Fora da capacidade do host", tente outra forma de domínio de disponibilidade (
GPU_AD_INDEX) ou GPU ou solicite a capacidade por meio do seu processo normal do OCI. -
Crie o pool de nós de GPU com um volume de inicialização de 200 GB.
oci ce node-pool create \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --cluster-id "${CLUSTER_ID}" \ --name "${GPU_NODE_POOL_NAME:-gpu-pool}" \ --kubernetes-version "${KUBERNETES_VERSION}" \ --node-shape "${GPU_SHAPE}" \ --node-image-id "${GPU_IMAGE_ID}" \ --node-boot-volume-size-in-gbs "${GPU_BOOT_VOLUME_GB:-200}" \ --size "${GPU_NODE_COUNT}" \ --placement-configs "[{\"availabilityDomain\": \"${AD}\", \"subnetId\": \"${WORKER_SUBNET_ID}\"}]" \ --initial-node-labels '[{"key": "app", "value": "gpu"}, {"key": "nvidia.com/gpu", "value": "true"}]'Observação: Os labels de nó
app=gpuenvidia.com/gpu=truesão usados posteriormente pelo gráfico vLLM Helm para programar pods de inferência em nós de GPU. O volume de inicialização de 200 GB fornece espaço para a imagem do contêiner vLLM (~10 GB) e pesos do modelo, mas o sistema de arquivos deve ser expandido antes do uso (consulte a Tarefa 8). -
Aguarde até que os nós de GPU se tornem Prontos. Isso normalmente leva de 5 a 10 minutos, enquanto o nó provisiona, inicializa, instala drivers de GPU e se registra no cluster.
Observação: as instâncias de GPU estão sujeitas a restrições de capacidade. Se o pool de nós permanecer no estado CREATING, verifique o status do nó na Console do OCI ou com o
oci ce node-pool get. Um erro de "Capacidade insuficiente do host" significa que não há instâncias de GPU disponíveis nesse domínio de disponibilidade. Para resolver isso, tente outro domínio de disponibilidade (GPU_AD_INDEX=0ouGPU_AD_INDEX=2), tente outra forma de GPU ou solicite uma reserva de capacidade por meio da Console do OCI ou de um ticket de suporte.kubectl get nodes -wSaída esperada quando o nó estiver pronto:
NAME STATUS ROLES AGE VERSION 10.0.10.x Ready node 5m v1.31.10 -
Verifique se a GPU foi detectada no nó.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu'Saída esperada:
NAME GPUs 10.0.10.x 1 -
Aplique o patch CoreDNS para programar em nós de GPU. Os nós de GPU do OKE têm uma taint
nvidia.com/gpu=present:NoSchedule. Em clusters que só têm nós de GPU, os pods do sistema como CoreDNS não podem ser programados sem uma tolerância para essa mancha. Sem o DNS, os pods não podem resolver nomes de host externos para fazer download de modelos.kubectl patch deployment coredns -n kube-system --type='json' \ -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]' kubectl patch deployment kube-dns-autoscaler -n kube-system --type='json' \ -p='[{"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}}]'Verifique se o CoreDNS está em execução.
kubectl get pods -n kube-system | grep corednsObservação: Se o cluster tiver um pool de nós de CPU dedicado para cargas de trabalho do sistema, essa etapa não será necessária. Este patch só é necessário quando os nós de GPU são os únicos nós do cluster.
oci ce node-pool list --compartment-id "${OCI_COMPARTMENT_ID}" --cluster-id "${CLUSTER_ID}" \ --query 'data[].{name:name,state:"lifecycle-state",shape:"node-shape",size:"node-config-details".size}' --output table
Tarefa 6: Instalar Plug-in do Dispositivo NVIDIA
Instale o plug-in de dispositivo NVIDIA para que o Kubernetes possa detectar e programar cargas de trabalho na GPU.
-
Aplique o plug-in de dispositivo NVIDIA DaemonSet.
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml -
Aguarde que os pods de plug-in estejam prontos.
kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300sObservação: Algumas imagens de nó de GPU do OKE incluem um plug-in de dispositivo NVIDIA pré-instalado (
nvidia-gpu-device-plugin). Se a imagem já a incluir, a aplicação do DaemonSet upstream criará uma segunda instância que não causará conflitos. O script automatizado (entry_point.sh deploy-vllm) sempre o instala para garantir que a detecção de GPU funcione, independentemente da versão da imagem do nó. -
Confirme se a GPU é alocável pelo Kubernetes.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'Saída esperada:
NAME GPUs 10.0.10.x 1 -
Aplique o patch CoreDNS para tolerar manchas de nó de GPU. Em clusters em que os nós de GPU são os únicos nós de trabalho, os pods CoreDNS não podem ser programados porque os nós de GPU do OKE têm uma mancha
nvidia.com/gpu=present:NoSchedule. Sem o DNS, os pods não podem resolver registros de imagem ou URLs de download de modelo.kubectl patch deployment coredns -n kube-system --type='json' -p='[ {"op": "add", "path": "/spec/template/spec/tolerations/-", "value": {"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}} ]' kubectl rollout status deployment/coredns -n kube-system --timeout=60sObservação: Esta etapa só é necessária quando os nós de GPU são os únicos nós de trabalho no cluster. Se você tiver um pool de nós de CPU dedicado para cargas de trabalho do sistema, o CoreDNS o programará por padrão e esse patch será desnecessário.
Tarefa 7: Configurar Armazenamento
Aplique o OCI Block Volume StorageClass para fornecer armazenamento persistente para pesos de modelo.
-
Aplique a definição StorageClass.
kubectl apply -f oci-block-storage-sc.yamlO arquivo define duas camadas de desempenho:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: oci-block-storage-enc provisioner: blockvolume.csi.oraclecloud.com parameters: vpusPerGB: "10" reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer allowVolumeExpansion: trueStorageClass Desempenho Caso de Uso oci-block-storage-encBalanceado ( vpusPerGB: 10)Padrão, econômico para a maioria dos modelos oci-block-storage-hpAlto desempenho ( vpusPerGB: 20)Carregamento de modelos mais rápido para modelos maiores -
Verifique se o StorageClasses está disponível.
kubectl get storageclass
Observação: Para implantações com vários nós que exigem armazenamento compartilhado em vários pods, use o OCI File Storage Service (NFS) com o modo de acesso
ReadWriteManyem vez de Block Volumes.
Tarefa 8: Expandir Sistema de Arquivos de Nó de GPU
Os volumes de inicialização do OCI têm uma partição fixa de ~47 GB, independentemente do tamanho do volume de inicialização especificado. A imagem do contêiner vLLM sozinha é de aproximadamente 10 GB, e os pesos do modelo exigem espaço adicional. Você deve expandir o sistema de arquivos antes de implantar o vLLM para evitar remoções de DiskPressure.
Observação: Este é um requisito específico do OCI. O volume de inicialização é provisionado em 200 GB, mas o sistema operacional só faz a partição de ~47 GB por padrão. O espaço restante deve ser reivindicado manualmente.
-
Verifique o tamanho atual do sistema de arquivos no nó GPU.
GPU_NODE=$(kubectl get nodes -l app=gpu -o jsonpath='{.items[0].metadata.name}') kubectl run check-disk --rm -i --restart=Never \ --image=busybox:latest \ --overrides="{\"spec\":{\"nodeName\":\"${GPU_NODE}\",\"tolerations\":[{\"operator\":\"Exists\"}],\"containers\":[{\"name\":\"check\",\"image\":\"busybox:latest\",\"command\":[\"sh\",\"-c\",\"chroot /host df -h / | tail -1\"],\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"name\":\"host\",\"mountPath\":\"/host\"}]}],\"volumes\":[{\"name\":\"host\",\"hostPath\":{\"path\":\"/\"}}]}}"A saída mostrará aproximadamente 47 GB no total, confirmando que a expansão é necessária.
-
Crie um pod privilegiado no nó da GPU para executar os comandos de expansão.
kubectl run expand-disk --restart=Never \ --image=busybox:latest \ --overrides="{\"spec\":{\"nodeName\":\"${GPU_NODE}\",\"tolerations\":[{\"operator\":\"Exists\"}],\"containers\":[{\"name\":\"expand\",\"image\":\"busybox:latest\",\"command\":[\"sleep\",\"600\"],\"securityContext\":{\"privileged\":true},\"volumeMounts\":[{\"name\":\"host\",\"mountPath\":\"/host\"}]}],\"volumes\":[{\"name\":\"host\",\"hostPath\":{\"path\":\"/\"}}]}}"Aguarde a inicialização do pod.
kubectl wait --for=condition=Ready pod/expand-disk --timeout=60s -
Execute todas as quatro etapas de expansão em um único comando
kubectl exec. Executá-los juntos evita o risco dekubectl execretornar o código de saída 137 (SIGKILL) entre as etapas, o que pode acontecer durante a E/S de disco pesado no host.kubectl exec expand-disk -- chroot /host bash -c ' set -x growpart /dev/sda 3 || echo "growpart: partition may already be expanded" sleep 3 pvresize /dev/sda3 lvextend -l +100%FREE /dev/ocivolume/root || echo "lvextend: may already be extended" xfs_growfs / echo "EXPANSION_COMPLETE" df -h / 'Etapa Comando Objetivo 1 growpart /dev/sda 3Expanda a partição 3 para usar o disco completo 2 pvresize /dev/sda3Redimensionar volume físico de LVM 3 lvextend -l +100%FREE /dev/ocivolume/rootEstender volume lógico 4 xfs_growfs /Aumente o sistema de arquivos XFS para preencher o volume Observação: todas as quatro operações são idempotentes. Se a execução retornar o código de saída 137, você poderá executar novamente todo o bloco com segurança. Procure
EXPANSION_COMPLETEna saída para confirmar o sucesso. -
Reinicie o kubelet para que o nó reporte o armazenamento alocável atualizado e, em seguida, verifique e limpe.
kubectl exec expand-disk -- nsenter -t 1 -m -p -- systemctl restart kubelet 2>/dev/null \ || echo "Warning: kubelet restart returned non-zero (non-critical)" kubectl exec expand-disk -- chroot /host df -h / kubectl delete pod expand-disk --forceObservação: O comando
nsenterinforma o namespace PID do host para acessar systemd. Umchroot /host systemctl restart kubeletsimples falha porque não pode se conectar ao barramento systemd de dentro de um chroot.A saída esperada deve mostrar aproximadamente 189 GB no total.
Tarefa 9: Implantar a Pilha de Produção vLLM
Instale a pilha de inferência de vLLM usando o Helm.
-
Adicione o repositório vLLM Helm.
helm repo add vllm https://vllm-project.github.io/production-stack helm repo update -
Verifique o arquivo de valores do Helm. O
production_stack_specification.yamlconfigura o modelo, os recursos e o armazenamento para o OCI.servingEngineSpec: runtimeClassName: "" modelSpec: - name: "gpt-oss" repository: "vllm/vllm-openai" tag: "latest" modelURL: "openai/gpt-oss-20b" replicaCount: 1 requestCPU: 4 requestMemory: "24Gi" requestGPU: 1 # No HF token needed - Apache 2.0 licensed OpenAI open source model pvcStorage: "100Gi" pvcAccessMode: - ReadWriteOnce storageClass: "oci-block-storage-enc" nodeSelector: app: gpu tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" extraArgs: - "--max-model-len=8192" - "--gpu-memory-utilization=0.90"Observação: O modelo
openai/gpt-oss-20bé um modelo de Mistura de Especialistas (MoE) com parâmetros totais 20B e parâmetros ativos 3.6B por avanço. Ele é lançado sob a licença Apache 2.0, portanto, nenhum token Hugging Face é necessário. A imagem do contêinervllm/vllm-openaifornece um servidor de API compatível com OpenAI, permitindo que os clientes usem chamadas padrão do SDK OpenAI em seu ponto final auto-hospedado. -
Implante a pilha. Não use
--waitaqui porque o pod do roteador será CrashLoop até ser corrigido na próxima etapa.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yamlAguarde o pod do mecanismo vLLM ser iniciado (o roteador será corrigido em seguida).
kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600sObservação: O pod do mecanismo leva vários minutos para se tornar Pronto porque faz download dos pesos do modelo no primeiro início. Se o pod permanecer em
ContainerCreating, a imagem do contêiner (~10 GB) ainda estará sendo extraída. Usekubectl describe pod <pod-name>para verificar o andamento. -
Corrigir a implantação do roteador. O roteador precisa de uma tolerância de GPU (para que possa ser agendado quando os nós de GPU forem os únicos com capacidade) e limites de memória aumentados (o padrão 500 Mi pode causar OOMKill).
kubectl patch deployment vllm-deployment-router --type='json' -p='[ {"op": "add", "path": "/spec/template/spec/tolerations", "value": [{"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}]}, {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "512Mi"}, {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "1Gi"} ]'Observação: A tolerância de GPU é necessária porque os nós de GPU do OKE têm uma taint
nvidia.com/gpu=present:NoScheduleque impede a programação de cargas de trabalho não GPU. Como o roteador não usa uma GPU, mas precisa ser executado em algum lugar, essa tolerância permite que ele seja programado em nós de GPU. Em clusters com pools de nós de CPU dedicados, essa tolerância não é necessária. -
Confirme se a release do Helm foi implantada.
helm list
-
Verifique se os pods estão em execução.
kubectl get podsSaída esperada:
NAME READY STATUS RESTARTS AGE vllm-deployment-router-xxxxxxxxxx-xxxxx 1/1 Running 0 5m vllm-gpt-oss-deployment-vllm-xxxxxxxxxx-xxxxx 1/1 Running 0 5mkubectl get pods
-
Verifique o andamento do carregamento do modelo nos logs de pod.
kubectl logs -f deployment/vllm-gpt-oss-deployment-vllmAguarde até que você veja uma mensagem indicando que o modelo foi carregado e que o servidor está pronto para aceitar solicitações.
Tarefa 10: Testar o Ponto Final de Inferência
Valide se a implantação está atendendo a solicitações de inferência. A Pilha de Produção vLLM expõe uma API compatível com OpenAI por meio do serviço do roteador, para que qualquer cliente SDK OpenAI ou comando curl possa interagir com ele.
O diagrama a seguir mostra o ciclo de vida da solicitação de inferência: desde a solicitação do cliente até a lógica de seleção do mecanismo do roteador, até as fases de pré-preenchimento e decodificação do mecanismo vLLM e de volta como uma resposta transmitida.

-
Liste os modelos disponíveis para confirmar se a implantação está íntegra.
kubectl get svc vllm-router-serviceO serviço de roteador fornece o gateway de API para todos os modelos implantados. Como o cluster usa um ponto final privado, você acessa o serviço por meio do
kubectl port-forward. -
Inicie uma transferência de porta da sua máquina local para o serviço do roteador. Abra um novo terminal (mantenha o túnel SSH em execução no outro) e execute:
kubectl port-forward svc/vllm-router-service 8080:80Isso mapeia
localhost:8080na sua máquina para a porta 80 no serviço do roteador dentro do cluster.Observação de Segurança: O
kubectl port-forwardé vinculado localmente e não expõe o serviço publicamente. Esta é a maneira mais segura de testar ao usar um cluster privado em um túnel bastion.Observação: O comando port-forward é executado em primeiro plano. Mantenha este terminal aberto durante o teste. Pressione Ctrl+C para interrompê-lo quando terminar.
-
Em outro terminal, verifique se o modelo está disponível consultando o ponto final dos modelos.
curl -s http://localhost:8080/v1/models | python3 -m json.toolSaída esperada:
{ "object": "list", "data": [ { "id": "openai/gpt-oss-20b", "object": "model", "created": 1234567890, "owned_by": "vllm" } ] } -
Enviar uma solicitação de conclusão de texto.
curl -s http://localhost:8080/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-oss-20b", "prompt": "Oracle Cloud Infrastructure is", "max_tokens": 50 }' | python3 -m json.toolSaída esperada (abreviada):
{ "id": "cmpl-xxxxxxxxxxxx", "object": "text_completion", "model": "openai/gpt-oss-20b", "choices": [ { "index": 0, "text": " a cloud computing platform that provides ...", "finish_reason": "length" } ], "usage": { "prompt_tokens": 5, "completion_tokens": 50, "total_tokens": 55 } } -
Envie uma solicitação de conclusão de chat. Esse é o mesmo formato usado pelo OpenAI Python SDK e é a maneira mais comum de interagir com LLMs de forma programática.
curl -s http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-oss-20b", "messages": [{"role": "user", "content": "What is Oracle Cloud Infrastructure in one sentence?"}], "max_tokens": 100 }' | python3 -m json.toolSaída esperada (abreviada):
{ "id": "chatcmpl-xxxxxxxxxxxx", "object": "chat.completion", "model": "openai/gpt-oss-20b", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Oracle Cloud Infrastructure (OCI) is Oracle's enterprise-grade cloud platform that provides a full range of services for building, deploying, and managing applications and workloads..." }, "finish_reason": "length" } ], "usage": { "prompt_tokens": 71, "completion_tokens": 100, "total_tokens": 171 } }Ambas as respostas incluem o texto gerado do modelo no array
choices, as estatísticas de uso do token e umfinish_reasondestop(o modelo foi concluído naturalmente) oulength(a saída foi truncada emmax_tokens).Observação: O nome do modelo na solicitação de API é o caminho completo do modelo (
openai/gpt-oss-20b), que corresponde ao campomodelURLnos valores do Helm. Qualquer cliente compatível com OpenAI pode usar esse ponto final definindobase_urlcomohttp://localhost:8080/v1.
-
Meça a latência de ponta a ponta para uma solicitação curta.
time curl -s http://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What is Kubernetes?"}],"max_tokens":50}' > /dev/nullEm uma única GPU A10, espere de 1 a 3 segundos de ponta a ponta para conclusões curtas. O tempo para o primeiro token (TTFT) geralmente é de 50 a 200 ms, dependendo do tamanho do prompt. Para um throughput mais alto, aumente
replicaCountnos valores do Helm para adicionar mais réplicas de mecanismo atrás do roteador.
Tarefa 11: Expor por meio do OCI Load Balancer (Opcional)
Torne o ponto final de inferência acessível externamente por meio de um Balanceador de Carga do OCI.
Observação de Segurança: Isso expõe a API de inferência à internet pública por padrão. Não ative isso na produção sem TLS, autenticação (chave de API/JWT/mTLS) e lista de permissões de IP ou controles de WAF. Se você precisar expô-lo, use um controlador de entrada ou um gateway de API que impõe limites de taxa e autenticação ou use um balanceador de carga interno.
-
Aplique patch no serviço do roteador para usar um tipo LoadBalancer.
kubectl patch svc vllm-router-service \ -p '{"spec": {"type": "LoadBalancer"}}'Observação: se você quiser um balanceador de carga interno, adicione a anotação do OCI ao serviço (exemplo abaixo). Isso mantém o ponto final privado dentro da VCN.
kubectl annotate svc vllm-router-service \ "service.beta.kubernetes.io/oci-load-balancer-internal"="true" -
Aguarde a designação do IP externo.
kubectl get svc vllm-router-service -wSaída esperada depois que o balanceador de carga for provisionado:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) vllm-router-service LoadBalancer 10.96.x.x 129.xxx.xxx.xx 80:xxxxx/TCPkubectl get svc
-
Teste o ponto final externo.
curl http://<EXTERNAL-IP>/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-oss-20b", "prompt": "Hello from OCI!", "max_tokens": 50 }'
Tarefa 12: Configurar Paralelismo de Tensor de Várias GPUs (Avançado)
Implante modelos maiores em várias GPUs em formas bare metal.
O paralelismo de tensores divide um modelo em várias GPUs em um único nó. Isso é necessário quando os requisitos de memória de um modelo excedem uma única GPU. Por exemplo, o Meta Llama 3.1 70B requer aproximadamente 140 GB de memória GPU, o que excede a capacidade de qualquer GPU única, mas se encaixa em 8x A100 80 GB ou 8x H100 GPUs.
-
Crie um segredo do Kubernetes com seu token Hugging Face. Modelos fechados, como o Llama 3.1 70B, exigem autenticação.
kubectl create secret generic hf-token-secret \ --from-literal=token=YOUR_HUGGINGFACE_TOKEN -
Atualize o
production_stack_specification.yamlcom uma configuração de vários GPUs.servingEngineSpec: modelSpec: - name: "llama70b" repository: "vllm/vllm-openai" tag: "latest" modelURL: "meta-llama/Llama-3.1-70B-Instruct" replicaCount: 1 tensorParallelSize: 8 requestCPU: 32 requestMemory: "256Gi" requestGPU: 8 hf_token: secretName: "hf-token-secret" secretKey: "token" pvcStorage: "500Gi" pvcAccessMode: - ReadWriteOnce storageClass: "oci-block-storage-enc" nodeSelector: node.kubernetes.io/instance-type: "BM.GPU.H100.8" tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" extraArgs: - "--max-model-len=8192" - "--gpu-memory-utilization=0.95" - "--tensor-parallel-size=8" -
Implante com os valores atualizados. Assim como na Tarefa 9, não use
--wait. O roteador será CrashLoop até ser corrigido.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900sEm seguida, corrija o roteador (o mesmo que Tarefa 9, etapa 4) e verifique:
kubectl patch deployment vllm-deployment-router --type='json' -p='[ {"op": "add", "path": "/spec/template/spec/tolerations", "value": [{"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}]}, {"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "512Mi"}, {"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "1Gi"} ]' -
Verifique se o pod está em execução e se todas as GPUs estão em uso.
kubectl get pods kubectl logs -f deployment/vllm-llama70b-deployment-vllm
Observação: Certifique-se de que o cluster do OKE tenha um pool de nós com a forma de GPU bare metal apropriada (por exemplo,
BM.GPU.H100.8) antes de implantar uma configuração de vários GPUs.
Tarefa 13: Usar o OCI Object Storage para Modelos (Avançado)
Carregue pesos de modelo do OCI Object Storage em vez de fazer download do Hugging Face. Isso é útil para modelos privados, downloads mais rápidos na OCI ou ambientes sem acesso externo à internet.
-
Faça upload dos pesos do modelo para um bucket do OCI Object Storage. Navegue até Armazenamento > Object Storage na Console do OCI e crie um bucket se ainda não tiver um.
-
Crie um URL de PAR (Solicitação Pré-Autenticada) para seu bucket. Na Console do OCI, selecione seu bucket, clique em Solicitações Pré-Autenticadas e crie uma nova solicitação com acesso de leitura.
-
Atualize o
production_stack_specification.yamlpara usar o URL da PAR.servingEngineSpec: modelSpec: - name: "custom-model" repository: "iad.ocir.io/YOUR_TENANCY/vllm-custom:latest" modelURL: "/models/custom-model" env: - name: BUCKET_PAR_URL value: "https://objectstorage.us-ashburn-1.oraclecloud.com/p/xxx/n/namespace/b/bucket/o" - name: MODEL_NAME value: "custom-model" -
Implante com os valores atualizados (Sem
--wait. Consulte a Tarefa 9 para saber por quê).helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=custom-model --timeout=600s -
Verifique os carregamentos de modelo do serviço Object Storage verificando os logs de pod.
kubectl logs -f deployment/vllm-custom-model-deployment-vllm
Tarefa 14: Limpar Recursos
Remova todos os recursos implantados para evitar cobranças contínuas.
-
Remova os recursos do Kubernetes usando o script de limpeza.
cd production-stack/deployment_on_cloud/oci ./clean_up.shIsso desinstala a release do Helm, exclui todos os recursos PersistentVolumeClaims, PersistentVolumes e vLLM personalizados.
-
Excluir o cluster do OKE e todos os recursos de rede do OCI.
./entry_point.sh cleanupIsso exclui os seguintes recursos na ordem:
- Pool de nós de GPU
- Cluster do OKE
- Bastion host (se criado)
- Sub-redes (API, worker, load balancer, bastion)
- Listas de segurança
- Gateway de Serviço, Gateway NAT e Gateway de Internet
- Tabelas de roteamento
- VCN
-
Verifique se todos os recursos foram removidos na Console do OCI em Developer Services > Clusters do Kubernetes e Networking > Redes Virtuais na Nuvem.
./entry_point.sh cleanup
Observação: certifique-se de que todos os recursos sejam excluídos para evitar cobranças contínuas. As instâncias de GPU e os volumes em blocos incorrem em custos mesmo quando ociosos.
O Que Vem a Seguir
Este tutorial implantou uma pilha de inferência funcional. Para cargas de trabalho de produção, considere os seguintes aprimoramentos:
- Monitoramento: o vLLM expõe as métricas do Prometheus em
/metricsem cada pod do mecanismo. Conecte uma pilha do Prometheus + Grafana para rastrear o tempo até o primeiro token, tokens por segundo, utilização do cache KV e profundidade da fila de solicitações. - Dimensionamento Automático: Configure o Kubernetes Horizontal Pod Autoscaler com métricas personalizadas (profundidade da fila de solicitações ou utilização de GPU via DCGM) para dimensionar réplicas do mecanismo automaticamente sob carga.
- Proteção de segurança: Restrinja
BASTION_CLIENT_CIDRà sua faixa de IPs, adicione políticas de rede do Kubernetes para isolar o namespace de inferência e armazenar credenciais de modelo no OCI Vault em vez de segredos do Kubernetes. - Alta disponibilidade: Distribua nós de GPU em vários domínios de disponibilidade e aumente
replicaCountnos valores do Helm para que o roteador possa fazer failover entre as réplicas do mecanismo. - Otimização de custo: use
./entry_point.sh cleanupquando não estiver em uso. Para cargas de trabalho de desenvolvimento/teste, considere instâncias de GPU preemptivas. Monitore a utilização da GPU para dimensionar corretamente seu pool de nós. - Infraestrutura como Código: Codifique essa implantação com o provedor OCI Terraform ou o OCI Resource Manager para infraestrutura repetível e controlada por versão.
Links Relacionados
Confirmações
- Autor - Federico Kamelhar (Arquiteto Principal Sênior, Agentic AI)
Mais Recursos de Aprendizado
Explore outros laboratórios em docs.oracle.com/learn ou acesse mais conteúdo de aprendizado gratuito no canal do Oracle Learning YouTube. Além disso, acesse education.oracle.com/learning-explorer para se tornar um Oracle Learning Explorer.
Para obter a documentação do produto, visite o Oracle Help Center.
Deploy OpenAI vLLM Production Stack on Oracle Kubernetes Engine (OKE)
G50931-01