Note:
- Este tutorial requiere acceso a Oracle Cloud. Para registrarse en una cuenta gratuita, consulte Introducción a la cuenta gratuita de Oracle Cloud Infrastructure.
- Utiliza valores de ejemplo para credenciales, arrendamiento y compartimentos de Oracle Cloud Infrastructure. Al finalizar el laboratorio, sustituya estos valores por otros específicos de su entorno en la nube.
Despliegue de la pila de producción de vLLM OpenAI en Oracle Kubernetes Engine (OKE)
Introducción
Las organizaciones que adoptan grandes modelos de lenguaje (LLM) para cargas de trabajo de producción se enfrentan a una decisión crítica de infraestructura: confiar en API de inferencia de terceros o desplegar una pila de inferencia autoalojada. Las implementaciones autoalojadas ofrecen ventajas significativas: privacidad de datos completa y control de cumplimiento, latencia de inferencia de menos de 100 milisegundos al eliminar viajes en red, costos predecibles a escala y la libertad de ajustar y servir cualquier modelo de código abierto sin dependencia del proveedor.
Sin embargo, la creación de una pila de inferencia de LLM de nivel de producción desde cero es compleja. Requiere orquestación de contenedores con GPU, enrutamiento de solicitudes inteligente en múltiples réplicas de modelos, almacenamiento persistente para grandes pesos de modelos y monitoreo continuo, todo integrado y en ejecución confiable.
Oracle Cloud Infrastructure ofrece varias rutas para la inferencia de IA. El servicio OCI Generative AI proporciona una experiencia totalmente gestionada con clusters de IA dedicados aislados en su arrendamiento, ideal para equipos que desean comenzar rápidamente con modelos soportados. En este tutorial se adopta el enfoque alternativo: desplegar su propia pila de inferencia en OKE. Esta ruta está diseñada para equipos que necesitan un control preciso sobre los controladores de GPU, las versiones de CUDA, las configuraciones de modelos y los parámetros de servicio, o equipos que entrenan y ajustan modelos personalizados y desean servirlos directamente. OCI proporciona instancias de GPU con hardware dedicado con GPU NVIDIA A10, A100 y H100, conectadas por redes de clústeres RDMA de latencia ultrabaja, lo que le brinda el mismo nivel de control de hardware que tendría en las instalaciones y se beneficia de la flexibilidad de la nube.
La pila de producción de vLLM resuelve la complejidad de la inferencia autoalojada al proporcionar una plataforma de código abierto nativa de Kubernetes basada en vLLM, el motor de inferencia de alto rendimiento utilizado en producción por organizaciones como Meta, Mistral AI e IBM. Ofrece hasta 24x mayor rendimiento en comparación con los marcos de servicio estándar a través de una gestión eficiente de la memoria GPU y la optimización de la caché de KV. En combinación con las unidades de GPU de OKE y OCI, obtienes una plataforma de inferencia lista para producción con redes, almacenamiento y seguridad de nivel empresarial. Los scripts de despliegue de OCI utilizados en este tutorial se añaden y mantienen en el repositorio oficial de pila de producción de vLLM.
Este tutorial le guiará a través del despliegue de la pila de producción de vLLM en OKE, desde el aprovisionamiento de infraestructura hasta la ejecución de su primera solicitud de inferencia.
Nota: En este tutorial se aprovisionan recursos paso a paso mediante la CLI de OCI para ayudarle a comprender el flujo completo de recursos en la nube de OCI necesarios para un despliegue de inferencia de GPU. Para los entornos de producción, se recomienda codificar esta infraestructura mediante Terraform o OCI Resource Manager (Shepherd) para despliegues repetibles y controlados por versiones.

En este tutorial se utilizan los siguientes servicios de OCI:
| Servicio | Finalidad |
|---|---|
| Motor Kubernetes de Oracle (OKE) | Cluster de Kubernetes gestionado para orquestación de contenedores y programación de cargas de trabajo de GPU |
| OCI Compute (unidades de GPU) | Instancias de GPU NVIDIA A10 (24 GB) y A100 (80 GB) para inferencia de modelos |
| Volúmenes en bloque de OCI | Almacenamiento persistente para pesos de modelo con niveles de rendimiento configurables |
| Red virtual en la nube (VCN) de OCI | Infraestructura de red, incluidas subredes, gateways y listas de seguridad |
| Equilibrador de carga de OCI | Acceso externo a puntos finales de inferencia |
| OCI Bastion | Túneles SSH gestionados para el acceso al cluster privado |
| OCI Object Storage | Origen de modelo alternativo mediante URL de solicitud autenticada previamente (PAR) |
Objetivos
En este tutorial, realizará lo siguiente:
- Desplegar un cluster de OKE con pools de nodos con GPU mediante la CLI de OCI
- Configurar redes de OCI (VCN, subredes y gateways) para cargas de trabajo de Kubernetes
- Instalar y configurar el plugin del dispositivo NVIDIA para la programación de GPU
- Amplíe el sistema de archivos del nodo de GPU para utilizar toda la capacidad del volumen de inicio
- Desplegar la pila de producción de vLLM con un punto final de inferencia compatible con OpenAI
- Prueba de inferencia de LLM con solicitudes de API en el modelo OpenAI GPT-OSS-20B
- Configurar el paralelismo de tensor de varias GPU para modelos más grandes en unidades con hardware dedicado
- Uso de OCI Object Storage como origen de modelo alternativo
- Limpia todos los recursos de OCI para evitar cargos continuos
Requisitos
- Una cuenta de Oracle Cloud Infrastructure con lo siguiente:
- Un compartimento con permisos para crear y gestionar clusters de OKE
- Cuota de recursos informáticos de GPU para la unidad deseada (por ejemplo,
VM.GPU.A10.1oBM.GPU.A100-v2.8). Si no tiene cuota de GPU, solicítela mediante un ticket de soporte - Permisos para crear VCN, subredes, gateways de Internet y equilibradores de carga
- Acceso a OCI Block Volumes para almacenamiento persistente
- Acceso a OCI Object Storage (para la sección de carga avanzada de modelos)
Nota: Las salidas y capturas de pantalla de ejemplo de este tutorial utilizan us-chicago-1. Puede desplegar en cualquier región soportada definiendo
OCI_REGION. La capacidad de la GPU varía según la región y el dominio de disponibilidad, por lo que debe confirmar que la unidad de GPU de destino está disponible antes del despliegue. Compruebe la disponibilidad de la unidad de GPU por región y prepárese para probar un dominio de disponibilidad diferente (GPU_AD_INDEX) si detecta errores de capacidad.
Nota: En este tutorial se aprovisionan recursos de GPU pagados (por ejemplo,
VM.GPU.A10.1). No es una carga de trabajo Siempre gratis de OCI. Ejecute siempre los pasos de limpieza cuando haya terminado para evitar cargos continuos.
-
CLI de OCI instalada y configurada con
oci setup config -
jq instalado para el análisis de JSON
-
kubectl instalado
-
Helm instalado
-
Par de claves SSH (por ejemplo,
~/.ssh/id_rsay~/.ssh/id_rsa.pub) para el acceso bastión. Genere uno conssh-keygen -t rsa -b 4096si es necesario -
Familiaridad con los conceptos de Kubernetes (pods, servicios, despliegues, pools de nodos)
Nota: Este tutorial despliega
openai/gpt-oss-20b, un modelo con licencia de Apache 2.0 de OpenAI. No se requiere ningún token Hugging Face. Si desea desplegar modelos cerrados como Meta Llama 3.1, necesitará una cuenta de Hugging Face con un token de API.
Tarea 1: Configuración de variables de entorno
Defina la configuración de OCI necesaria antes de desplegar la infraestructura.
-
Busque el OCID del compartimento en la consola de OCI. Vaya a Identity & Security > Compartments y, a continuación, haga clic en el compartimento de destino y copie el OCID.
oci iam compartment list --query 'data[].{name:name,id:id}' --output table
-
Exporte la variable de entorno necesaria.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Opcionalmente, sustituya la configuración por defecto definiendo cualquiera de las siguientes variables de entorno.
Variable Por defecto Descripción OCI_REGIONus-ashburn-1Región de OCI para el despliegue OCI_PROFILEDEFAULTPerfil de configuración de CLI de OCI CLUSTER_NAMEproduction-stackNombre del cluster de OKE GPU_SHAPEVM.GPU.A10.1Unidad de computación de GPU para el pool de nodos GPU_NODE_COUNT1Número de nodos de GPU en el pool GPU_BOOT_VOLUME_GB200Tamaño del volumen de inicio en GB para los nodos de GPU CPU_BOOT_VOLUME_GB100Tamaño del volumen de inicio en GB para los nodos de CPU GPU_AD_INDEX1Índice de dominio de disponibilidad (basado en 0) para colocación de GPU PRIVATE_CLUSTERtrueDefinido en falsepara un punto final de API de Kubernetes públicoKUBERNETES_VERSIONv1.31.10Versión de Kubernetes para el cluster de OKE Por ejemplo, para desplegar con dos nodos 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 las unidades de GPU disponibles y seleccione una según los requisitos de tamaño del modelo.
Forma GPU Tipo de GPU Memoria de GPU Recomendada 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 con modelos pequeños BM.GPU4.88 NVIDIA A100 40 GB 320 GB modelos 70B, rentables BM.GPU.A100-v2.88 NVIDIA A100 80 GB 640 GB Modelos de parámetros 70B+ BM.GPU.H100.88 NVIDIA H100 640 GB Modelos más grandes, soporte RDMA Nota: Las unidades con hardware dedicado (
BM.*) proporcionan hardware dedicado sin sobrecarga de virtualización y soportan el paralelismo de tensor de varias GPU. Las unidades de máquina virtual (VM.*) son más rentables para modelos más pequeños.Nota: En este tutorial se utiliza
VM.GPU.A10.1(NVIDIA única A10 con memoria de GPU de 24 GB) para desplegaropenai/gpt-oss-20b, un modelo de mezcla de expertos (MoE) con parámetros activos 3.6B que normalmente se ajusta a una única GPU A10. En las secciones avanzadas se muestran configuraciones de varias GPU medianteBM.GPU.H100.8para modelos más grandes, como Llama 3.1 70B.
Tarea 2: Despliegue mediante el script automatizado (inicio rápido)
La pila de producción de vLLM incluye un script de despliegue automatizado que aprovisiona todos los recursos de OCI y despliega la pila de inferencia con un único comando. Utilice este enfoque para un despliegue rápido. Las tareas 3 a 10 cubren cada paso individualmente para los usuarios que desean personalizar el proceso.
-
Clone el repositorio de la pila de producción de vLLM.
git clone https://github.com/vllm-project/production-stack.git cd production-stack/deployment_on_cloud/oci -
Exporte el OCID de su compartimento.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Ejecute el script de despliegue.
./entry_point.sh setup
Para clusters públicos (
PRIVATE_CLUSTER=false), la configuración crea toda la infraestructura y despliega la pila de vLLM en un único comando. Transfiera el archivo de valores de Helm como un segundo argumento:PRIVATE_CLUSTER=false ./entry_point.sh setup ./production_stack_specification.yamlPara los clusters privados (valor por defecto), la configuración crea la infraestructura, pero no puede acceder directamente a la API de Kubernetes. Abra un terminal independiente e inicie el túnel y, a continuación, despliegue:
# 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 que el despliegue se está ejecutando.
kubectl get podsSalida 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
Nota: Si ambos pods muestran el estado
Running, el despliegue está listo. Vaya a Tarea 10: probar el punto final de inferencia.
Nota: Las instancias de GPU están sujetas a restricciones de capacidad de OCI. Si el script permanece en el bucle "Waiting for GPU node" (Esperando nodo de GPU) durante más de 15 minutos, es posible que la unidad de GPU no esté disponible en el dominio de disponibilidad seleccionado. Compruebe el estado del pool de nodos con
oci ce node-pool gety busque los errores "Fuera de la capacidad del host". Para resolverlo, limpie con./entry_point.sh cleanupy vuelva a desplegar con un dominio de disponibilidad diferente (por ejemplo,GPU_AD_INDEX=0oGPU_AD_INDEX=2) o una unidad de GPU diferente (por ejemplo,GPU_SHAPE=VM.GPU.A10.2).
Nota: El script de despliegue utiliza instancias de GPU que incurren en costos significativos (~50 $/día para una única GPU A10). Ejecute siempre
./entry_point.sh cleanupcuando haya terminado para evitar cargos continuos.
Tarea 3: Creación de VCN y redes
Cree la infraestructura de red de OCI necesaria para el cluster de OKE. Esto incluye una red virtual en la nube (VCN), gateways, tablas de rutas, listas de seguridad y subredes. Cada recurso de red se crea en unos segundos; el conjunto completo de comandos se completa en menos de 2 minutos.
-
Cree una VCN con un bloque de 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) -
Cree un gateway de Internet para el enrutamiento de subred 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) -
Cree un gateway de NAT para el tráfico saliente desde subredes 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) -
Cree un gateway de servicio para acceder a Oracle Services Network. El controlador en la nube de OKE utiliza los servicios de Oracle para inicializar los nodos de trabajador (definir etiquetas de dominio de disponibilidad y eliminar las manchas de inicialización). Sin un gateway de servicio, los nodos de GPU pueden permanecer en un estado no inicializado y el aprovisionamiento de volúmenes en bloque fallará.
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) -
Cree tablas de rutas para subredes públicas y 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)Nota: La tabla de rutas privada tiene dos reglas: una ruta de gateway de NAT para el acceso general a Internet (extracción de imágenes de contenedor, descarga de modelos) y una ruta de gateway de servicio para el acceso directo a Oracle Services Network. La ruta del gateway de servicios es fundamental. Sin él, el controlador de nube de OKE no puede inicializar nodos de trabajador, lo que impide el aprovisionamiento de volúmenes en bloque. La tabla de rutas pública utiliza el gateway de Internet para el acceso al equilibrador de carga.
-
Cree una lista de seguridad con las reglas de entrada y salida necesarias para 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 seguridad: esta lista de seguridad de ejemplo es intencionalmente amplia por simplicidad. Para la producción, restrinja SSH a la subred bastión y al rango de IP, y prefiera listas de seguridad o NSG independientes por subred para que la subred del equilibrador de carga no permita SSH desde
0.0.0.0/0.Valor por defecto seguro: comience limitando SSH a su IP pública y asociando reglas SSH solo a la subred del bastión. Puede mantener los CIDR de pod/servicio de Kubernetes en la subred de trabajador y omitir SSH por completo de la subred del equilibrador de carga.
Dividido opcional (recomendado): cree una pequeña lista de seguridad solo SSH para la subred de bastión y una lista independiente para subredes de trabajador/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)Nota: Si solo utiliza un equilibrador de carga interno, sustituya los orígenes
0.0.0.0/0anteriores por10.0.0.0/16(o el CIDR de la VCN). Uso: asocieBASTION_SL_IDa la subred del bastión,WORKER_SL_IDa las subredes API/worker yLB_SL_IDa la subred del equilibrador de carga.Nota: Las reglas CIDR de los pods de Kubernetes (
10.244.0.0/16) y CIDR de servicios (10.96.0.0/16) son necesarias para que los nodos de trabajador de GPU se registren en el cluster. La regla de código 4 ICMP tipo 3 permite la detección de MTU de ruta, lo que evita problemas de fragmentación de paquetes. -
Cree las subredes. El cluster necesita cuatro subredes: una para el punto final de API de Kubernetes, otra para los nodos de trabajador, otra para los equilibradores de carga y otra para el host bastión utilizado para acceder al 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)Subred CIDR Visibilidad Finalidad Punto final de API 10.0.0.0/28Privada Servidor Kubernetes de API Nodos de trabajador 10.0.10.0/24Privada Nodos de cálculo de GPU Equilibradores de carga 10.0.20.0/24Pública Acceso a servicios externos Bastion 10.0.30.0/24Pública Túnel SSH para acceso de cluster privado
Tarea 4: Creación del cluster de OKE
Despliegue un cluster de Kubernetes gestionado en OKE mediante los recursos de red creados en la tarea 3. El cluster tarda aproximadamente 10 minutos en aprovisionarse. Este tutorial crea un cluster privado (valor por defecto del script), que no consume una IP pública reservada para el punto final de API de Kubernetes. Los clusters privados son el enfoque recomendado para las cargas de trabajo de producción porque el servidor de API no está expuesto a la red pública de Internet.
-
Cree el cluster de OKE con un punto 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 falseEl comando devuelve un ID de solicitud de trabajo. Obtenga el ID de cluster de la 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}"Nota: Los clusters privados no necesitan una IP pública reservada. Los nodos de trabajador siguen accediendo a Internet a través del gateway de NAT para extraer imágenes de contenedor y descargar modelos. Solo el acceso
kubectlrequiere un túnel SSH a través del bastión (configurado en los pasos siguientes). -
Espere a que el cluster se ACTIVE. Este paso tarda aproximadamente 10 minutos.
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputSondee el comando hasta que la salida devuelva
ACTIVE.Opcional: mostrar un resumen de estado conciso (incluido el punto final de API privado).
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
-
Cree un bastión de OCI para acceder al cluster privado. El bastión proporciona un túnel SSH gestionado al punto final de API de Kubernetes privado.
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)Nota: Sustituya
YOUR_PUBLIC_IP/32por la IP pública actual. Para redes compartidas, utilice su bloque CIDR corporativo en su lugar.Espere a que el bastión se ACTIVE (aproximadamente 1 minuto).
oci bastion bastion get \ --bastion-id "${BASTION_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputNota de seguridad: para producción, no utilice
0.0.0.0/0. Restrinja--client-cidr-lista su IP pública o CIDR corporativo (por ejemplo,"YOUR_PUBLIC_IP/32"); de lo contrario, cualquier usuario de Internet puede intentar una sesión de bastión. -
Descargue el kubeconfig mediante el punto 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 -
Obtenga la dirección IP de punto final privado para el 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}" -
Cree una sesión de reenvío de puerto de bastión. Necesitará un archivo de clave 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)Espere a que la sesión se ACTIVE y, a continuación, obtenga el comando SSH.
oci bastion session get \ --session-id "${SESSION_ID}" \ --query "data.{state:\"lifecycle-state\", ssh:\"ssh-metadata\".command}" 2>&1 -
Abra un terminal independiente e inicie el túnel SSH con el comando del paso anterior. El túnel reenvía el puerto local 6443 a la API de Kubernetes privada.
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.comNota: Sustituya
<PRIVATE_IP>,<SESSION_OCID>y<REGION>por los valores del paso anterior. Mantenga este terminal abierto durante la sesión. El indicador-o IdentitiesOnly=yesevita errores de "demasiados fallos de autenticación" cuando el agente SSH tiene varias claves cargadas. -
Actualice el kubeconfig para conectarse a través del 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=trueNota: El indicador
--insecure-skip-tls-verifyes necesario porque el certificado de cluster se ha emitido para la IP de punto final privado, no127.0.0.1. Esto es seguro porque el tráfico se cifra a través del túnel SSH. -
Si utiliza un perfil de la CLI de OCI no predeterminado (por ejemplo,
API_KEY_AUTH), actualice kubeconfig para utilizarlo. El valor por defecto de kubeconfig generado es el perfilDEFAULTpara la generación de token.kubectl config set-credentials \ $(kubectl config view --minify -o jsonpath='{.users[0].name}') \ --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}Consejo: los pasos 6 a 9 son automatizados por
./entry_point.sh tunnel, que también se vuelve a conectar automáticamente si el túnel SSH se cae durante operaciones de larga ejecución como la expansión del disco. Ejecútelo en un terminal independiente y déjelo en ejecución mientras dure la sesión. -
Verifique el acceso al cluster.
kubectl get nodes
En este punto, la salida no mostrará ningún nodo, ya que el pool de nodos de GPU aún no se ha agregado.
No resources found
Tarea 5: Agregar pool de nodos de GPU
Agregue un pool de nodos con instancias informáticas de GPU al cluster de OKE.
-
Busque la última imagen de nodo de OKE compatible con GPU. OKE requiere imágenes específicas con componentes de registro de nodos y kubelet preinstalados. Utilice la API
node-pool-optionspara buscar la imagen correcta para su versión de 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}"Nota: Los filtros de consulta para las imágenes de GPU de Oracle Linux 8.10 que coincidan con la versión de Kubernetes (por ejemplo,
OKE-1.31.10). Si necesita imágenes basadas en ARM, sustituya8.10por el filtro adecuado. -
Determine el dominio de disponibilidad que tiene unidades de GPU. No todos los dominios de disponibilidad tienen capacidad 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}"Nota: La capacidad de la GPU varía según la región y el dominio de disponibilidad. Si la creación del pool de nodos falla con un error de "Capacidad insuficiente del host", pruebe con un dominio de disponibilidad diferente (
GPU_AD_INDEX) o una unidad de GPU, o solicite capacidad mediante el proceso normal de OCI. -
Cree el pool de nodos de GPU con un volumen de inicio 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"}]'Nota: El gráfico de Helm de vLLM utiliza las etiquetas de nodo
app=gpuynvidia.com/gpu=truemás adelante para programar pods de inferencia en nodos de GPU. El volumen de inicio de 200 GB proporciona espacio para la imagen de contenedor vLLM (~10 GB) y los pesos del modelo, pero el sistema de archivos se debe ampliar antes de su uso (consulte la tarea 8). -
Espere a que los nodos de GPU estén listos. Esto suele tardar entre 5 y 10 minutos mientras el nodo aprovisiona, inicia, instala controladores de GPU y se registra con el cluster.
Nota: Las instancias de GPU están sujetas a restricciones de capacidad. Si el pool de nodos permanece en estado CREATING, compruebe el estado del nodo en la consola de OCI o con
oci ce node-pool get. Un error de "Capacidad insuficiente del host" significa que no hay instancias de GPU disponibles en ese dominio de disponibilidad. Para resolverlo, pruebe con un dominio de disponibilidad diferente (GPU_AD_INDEX=0oGPU_AD_INDEX=2), pruebe con una unidad de GPU diferente o solicite una reserva de capacidad a través de la consola de OCI o un ticket de soporte.kubectl get nodes -wSalida esperada una vez que el nodo está listo:
NAME STATUS ROLES AGE VERSION 10.0.10.x Ready node 5m v1.31.10 -
Verifique que la GPU se haya detectado en el nodo.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu'Salida esperada:
NAME GPUs 10.0.10.x 1 -
Parche CoreDNS para programar en nodos de GPU. Los nodos de GPU de OKE tienen un tinte
nvidia.com/gpu=present:NoSchedule. En los clusters que solo tienen nodos de GPU, los pods del sistema como CoreDNS no se pueden programar sin una tolerancia para este mantenimiento. Sin DNS, los pods no pueden resolver nombres de host externos para descargar 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 que CoreDNS se está ejecutando.
kubectl get pods -n kube-system | grep corednsNota: Si el cluster tiene un pool de nodos de CPU dedicado para las cargas de trabajo del sistema, este paso no es necesario. Este parche solo es necesario cuando los nodos de GPU son los únicos nodos del 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
Tarea 6: Instalación del plugin de dispositivo NVIDIA
Instale el plugin del dispositivo NVIDIA para que Kubernetes pueda detectar y programar cargas de trabajo en la GPU.
-
Aplique el plugin de dispositivo NVIDIA DaemonSet.
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml -
Espere a que los pods del plugin estén listos.
kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300sNota: Algunas imágenes de nodo de GPU de OKE incluyen un plugin de dispositivo NVIDIA preinstalado (
nvidia-gpu-device-plugin). Si la imagen ya la incluye, al aplicar el flujo ascendente DaemonSet se crea una segunda instancia que no causa conflictos. El script automatizado (entry_point.sh deploy-vllm) siempre lo instala para garantizar que la detección de GPU funcione independientemente de la versión de la imagen del nodo. -
Confirme que Kubernetes puede asignar la GPU.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'Salida esperada:
NAME GPUs 10.0.10.x 1 -
Aplique el parche CoreDNS para tolerar los contaminantes del nodo de GPU. En los clusters en los que los nodos de GPU son los únicos nodos de trabajador, los pods CoreDNS no se pueden programar porque los nodos de GPU de OKE llevan un mantenimiento de
nvidia.com/gpu=present:NoSchedule. Sin DNS, los pods no pueden resolver los registros de imágenes ni las URL de descarga 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 rollout status deployment/coredns -n kube-system --timeout=60sNota: Este paso solo es necesario cuando los nodos de GPU son los únicos nodos de trabajador del cluster. Si tiene un pool de nodos de CPU dedicado para las cargas de trabajo del sistema, CoreDNS programa allí por defecto y este parche no es necesario.
Tarea 7: Configuración del almacenamiento
Aplique el volumen en bloque de OCI StorageClass para proporcionar almacenamiento persistente para los pesos del modelo.
-
Aplique la definición StorageClass.
kubectl apply -f oci-block-storage-sc.yamlEl archivo define dos niveles de rendimiento:
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 Rendimiento Caso de uso oci-block-storage-encEquilibrado ( vpusPerGB: 10)Predeterminado y rentable para la mayoría de los modelos oci-block-storage-hpAlto rendimiento ( vpusPerGB: 20)Carga de modelos más rápida para modelos más grandes -
Verifique que StorageClasses esté disponible.
kubectl get storageclass
Nota: Para despliegues de varios nodos que requieren almacenamiento compartido en varios pods, utilice el servicio OCI File Storage (NFS) con el modo de acceso
ReadWriteManyen lugar de Block Volumes.
Tarea 8: Expansión del sistema de archivos del nodo de GPU
Los volúmenes de inicio de OCI tienen una partición fija de ~47 GB independientemente del tamaño del volumen de inicio que especifique. La imagen del contenedor vLLM es de aproximadamente 10 GB, y los pesos del modelo requieren espacio adicional. Debe ampliar el sistema de archivos antes de desplegar vLLM para evitar expulsiones de DiskPressure.
Nota: Este es un requisito específico de OCI. El volumen de inicio se aprovisiona a 200 GB, pero el sistema operativo solo particiona ~47 GB por defecto. El espacio restante se debe reclamar manualmente.
-
Verifique el tamaño actual del sistema de archivos en el nodo de 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\":\"/\"}}]}}"La salida mostrará aproximadamente 47 GB en total, lo que confirma que se necesita expansión.
-
Cree un pod con privilegios en el nodo de GPU para ejecutar los comandos de expansión.
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\":\"/\"}}]}}"Espere a que se inicie el pod.
kubectl wait --for=condition=Ready pod/expand-disk --timeout=60s -
Ejecute los cuatro pasos de expansión en un único comando
kubectl exec. Al ejecutarlos juntos, se evita el riesgo de quekubectl execdevuelva el código de salida 137 (SIGKILL) entre los pasos, lo que puede ocurrir durante la E/S de disco pesado en el 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 / 'Paso Comando Finalidad 1 growpart /dev/sda 3Amplíe la partición 3 para utilizar el disco completo 2 pvresize /dev/sda3Cambiar tamaño de volumen físico de LVM 3 lvextend -l +100%FREE /dev/ocivolume/rootAmpliar volumen lógico 4 xfs_growfs /Ampliar el sistema de archivos XFS para llenar el volumen Nota: Las cuatro operaciones son idempotentes. Si el exec devuelve el código de salida 137, puede volver a ejecutar el bloque completo de forma segura. Busque
EXPANSION_COMPLETEen la salida para confirmar que se ha realizado correctamente. -
Reinicie kubelet para que el nodo informe el almacenamiento asignable actualizado y, a continuación, verifique y limpie.
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 --forceNota: El comando
nsenterintroduce el espacio de nombres PID del host para acceder a systemd. Un archivochroot /host systemctl restart kubeletsin formato falla porque no se puede conectar al bus systemd desde un chroot.La salida esperada debe mostrar aproximadamente 189 GB en total.
Tarea 9: Despliegue de la pila de producción de vLLM
Instale la pila de inferencia de vLLM mediante Helm.
-
Agregue el repositorio de vLLM Helm.
helm repo add vllm https://vllm-project.github.io/production-stack helm repo update -
Revise el archivo de valores de Helm.
production_stack_specification.yamlconfigura el modelo, los recursos y el almacenamiento para 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"Nota: El modelo
openai/gpt-oss-20bes un modelo de mezcla de expertos (MoE) con parámetros totales 20B y parámetros activos 3.6B por transferencia directa. Se publica bajo la licencia Apache 2.0, por lo que no se requiere ningún token de Hugging Face. La imagen de contenedorvllm/vllm-openaiproporciona un servidor de API compatible con OpenAI, lo que permite a los clientes utilizar llamadas estándar del SDK OpenAI en el punto final autoalojado. -
Despliegue la pila. No utilice
--waitaquí porque el pod de enrutador CrashLoop hasta que se aplique el parche en el siguiente paso.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yamlEspere a que se inicie el pod del motor vLLM (el enrutador se aplicará a continuación).
kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600sNota: El pod del motor tarda varios minutos en estar listo porque descarga los pesos del modelo en el primer inicio. Si el pod permanece en
ContainerCreating, la imagen de contenedor (~10 GB) se sigue extrayendo. Utilicekubectl describe pod <pod-name>para comprobar el progreso. -
Aplique parches al despliegue del enrutador. El enrutador necesita una tolerancia de GPU (para que pueda programar cuando los nodos de GPU sean los únicos nodos con capacidad) y un aumento de los límites de memoria (los 500 Mi por defecto pueden 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"} ]'Nota: La tolerancia de GPU es necesaria porque los nodos de GPU de OKE tienen un mantenimiento
nvidia.com/gpu=present:NoScheduleque evita que las cargas de trabajo que no son de GPU se programen. Dado que el enrutador no utiliza una GPU, pero necesita ejecutarse en algún lugar, esta tolerancia le permite programar en nodos de GPU. En clusters con pools de nodos de CPU dedicados, esta tolerancia no es necesaria. -
Confirme que se ha desplegado la versión de Helm.
helm list
-
Verifique que los pods se están ejecutando.
kubectl get podsSalida 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
-
Compruebe el progreso de carga del modelo en los logs de pod.
kubectl logs -f deployment/vllm-gpt-oss-deployment-vllmEspere hasta que vea un mensaje que indique que el modelo se ha cargado y que el servidor está listo para aceptar solicitudes.
Tarea 10: Prueba del punto final de inferencia
Valide que el despliegue sirve solicitudes de inferencia. La pila de producción de vLLM expone una API compatible con OpenAI a través del servicio de enrutador, por lo que cualquier cliente SDK OpenAI o comando curl puede interactuar con ella.
El siguiente diagrama muestra el ciclo de vida de la solicitud de inferencia: desde la solicitud del cliente hasta la lógica de selección del motor del enrutador, hasta las fases de prelleno y descodificación del motor vLLM y viceversa como respuesta transmitida.

-
Muestre los modelos disponibles para confirmar que el despliegue está en buen estado.
kubectl get svc vllm-router-serviceEl servicio de enrutador proporciona el gateway de API para todos los modelos desplegados. Dado que el cluster utiliza un punto final privado, puede acceder al servicio mediante
kubectl port-forward. -
Inicie un reenvío de puerto desde la máquina local al servicio de enrutador. Abra un nuevo terminal (mantenga el túnel SSH en ejecución en el otro) y ejecute:
kubectl port-forward svc/vllm-router-service 8080:80Esto asigna
localhost:8080en el equipo al puerto 80 en el servicio de enrutador dentro del cluster.Nota de seguridad:
kubectl port-forwardse enlaza localmente y no expone el servicio públicamente. Esta es la forma más segura de realizar pruebas cuando se utiliza un cluster privado en un túnel bastión.Nota: El comando port-forward se ejecuta en primer plano. Mantenga este terminal abierto durante las pruebas. Pulse Ctrl+C para detenerlo cuando haya terminado.
-
En otro terminal, verifique que el modelo está disponible consultando el punto final de los modelos.
curl -s http://localhost:8080/v1/models | python3 -m json.toolSalida esperada:
{ "object": "list", "data": [ { "id": "openai/gpt-oss-20b", "object": "model", "created": 1234567890, "owned_by": "vllm" } ] } -
Enviar una solicitud de finalización 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.toolSalida esperada (abreviado):
{ "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 } } -
Enviar una solicitud de finalización de chat. Este es el mismo formato que utiliza el SDK de Python OpenAI y es la forma más común de interactuar con los LLM mediante programación.
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.toolSalida esperada (abreviado):
{ "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 respuestas incluyen el texto generado por el modelo en la matriz
choices, las estadísticas de uso de token y unfinish_reasondestop(el modelo finalizó naturalmente) olength(la salida se truncó enmax_tokens).Nota: El nombre de modelo de la solicitud de API es la ruta de modelo completa (
openai/gpt-oss-20b), que coincide con el campomodelURLen los valores de Helm. Cualquier cliente compatible con OpenAI puede utilizar este punto final definiendobase_urlenhttp://localhost:8080/v1.
-
Mida la latencia de extremo a extremo para una solicitud corta.
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/nullEn una única GPU A10, espere de 1 a 3 segundos de principio a fin para finalizaciones cortas. El tiempo hasta el primer token (TTFT) suele ser de 50-200 ms en función de la longitud de la petición de datos. Para obtener un mayor rendimiento, aumente
replicaCounten los valores de Helm para agregar más réplicas del motor detrás del enrutador.
Tarea 11: Exponer a través de OCI Load Balancer (opcional)
Haga que el punto final de inferencia sea accesible externamente a través de un equilibrador de carga de OCI.
Nota de seguridad: esto expone la API de inferencia a la red pública de Internet por defecto. No lo active en producción sin TLS, autenticación (clave de API/JWT/mTLS) y controles de lista de permitidos de IP o WAF. Si debe exponerlo, avíselo con un controlador de entrada o gateway de API que aplique límites de autenticación y velocidad, o bien utilice un equilibrador de carga interno.
-
Aplique parches al servicio de enrutador para que utilice un tipo LoadBalancer.
kubectl patch svc vllm-router-service \ -p '{"spec": {"type": "LoadBalancer"}}'Nota: Si desea un equilibrador de carga interno, agregue la anotación de OCI al servicio (ejemplo a continuación). Esto mantiene el punto final privado dentro de la VCN.
kubectl annotate svc vllm-router-service \ "service.beta.kubernetes.io/oci-load-balancer-internal"="true" -
Espere a que se asigne la IP externa.
kubectl get svc vllm-router-service -wSalida esperada una vez aprovisionado el equilibrador de carga:
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
-
Pruebe el punto 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 }'
Tarea 12: Configuración del paralelismo de tensores de varias GPU (avanzado)
Despliegue modelos más grandes en varias GPU en unidades con hardware dedicado.
El paralelismo de sensores divide un modelo en varias GPU en un solo nodo. Esto es necesario cuando los requisitos de memoria de un modelo superan una única GPU. Por ejemplo, Meta Llama 3.1 70B requiere aproximadamente 140 GB de memoria de GPU, lo que excede la capacidad de cualquier GPU única, pero cabe en GPU 8x A100 de 80 GB o 8x H100.
-
Cree un secreto de Kubernetes con su token Hugging Face. Los modelos cerrados como Llama 3.1 70B requieren autenticación.
kubectl create secret generic hf-token-secret \ --from-literal=token=YOUR_HUGGINGFACE_TOKEN -
Actualice
production_stack_specification.yamlcon una configuración de varias GPU.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" -
Desplegar con los valores actualizados. Al igual que con la tarea 9, no utilice
--wait. El enrutador CrashLoop hasta que se aplique el parche.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900sA continuación, aplique el parche al enrutador (igual que la tarea 9, paso 4) y 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 que el pod se está ejecutando y que todas las GPU están en uso.
kubectl get pods kubectl logs -f deployment/vllm-llama70b-deployment-vllm
Nota: Asegúrese de que el cluster de OKE tiene un pool de nodos con la unidad de GPU con hardware dedicado adecuada (por ejemplo,
BM.GPU.H100.8) antes de desplegar una configuración de varias GPU.
Tarea 13: Uso de OCI Object Storage para modelos (avanzado)
Cargue los pesos del modelo de OCI Object Storage en lugar de descargarlos de Hugging Face. Esto es útil para modelos privados, descargas más rápidas dentro de OCI o entornos sin acceso a Internet externo.
-
Cargue los pesos del modelo en un cubo de OCI Object Storage. Vaya a Almacenamiento > Almacenamiento de objetos en la consola de OCI y cree un cubo si aún no lo tiene.
-
Cree una URL de solicitud autenticada previamente (SAP) para el cubo. En la consola de OCI, seleccione el cubo, haga clic en Solicitudes autenticadas previamente y cree una nueva solicitud con acceso de lectura.
-
Actualice
production_stack_specification.yamlpara utilizar la URL de 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" -
Despliegue con los valores actualizados (sin
--wait). Consulte la tarea 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 las cargas del modelo desde Object Storage comprobando los logs de pod.
kubectl logs -f deployment/vllm-custom-model-deployment-vllm
Tarea 14: Limpiar recursos
Elimine todos los recursos desplegados para evitar cargos continuos.
-
Elimine los recursos de Kubernetes mediante el script de limpieza.
cd production-stack/deployment_on_cloud/oci ./clean_up.shEsto desinstala la versión de Helm, suprime todos los recursos PersistentVolumeClaims, PersistentVolumes y vLLM personalizados.
-
Suprima el cluster de OKE y todos los recursos de red de OCI.
./entry_point.sh cleanupEsto suprime los siguientes recursos en orden:
- Pool de nodos de GPU
- Cluster de OKE
- Host bastión (si se crea)
- Subredes (API, trabajador, equilibrador de carga, bastión)
- Listas de seguridad
- Gateway de servicios, gateway de NAT y gateway de Internet
- Tablas de rutas
- VCN
-
Verifique que todos los recursos se hayan eliminado en la consola de OCI en Developer Services > Kubernetes Clusters y Networking > Virtual Cloud Networks.
./entry_point.sh cleanup
Nota: Asegúrese de suprimir todos los recursos para evitar cargos en curso. Las instancias de GPU y los volúmenes en bloque generan costos incluso cuando están inactivos.
Siguiente paso
En este tutorial se desplegó una pila de inferencia funcional. Para las cargas de trabajo de producción, tenga en cuenta las siguientes mejoras:
- Supervisión: vLLM expone las métricas de Prometheus en
/metricsen cada pod del motor. Conecte una pila Prometheus + Grafana para realizar un seguimiento del tiempo hasta el primer token, los tokens por segundo, el uso de la caché de KV y la profundidad de la cola de solicitudes. - Escala automática: configure la escala automática de pod horizontal de Kubernetes con métricas personalizadas (profundidad de cola de solicitudes o utilización de GPU a través de DCGM) para escalar las réplicas del motor automáticamente bajo carga.
- Endurecimiento de la seguridad: restrinja
BASTION_CLIENT_CIDRa su rango de IP, agregue políticas de red de Kubernetes para aislar el espacio de nombres de inferencia y almacene credenciales de modelo en OCI Vault en lugar de secretos de Kubernetes. - Alta disponibilidad: distribuya los nodos de GPU entre varios dominios de disponibilidad y aumente
replicaCounten los valores de Helm para que el enrutador pueda realizar un failover entre réplicas del motor. - Optimización de costos: utilice
./entry_point.sh cleanupcuando no esté en uso. Para cargas de trabajo de desarrollo/prueba, considere las instancias de GPU preferentes. Supervise el uso de la GPU para ajustar el tamaño del pool de nodos. - Infraestructura como código: codifique este despliegue con el proveedor de Terraform de OCI o con OCI Resource Manager para una infraestructura repetible y controlada por versiones.
Enlaces relacionados
Acuses de recibo
- Autor: Federico Kamelhar (arquitecto principal sénior de IA de Agentic)
Más recursos de aprendizaje
Explore otros laboratorios en docs.oracle.com/learn o acceda a más contenido de aprendizaje gratuito en el canal YouTube de Oracle Learning. Además, visite education.oracle.com/learning-explorer para convertirse en un explorador de Oracle Learning.
Para obtener documentación sobre el producto, visite Oracle Help Center.
Deploy OpenAI vLLM Production Stack on Oracle Kubernetes Engine (OKE)
G50932-01