Remarques :
- Ce tutoriel nécessite l'accès à Oracle Cloud. Pour vous inscrire à un compte gratuit, reportez-vous à Introduction à Oracle Cloud Infrastructure Free Tier.
- Il utilise des exemples de valeur pour les informations d'identification, la location et les compartiments Oracle Cloud Infrastructure. Lorsque vous terminez votre atelier, remplacez ces valeurs par celles propres à votre environnement cloud.
Déployer la pile de production OpenAI vLLM sur Oracle Kubernetes Engine (OKE)
Introduction
Les entreprises qui adoptent de grands modèles de langage (LLM) pour les charges de travail de production sont confrontées à une décision d'infrastructure critique : faites confiance à des API d'inférence tierces ou déployez une pile d'inférence auto-hébergée. Les déploiements auto-hébergés offrent des avantages significatifs : un contrôle complet de la conformité et de la confidentialité des données, une latence d'inférence inférieure à 100 millisecondes en éliminant les allers-retours réseau, un coût prévisible à grande échelle et la liberté d'affiner et de servir tout modèle open source sans dépendance vis-à-vis d'un fournisseur.
Cependant, la création d'une pile d'inférence LLM de qualité production à partir de zéro est complexe. Elle nécessite une orchestration de conteneurs prenant en charge les GPU, un routage intelligent des demandes sur plusieurs répliques de modèles, un stockage persistant pour de grands poids de modèle et une surveillance continue, le tout intégré et exécuté de manière fiable.
Oracle Cloud Infrastructure propose plusieurs chemins pour l'inférence en IA. Le service OCI Generative AI offre une expérience entièrement gérée avec des clusters d'IA dédiés isolés de votre location, idéal pour les équipes qui souhaitent se lancer rapidement avec des modèles pris en charge. Ce tutoriel adopte une autre approche : déployer votre propre pile d'inférence sur OKE. Ce chemin est conçu pour les équipes qui ont besoin d'un contrôle précis sur les pilotes GPU, les versions CUDA, les configurations de modèle et les paramètres de service, ou les équipes qui entraînent et affinent les modèles personnalisés et souhaitent les servir directement. OCI fournit des instances de GPU Bare Metal avec des GPU NVIDIA A10, A100 et H100, connectés par un réseau de cluster RDMA à très faible latence, vous donnant le même niveau de contrôle matériel que sur site tout en bénéficiant de l'élasticité du cloud.
La pile de production vLLM résout la complexité de l'inférence auto-hébergée en fournissant une plate-forme open source native de Kubernetes basée sur vLLM, le moteur d'inférence à haut débit utilisé en production par des organisations telles que Meta, Mistral AI et IBM. Il offre un débit jusqu'à 24x supérieur à celui des structures de service standard grâce à une gestion efficace de la mémoire GPU et à une optimisation du cache KV. Associée aux formes de GPU OKE et OCI, vous bénéficiez d'une plate-forme d'inférence prête à l'emploi avec un réseau, un stockage et une sécurité de niveau entreprise. Les scripts de déploiement OCI utilisés dans ce tutoriel sont fournis et gérés dans le référentiel officiel de la pile de production vLLM.
Ce tutoriel vous explique comment déployer la pile de production vLLM sur OKE, du provisionnement de l'infrastructure à l'exécution de votre première demande d'inférence.
Remarque : ce tutoriel provisionne des ressources étape par étape à l'aide de l'interface de ligne de commande OCI pour vous aider à comprendre le flux complet des ressources cloud OCI requises pour un déploiement d'inférence de GPU. Pour les environnements de production, il est recommandé de codifier cette infrastructure à l'aide de Terraform ou d'OCI Resource Manager (Shepherd) pour les déploiements reproductibles et contrôlés par version.

Les services OCI suivants sont utilisés dans ce tutoriel :
| Service | Description |
|---|---|
| Moteur Oracle Kubernetes (OKE) | Cluster Kubernetes géré pour l'orchestration de conteneurs et la programmation de charges de travail GPU |
| OCI Compute (formes GPU) | Instances de GPU NVIDIA A10 (24 Go) et A100 (80 Go) pour l'inférence de modèle |
| Volumes de blocs OCI | Stockage persistant pour les poids de modèle avec des niveaux de performances configurables |
| Réseau cloud virtuel OCI | Infrastructure réseau comprenant des sous-réseaux, des passerelles et des listes de sécurité |
| Equilibreur de charge OCI | Accès externe aux adresses d'inférence |
| OCI Bastion | Tunnels SSH gérés pour l'accès au cluster privé |
| OCI Object Storage | Autre source de modèle utilisant des URL de demande pré-authentifiée |
Objectifs
Dans ce tutoriel, vous allez :
- Déployer un cluster OKE avec des pools de noeuds compatibles GPU à l'aide de l'interface de ligne de commande OCI
- Configurer le réseau OCI (VCN, sous-réseaux, passerelles) pour les charges de travail Kubernetes
- Installer et configurer le module d'extension de périphérique NVIDIA pour la planification de GPU
- Développer le système de fichiers du noeud GPU pour utiliser la capacité totale du volume d'initialisation
- Déployer la pile de production vLLM avec une adresse d'inférence compatible OpenAI
- Tester l'inférence LLM avec les demandes d'API par rapport au modèle OpenAI GPT-OSS-20B
- Configurer le parallélisme des tenseurs multi-GPU pour les modèles plus volumineux sur les formes Bare Metal
- Utiliser OCI Object Storage en tant que source de modèle alternative
- Nettoyez toutes les ressources OCI pour éviter les frais continus
Prérequis
- Un compte Oracle Cloud Infrastructure pour les opérations suivantes :
- Compartiment avec droits d'accès permettant de créer et de gérer des clusters OKE
- Quota de calcul de GPU pour la forme souhaitée (par exemple,
VM.GPU.A10.1ouBM.GPU.A100-v2.8). Si vous n'avez pas de quota de GPU, demandez-le via un ticket d'assistance - Autorisations permettant de créer des VCN, des sous-réseaux, des passerelles Internet et des équilibreurs de charge
- Accès aux volumes de blocs OCI pour le stockage persistant
- Accès à OCI Object Storage (pour la section de chargement de modèle avancé)
Remarque : les exemples de sortie et de capture d'écran de ce tutoriel utilisent us-chicago-1. Vous pouvez effectuer le déploiement dans n'importe quelle région prise en charge en définissant
OCI_REGION. La capacité des GPU varie en fonction de la région et du domaine de disponibilité. Vérifiez donc que la forme de GPU cible est disponible avant le déploiement. Vérifiez la disponibilité de la forme GPU par région et préparez-vous à essayer un autre domaine de disponibilité (GPU_AD_INDEX) si vous rencontrez des erreurs de capacité.
Remarque : ce tutoriel provisionne les ressources de GPU payantes (par exemple,
VM.GPU.A10.1). Il ne s'agit pas d'une charge globale OCI Always Free. Exécutez toujours les étapes de nettoyage lorsque vous avez terminé pour éviter les frais en cours.
-
Interface de ligne de commande OCI installée et configurée avec
oci setup config -
jq installé pour l'analyse JSON
-
kubectl installé
-
Helm installé
-
Une paire de clés SSH (par exemple,
~/.ssh/id_rsaet~/.ssh/id_rsa.pub) pour l'accès au bastion. Générez-en un avecssh-keygen -t rsa -b 4096si nécessaire -
Connaissance des concepts de Kubernetes (pods, services, déploiements, pools de noeuds)
Remarque : ce tutoriel déploie
openai/gpt-oss-20b, un modèle sous licence Apache 2.0 à partir de OpenAI. Aucun jeton Hugging Face n'est requis. Si vous souhaitez déployer des modèles sécurisés tels que Meta Llama 3.1, vous aurez besoin d'un compte Hugging Face avec un jeton d'API.
Tâche 1 : configurer les variables d'environnement
Définissez la configuration OCI requise avant de déployer l'infrastructure.
-
Recherchez l'OCID de compartiment dans la console OCI. Accédez à Identité et sécurité > Compartiments, puis cliquez sur le compartiment cible et copiez l'OCID.
oci iam compartment list --query 'data[].{name:name,id:id}' --output table
-
Exportez la variable d'environnement requise.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Vous pouvez éventuellement remplacer la configuration par défaut en définissant l'une des variables d'environnement suivantes.
Variable Par défaut Description OCI_REGIONus-ashburn-1Région OCI pour le déploiement OCI_PROFILEDEFAULTProfil de configuration OCI CLI CLUSTER_NAMEproduction-stackNom du cluster OKE GPU_SHAPEVM.GPU.A10.1Forme de calcul GPU pour le pool de noeuds GPU_NODE_COUNT1Nombre de noeuds GPU dans le pool GPU_BOOT_VOLUME_GB200Taille de volume d'initialisation (en Go) pour les noeuds GPU CPU_BOOT_VOLUME_GB100Taille de volume d'initialisation en Go pour les noeuds d'UC GPU_AD_INDEX1Index de domaine de disponibilité (basé sur 0) pour le placement de GPU PRIVATE_CLUSTERtrueDéfinir sur falsepour une adresse d'API Kubernetes publiqueKUBERNETES_VERSIONv1.31.10Version de Kubernetes pour le cluster OKE Par exemple, pour effectuer un déploiement avec deux noeuds GPU A100 :
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" export GPU_SHAPE="BM.GPU.A100-v2.8" export GPU_NODE_COUNT="2" -
Passez en revue les formes de GPU disponibles et sélectionnez-en une en fonction des exigences de taille de modèle.
Forme GPU Type de GPU Mémoire GPU Recommandée pour VM.GPU.A10.11 NVIDIA A10 24 GB Modèles de paramètre 7B–13B VM.GPU.A10.22 NVIDIA A10 48 GB Tenseur parallèle avec petits modèles BM.GPU4.88 NVIDIA A100 40 GB 320 GB 70B modèles, rentables BM.GPU.A100-v2.88 NVIDIA A100 80 GB 640 GB Modèles de paramètre 70B+ BM.GPU.H100.88 NVIDIA H100 640 GB Les plus grands modèles, prise en charge RDMA Remarque : les formes Bare Metal (
BM.*) fournissent du matériel dédié sans surcharge de virtualisation et prennent en charge le parallélisme des tenseurs multi-GPU. Les formes de machine virtuelle (VM.*) sont plus économiques pour les petits modèles.Remarque : ce tutoriel utilise
VM.GPU.A10.1(un seul NVIDIA A10 avec 24 Go de mémoire GPU) pour déployeropenai/gpt-oss-20b, un modèle Mixture of Experts (MoE) avec des paramètres actifs 3.6B qui tient généralement sur un seul GPU A10. Les sections avancées présentent les configurations multi-GPU à l'aide deBM.GPU.H100.8pour les modèles plus volumineux tels que Llama 3.1 70B.
Tâche 2 : déploiement à l'aide du script automatisé (démarrage rapide)
La pile de production vLLM inclut un script de déploiement automatisé qui provisionne toutes les ressources OCI et déploie la pile d'inférence avec une seule commande. Utilisez cette approche pour un déploiement rapide. Les tâches 3 à 10 couvrent chaque étape individuellement pour les utilisateurs qui souhaitent personnaliser le processus.
-
Clonez le référentiel vLLM Production Stack.
git clone https://github.com/vllm-project/production-stack.git cd production-stack/deployment_on_cloud/oci -
Exportez l'OCID de compartiment.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Exécutez le script de déploiement.
./entry_point.sh setup
Pour les clusters publics (
PRIVATE_CLUSTER=false), la configuration crée toute l'infrastructure et déploie la pile vLLM en une seule commande. Transmettez le fichier de valeurs Helm en tant que deuxième argument :PRIVATE_CLUSTER=false ./entry_point.sh setup ./production_stack_specification.yamlPour les clusters privés (valeur par défaut), la configuration crée l'infrastructure mais ne peut pas atteindre directement l'API Kubernetes. Ouvrez un terminal distinct et démarrez le tunnel, puis déployez :
# 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 -
Vérifiez que le déploiement est en cours d'exécution.
kubectl get podsSortie attendue :
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
Remarque : si les deux pods affichent le statut
Running, le déploiement est prêt. Passez à la tâche 10 : test de l'adresse d'inférence.
Remarque : les instances de GPU sont soumises à des contraintes de capacité OCI. Si le script reste dans la boucle "Waiting for GPU node" pendant plus de 15 minutes, la forme de GPU peut ne pas être disponible dans le domaine de disponibilité sélectionné. Vérifiez le statut du pool de noeuds avec
oci ce node-pool getet recherchez les erreurs "Out of host capacity". Pour résoudre ce problème, nettoyez-le avec./entry_point.sh cleanupet redéployez-le avec un autre domaine de disponibilité (par exemple,GPU_AD_INDEX=0ouGPU_AD_INDEX=2) ou une autre forme de GPU (par exemple,GPU_SHAPE=VM.GPU.A10.2).
Remarque : le script de déploiement utilise des instances de GPU qui entraînent des coûts importants (~50 $/jour pour un seul GPU A10). Exécutez toujours
./entry_point.sh cleanuplorsque vous avez terminé pour éviter les frais permanents.
Tâche 3 : créer un VCN et un réseau
Créez l'infrastructure réseau OCI requise pour le cluster OKE. Cela inclut un réseau cloud virtuel (VCN), des passerelles, des tables de routage, des listes de sécurité et des sous-réseaux. Chaque ressource réseau est créée en quelques secondes ; l'ensemble complet des commandes se termine en moins de 2 minutes.
-
Créez un VCN avec un bloc 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) -
Créez une passerelle Internet pour le routage de sous-réseau public.
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) -
Créez une passerelle NAT pour le trafic sortant à partir de sous-réseaux privés.
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) -
Créez une passerelle de service pour l'accès à Oracle Services Network. Le contrôleur de cloud OKE utilise les services Oracle pour initialiser les noeuds de processus actifs (définir des étiquettes de domaine de disponibilité, supprimer les taches d'initialisation). Sans passerelle de service, les noeuds GPU peuvent rester dans un état non initialisé et le provisionnement de volume de blocs échouera.
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) -
Créez des tables de routage pour les sous-réseaux publics et privés.
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)Remarque : la table de routage privée comporte deux règles : un routage de passerelle NAT pour un accès Internet général (extraction d'images de conteneur, téléchargement de modèles) et un routage de passerelle de service pour un accès direct à Oracle Services Network. Le routage de la passerelle de service est critique. Sans cela, le contrôleur de cloud OKE ne peut pas initialiser les noeuds de processus actifs, ce qui empêche le provisionnement des volumes de blocs. La table de routage publique utilise la passerelle Internet pour l'accès à l'équilibreur de charge.
-
Créez une liste de sécurité avec les règles entrantes et sortantes requises pour 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)Remarque sur la sécurité : cet exemple de liste de sécurité est intentionnellement étendu pour plus de simplicité. Pour la production, limitez SSH au sous-réseau de bastion et à votre plage d'adresses IP, et préférez des listes de sécurité ou des groupes de sécurité réseau distincts par sous-réseau afin que le sous-réseau d'équilibreur de charge n'autorise pas SSH à partir de
0.0.0.0/0.Valeur par défaut sécurisée : commencez par limiter SSH à votre adresse IP publique et par attacher des règles SSH uniquement au sous-réseau de bastion. Vous pouvez conserver les CIDR de pod/service Kubernetes sur le sous-réseau de processus actif et omettre entièrement SSH du sous-réseau d'équilibreur de charge.
Fractionnement facultatif (recommandé) : créez une petite liste de sécurité SSH uniquement pour le sous-réseau de bastion et une liste distincte pour les sous-réseaux de processus actifs/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)Remarque : si vous utilisez uniquement un équilibreur de charge interne, remplacez les sources
0.0.0.0/0ci-dessus par10.0.0.0/16(ou votre CIDR VCN). Utilisation : attachezBASTION_SL_IDau sous-réseau de bastion,WORKER_SL_IDaux sous-réseaux d'API/de travail etLB_SL_IDau sous-réseau d'équilibreur de charge.Remarque : les règles CIDR de pods Kubernetes (
10.244.0.0/16) et CIDR de services (10.96.0.0/16) sont requises pour que les noeuds de processus actif GPU s'inscrivent dans le cluster. La règle ICMP type 3 code 4 permet la découverte de MTU de chemin, ce qui empêche les problèmes de fragmentation des paquets. -
Créez les sous-réseaux. Le cluster requiert quatre sous-réseaux : un pour l'adresse d'API Kubernetes, un pour les noeuds de processus actif, un pour les équilibreurs de charge et un pour l'hôte de bastion utilisé pour accéder au cluster privé.
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)Sous-réseau CIDR Visibilité Description Adresse d'API 10.0.0.0/28Privé Serveur d'API Kubernetes Noeuds de processus actif 10.0.10.0/24Privé Noeuds de calcul GPU Equilibreurs de charge 10.0.20.0/24Public Accès au service externe Bastion 10.0.30.0/24Public Tunnel SSH pour l'accès au cluster privé
Tâche 4 : créer le cluster OKE
Déployez un cluster Kubernetes géré sur OKE à l'aide des ressources réseau créées dans la tâche 3. Le provisionnement du cluster prend environ 10 minutes. Ce tutoriel crée un cluster privé (valeur par défaut du script), qui n'utilise pas d'adresse IP publique réservée pour l'adresse d'API Kubernetes. Les clusters privés sont l'approche recommandée pour les charges de travail de production car le serveur d'API n'est pas exposé au réseau Internet public.
-
Créez le cluster OKE avec une adresse privée.
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 falseLa commande renvoie un ID de demande de travail. Obtenez l'ID de cluster dans la liste des 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}"Remarque : les clusters privés ne nécessitent pas d'adresse IP publique réservée. Les noeuds de processus actif accèdent toujours à Internet via la passerelle NAT pour extraire des images de conteneur et télécharger des modèles. Seul l'accès
kubectlnécessite un tunnel SSH via le bastion (configuré dans les étapes suivantes). -
Attendez que le cluster devienne actif. Cette étape prend environ 10 minutes.
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputInterrogez la commande jusqu'à ce que la sortie renvoie
ACTIVE.Facultatif : affichez un récapitulatif concis des statuts (y compris l'adresse d'API privée).
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
-
Créez un bastion OCI pour accéder au cluster privé. Le bastion fournit un tunnel SSH géré vers l'adresse d'API Kubernetes privée.
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)Remarque : remplacez
YOUR_PUBLIC_IP/32par l'adresse IP publique en cours. Pour les réseaux partagés, utilisez plutôt le bloc CIDR de votre entreprise.Attendez que le bastion devienne ACTIF (environ 1 minute).
oci bastion bastion get \ --bastion-id "${BASTION_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputRemarque relative à la sécurité : pour la production, n'utilisez pas
0.0.0.0/0. Restreignez--client-cidr-listà votre adresse IP publique ou à votre CIDR d'entreprise (par exemple,"YOUR_PUBLIC_IP/32"), sinon toute personne sur Internet peut tenter une session de bastion. -
Téléchargez kubeconfig à l'aide de l'adresse privée.
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 -
Obtenez l'adresse IP de l'adresse privée pour le tunnel 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}" -
Créez une session de transfert de port de bastion. Vous aurez besoin d'un fichier de clés publiques 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)Attendez que la session devienne ACTIVE, puis obtenez la commande SSH.
oci bastion session get \ --session-id "${SESSION_ID}" \ --query "data.{state:\"lifecycle-state\", ssh:\"ssh-metadata\".command}" 2>&1 -
Ouvrez un terminal distinct et démarrez le tunnel SSH à l'aide de la commande de l'étape précédente. Le tunnel transfère le port local 6443 vers l'API Kubernetes privée.
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.comRemarque : remplacez
<PRIVATE_IP>,<SESSION_OCID>et<REGION>par les valeurs de l'étape précédente. Gardez ce terminal ouvert pendant toute la durée de votre session. L'indicateur-o IdentitiesOnly=yesempêche les erreurs "trop d'échecs d'authentification" lorsque plusieurs clés sont chargées pour l'agent SSH. -
Mettez à jour le kubeconfig pour qu'il se connecte via le tunnel 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=trueRemarque : l'indicateur
--insecure-skip-tls-verifyest requis car le certificat de cluster a été émis pour l'adresse IP de l'adresse privée, et non pour127.0.0.1. Ceci est sûr car le trafic est chiffré via le tunnel SSH. -
Si vous utilisez un profil d'interface de ligne de commande OCI autre que celui par défaut (par exemple,
API_KEY_AUTH), mettez à jour le fichier kubeconfig pour l'utiliser. Le profil kubeconfig généré est par défaut le profilDEFAULTpour la génération de jeton.kubectl config set-credentials \ $(kubectl config view --minify -o jsonpath='{.users[0].name}') \ --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}A savoir : Les étapes 6 à 9 sont automatisées par
./entry_point.sh tunnel, qui se connecte également automatiquement si le tunnel SSH est abandonné lors d'opérations à longue durée d'exécution telles que l'extension de disque. Exécutez-le dans un terminal distinct et laissez-le fonctionner pendant toute la durée de votre session. -
Vérifiez l'accès au cluster.
kubectl get nodes
A ce stade, la sortie n'affichera aucun noeud, car le pool de noeuds GPU n'a pas encore été ajouté.
No resources found
Tâche 5 : ajouter un pool de noeuds GPU
Ajoutez un pool de noeuds avec des instances de calcul GPU au cluster OKE.
-
Recherchez la dernière image de noeud OKE compatible GPU. OKE requiert des images spécifiques avec des composants d'enregistrement de kubelet et de noeud préinstallés. Utilisez l'API
node-pool-optionspour trouver l'image correcte pour votre version 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}"Remarque : les filtres de requête pour les images de GPU Oracle Linux 8.10 correspondant à votre version de Kubernetes (par exemple,
OKE-1.31.10). Si vous avez besoin d'images basées sur ARM, remplacez8.10par le filtre approprié. -
Déterminez le domaine de disponibilité qui comporte des formes GPU. Tous les domaines de disponibilité n'ont pas de capacité GPU.
AD=$(oci iam availability-domain list \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --query "data[${GPU_AD_INDEX}].name" \ --raw-output) echo "Availability Domain: ${AD}"Remarque : la capacité des GPU varie selon la région et le domaine de disponibilité. Si la création du pool de noeuds échoue avec une erreur "Out of host capacity", essayez un autre domaine de disponibilité (
GPU_AD_INDEX) ou une autre forme de GPU, ou demandez de la capacité via le processus OCI normal. -
Créez le pool de noeuds GPU avec un volume d'initialisation de 200 Go.
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"}]'Remarque : les libellés de noeud
app=gpuetnvidia.com/gpu=truesont utilisés ultérieurement par le graphique Helm vLLM pour programmer des pods d'inférence sur des noeuds GPU. Le volume d'initialisation de 200 Go fournit de l'espace pour l'image de conteneur vLLM (~10 Go) et les poids de modèle, mais le système de fichiers doit être étendu avant utilisation (voir la tâche 8). -
Attendez que les noeuds GPU deviennent prêts. Cela prend généralement entre 5 et 10 minutes lorsque le noeud provisionne, initialise, installe les pilotes GPU et s'inscrit dans le cluster.
Remarque : les instances de GPU sont soumises à des contraintes de capacité. Si le pool de noeuds reste à l'état CREATING, vérifiez le statut du noeud dans la console OCI ou avec
oci ce node-pool get. Une erreur "Out of host capacity" signifie qu'aucune instance GPU n'est disponible dans ce domaine de disponibilité. Pour résoudre ce problème, essayez un autre domaine de disponibilité (GPU_AD_INDEX=0ouGPU_AD_INDEX=2), essayez une autre forme de GPU ou demandez une réservation de capacité via la console OCI ou un ticket d'assistance.kubectl get nodes -wSortie attendue une fois le noeud prêt :
NAME STATUS ROLES AGE VERSION 10.0.10.x Ready node 5m v1.31.10 -
Vérifiez que le GPU est détecté sur le noeud.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu'Sortie attendue :
NAME GPUs 10.0.10.x 1 -
Appliquez un patch à CoreDNS pour programmer les noeuds GPU. Les noeuds de GPU OKE ont une tache
nvidia.com/gpu=present:NoSchedule. Dans les clusters qui n'ont que des noeuds GPU, les pods système tels que CoreDNS ne peuvent pas programmer sans tolérance pour cette tache. Sans DNS, les pods ne peuvent pas résoudre les noms d'hôte externes pour télécharger des modèles.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"}}]'Vérifiez que CoreDNS est en cours d'exécution.
kubectl get pods -n kube-system | grep corednsRemarque : si votre cluster dispose d'un pool de noeuds CPU dédié pour les charges globales système, cette étape n'est pas nécessaire. Ce patch n'est nécessaire que lorsque les noeuds GPU sont les seuls noeuds du 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
Tâche 6 : installation du plug-in de périphérique NVIDIA
Installez le module d'extension de périphérique NVIDIA afin que Kubernetes puisse détecter et programmer des charges de travail sur le GPU.
-
Appliquez le plug-in de périphérique NVIDIA DaemonSet.
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml -
Attendez que les pods de plugin soient prêts.
kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300sRemarque : certaines images de noeud de GPU OKE incluent un module d'extension de périphérique NVIDIA préinstallé (
nvidia-gpu-device-plugin). Si l'image l'inclut déjà, l'application de l'élément DaemonSet en amont crée une deuxième instance qui n'entraîne pas de conflits. Le script automatisé (entry_point.sh deploy-vllm) l'installe toujours pour s'assurer que la détection de GPU fonctionne quelle que soit la version de l'image du noeud. -
Vérifiez que le GPU est allouable par Kubernetes.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'Sortie attendue :
NAME GPUs 10.0.10.x 1 -
Appliquez un patch à CoreDNS pour tolérer les taches de noeud de GPU. Dans les clusters où les noeuds de GPU sont les seuls noeuds de processus actif, les pods CoreDNS ne peuvent pas programmer car les noeuds de GPU OKE comportent une tache
nvidia.com/gpu=present:NoSchedule. Sans DNS, les pods ne peuvent pas résoudre les registres d'images ou les URL de téléchargement de modèle.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=60sRemarque : cette étape n'est nécessaire que lorsque les noeuds GPU sont les seuls noeuds de processus actif du cluster. Si vous disposez d'un pool de noeuds de CPU dédié pour les charges globales système, CoreDNS y planifie par défaut et ce patch n'est pas nécessaire.
Tâche 7 : configurer le stockage
Appliquez le volume de blocs OCI StorageClass pour fournir un stockage persistant pour les poids de modèle.
-
Appliquez la définition StorageClass.
kubectl apply -f oci-block-storage-sc.yamlLe fichier définit deux niveaux de performances :
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 Les performances Cas d'emploi oci-block-storage-encEquilibré ( vpusPerGB: 10)Par défaut, économique pour la plupart des modèles oci-block-storage-hpHautes performances ( vpusPerGB: 20)Chargement plus rapide des modèles pour les modèles plus grands -
Vérifiez que StorageClasses est disponible.
kubectl get storageclass
Remarque : pour les déploiements à plusieurs noeuds nécessitant un stockage partagé sur plusieurs pods, utilisez OCI File Storage Service (NFS) avec le mode d'accès
ReadWriteManyau lieu de Block Volumes.
Tâche 8 : développer le système de fichiers du noeud GPU
Les volumes d'initialisation OCI disposent d'une partition fixe d'environ 47 Go, quelle que soit la taille de volume d'initialisation indiquée. L'image de conteneur vLLM seule est d'environ 10 Go et le poids du modèle nécessite de l'espace supplémentaire. Vous devez développer le système de fichiers avant de déployer vLLM pour éviter les expulsions DiskPressure.
Remarque : il s'agit d'une exigence propre à OCI. Le volume d'initialisation est provisionné à 200 Go, mais le système d'exploitation ne partitionne que ~47 Go par défaut. L'espace restant doit être réclamé manuellement.
-
Vérifiez la taille actuelle du système de fichiers sur le noeud 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 sortie affiche environ 47 Go au total, confirmant que l'extension est nécessaire.
-
Créez un pod privilégié sur le noeud GPU pour exécuter les commandes d'extension.
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\":\"/\"}}]}}"Attendez que le pod démarre.
kubectl wait --for=condition=Ready pod/expand-disk --timeout=60s -
Exécutez les quatre étapes d'extension en une seule commande
kubectl exec. Les exécuter ensemble évite le risque quekubectl execrenvoie le code de sortie 137 (SIGKILL) entre les étapes, ce qui peut se produire lors des E/S de disque lourdes sur l'hôte.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 / 'Étape Commande Description 1 growpart /dev/sda 3Développer la partition 3 pour utiliser le disque complet 2 pvresize /dev/sda3Redimensionner le volume physique LVM 3 lvextend -l +100%FREE /dev/ocivolume/rootEtendre le volume logique 4 xfs_growfs /Développer le système de fichiers XFS pour remplir le volume Remarque : les quatre opérations sont idempotentes. Si l'exécutable renvoie le code de sortie 137, vous pouvez réexécuter le bloc entier en toute sécurité. Recherchez
EXPANSION_COMPLETEdans la sortie pour confirmer la réussite. -
Redémarrez le kubelet pour que le noeud signale le stockage allouable mis à jour, puis vérifiez et nettoyez.
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 --forceRemarque : la commande
nsenterentre l'espace de noms PID de l'hôte pour accéder à systemd. Un élémentchroot /host systemctl restart kubeletbrut échoue car il ne peut pas se connecter au bus systemd à partir d'un chroot.La sortie attendue devrait afficher un total d'environ 189 Go.
Tâche 9 : déployer la pile de production vLLM
Installez la pile d'inférence vLLM à l'aide de Helm.
-
Ajoutez le référentiel Helm vLLM.
helm repo add vllm https://vllm-project.github.io/production-stack helm repo update -
Consultez le fichier de valeurs Helm.
production_stack_specification.yamlconfigure le modèle, les ressources et le stockage pour 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"Remarque : le modèle
openai/gpt-oss-20best un modèle de mélange d'experts (MoE) avec des paramètres totaux 20B et des paramètres actifs 3.6B par passe aval. Il est publié sous la licence Apache 2.0, de sorte qu'aucun jeton Hugging Face n'est requis. L'image de conteneurvllm/vllm-openaifournit un serveur d'API compatible OpenAI, permettant aux clients d'utiliser des appels de kit SDK OpenAI standard sur l'adresse auto-hébergée. -
Déployez la pile. N'utilisez pas
--waitici car le pod de routeur CrashLoop jusqu'à ce que des patches soient appliqués à l'étape suivante.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yamlAttendez que le pod du moteur vLLM démarre (le routeur sera ensuite corrigé).
kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600sRemarque : le pod de moteur met plusieurs minutes à être prêt car il télécharge les poids du modèle au premier démarrage. Si le pod reste dans
ContainerCreating, l'image de conteneur (~10 Go) est toujours extraite. Utilisezkubectl describe pod <pod-name>pour vérifier la progression. -
Appliquez un patch au déploiement du routeur. Le routeur a besoin d'une tolérance GPU (de sorte qu'il peut planifier lorsque les noeuds GPU sont les seuls noeuds ayant une capacité) et d'une augmentation des limites de mémoire (la valeur par défaut de 500 Mi peut entraîner 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"} ]'Remarque : la tolérance de GPU est nécessaire car les noeuds de GPU OKE ont une tache
nvidia.com/gpu=present:NoSchedulequi empêche la planification des charges globales non GPU. Comme le routeur n'utilise pas de GPU mais doit s'exécuter quelque part, cette tolérance lui permet de programmer sur les noeuds de GPU. Dans les clusters avec des pools de noeuds de CPU dédiés, cette tolérance n'est pas nécessaire. -
Vérifiez que la version Helm est déployée.
helm list
-
Vérifiez que les pods sont en cours d'exécution.
kubectl get podsSortie attendue :
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
-
Vérifiez la progression du chargement du modèle dans les journaux de pod.
kubectl logs -f deployment/vllm-gpt-oss-deployment-vllmAttendez qu'un message indiquant que le modèle a été chargé et que le serveur est prêt à accepter les demandes s'affiche.
Tâche 10 : test de l'adresse d'inférence
Vérifiez que le déploiement traite les demandes d'inférence. La pile de production vLLM expose une API compatible OpenAI via le service de routeur, de sorte que tout client SDK OpenAI ou toute commande curl peut interagir avec elle.
Le diagramme suivant illustre le cycle de vie de la demande d'inférence : de la demande client à la logique de sélection du moteur du routeur, en passant par les phases de préremplissage et de décodage du moteur vLLM et en retour en tant que réponse en flux.

-
Répertoriez les modèles disponibles pour vérifier que le déploiement est en bon état.
kubectl get svc vllm-router-serviceLe service de routeur fournit la passerelle d'API à tous les modèles déployés. Le cluster utilisant une adresse privée, vous accédez au service via
kubectl port-forward. -
Démarrez un transfert de port de votre machine locale vers le service de routeur. Ouvrez un nouveau terminal (gardez le tunnel SSH en cours d'exécution dans l'autre) et exécutez :
kubectl port-forward svc/vllm-router-service 8080:80Cette opération met en correspondance
localhost:8080sur votre ordinateur avec le port 80 sur le service de routeur à l'intérieur du cluster.Remarque de sécurité :
kubectl port-forwardétablit une liaison locale et n'expose pas le service publiquement. Il s'agit du moyen le plus sûr de tester l'utilisation d'un cluster privé sur un tunnel de bastion.Remarque : la commande port-forward s'exécute au premier plan. Gardez ce terminal ouvert pendant le test. Appuyez sur Ctrl+C pour l'arrêter lorsque vous avez terminé.
-
Dans un autre terminal, vérifiez que le modèle est disponible en interrogeant l'adresse des modèles.
curl -s http://localhost:8080/v1/models | python3 -m json.toolSortie attendue :
{ "object": "list", "data": [ { "id": "openai/gpt-oss-20b", "object": "model", "created": 1234567890, "owned_by": "vllm" } ] } -
Envoyer une demande d'achèvement de texte.
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.toolRésultat attendu (abrégé) :
{ "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 } } -
Envoyer une demande de fin de discussion. Il s'agit du même format que celui utilisé par le kit SDK Python OpenAI. Il s'agit du moyen le plus courant d'interagir par programmation avec les LLM.
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.toolRésultat attendu (abrégé) :
{ "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 } }Les deux réponses incluent le texte généré par le modèle dans le tableau
choices, les statistiques d'utilisation de jeton et une valeurfinish_reasondestop(le modèle s'est terminé naturellement) oulength(la sortie a été tronquée àmax_tokens).Remarque : le nom de modèle dans la demande d'API est le chemin de modèle complet (
openai/gpt-oss-20b), qui correspond au champmodelURLdans les valeurs Helm. Tout client compatible OpenAI peut utiliser cette adresse en définissantbase_urlsurhttp://localhost:8080/v1.
-
Mesurez la latence de bout en bout pour une courte demande.
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/nullSur un seul GPU A10, attendez-vous à 1 à 3 secondes de bout en bout pour les fins de production courtes. Le délai de réception du premier jeton (TTFT) est généralement compris entre 50 et 200 ms en fonction de la longueur de l'invite. Pour un débit plus élevé, augmentez
replicaCountdans les valeurs Helm pour ajouter d'autres répliques de moteur derrière le routeur.
Tâche 11 : affichage via l'équilibreur de charge OCI (facultatif)
Rendez l'adresse d'inférence accessible en externe via un équilibreur de charge OCI.
Remarque sur la sécurité : par défaut, l'API d'inférence est exposée au réseau Internet public. Ne l'activez pas en production sans contrôle TLS, authentification (clé d'API/JWT/mTLS) et liste d'autorisation IP ou WAF. Si vous devez l'exposer, placez-le devant un contrôleur entrant ou une passerelle d'API qui applique des limites d'authentification et de débit, ou utilisez un équilibreur de charge interne.
-
Appliquez un patch au service de routeur pour qu'il utilise un type LoadBalancer.
kubectl patch svc vllm-router-service \ -p '{"spec": {"type": "LoadBalancer"}}'Remarque : si vous voulez un équilibreur de charge interne, ajoutez l'annotation OCI au service (exemple ci-dessous). L'adresse reste ainsi privée dans le VCN.
kubectl annotate svc vllm-router-service \ "service.beta.kubernetes.io/oci-load-balancer-internal"="true" -
Attendez que l'adresse IP externe soit affectée.
kubectl get svc vllm-router-service -wRésultat attendu une fois l'équilibreur de charge provisionné :
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
-
Testez l'adresse externe.
curl http://<EXTERNAL-IP>/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-oss-20b", "prompt": "Hello from OCI!", "max_tokens": 50 }'
Tâche 12 : configurer le parallélisme de capteur multi-GPU (avancé)
Déployez des modèles plus importants sur plusieurs GPU sur des formes Bare Metal.
Le parallélisme de Tensor divise un modèle en plusieurs GPU sur un seul noeud. Cela est nécessaire lorsque les besoins en mémoire d'un modèle dépassent un seul GPU. Par exemple, Meta Llama 3.1 70B nécessite environ 140 Go de mémoire GPU, ce qui dépasse la capacité d'un seul GPU, mais s'adapte aux GPU 8x A100 80 Go ou 8x H100.
-
Créez une clé secrète Kubernetes avec votre jeton Hugging Face. Les modèles fermés tels que Llama 3.1 70B nécessitent une authentification.
kubectl create secret generic hf-token-secret \ --from-literal=token=YOUR_HUGGINGFACE_TOKEN -
Mettez à jour
production_stack_specification.yamlavec une configuration multi-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" -
Déployer avec les valeurs mises à jour. Comme pour la tâche 9, n'utilisez pas
--wait. Le routeur CrashLoop jusqu'à l'application du patch.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900sEnsuite, appliquez un patch au routeur (identique à la tâche 9, étape 4) et vérifiez :
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"} ]' -
Vérifiez que le pod est en cours d'exécution et que tous les GPU sont en cours d'utilisation.
kubectl get pods kubectl logs -f deployment/vllm-llama70b-deployment-vllm
Remarque : assurez-vous que votre cluster OKE dispose d'un pool de noeuds avec la forme de GPU Bare Metal appropriée (par exemple,
BM.GPU.H100.8) avant de déployer une configuration multiGPU.
Tâche 13 : utiliser OCI Object Storage pour les modèles (avancé)
Chargez les poids des modèles à partir d'OCI Object Storage au lieu de les télécharger à partir de Hugging Face. Cela est utile pour les modèles privés, les téléchargements plus rapides dans OCI ou les environnements sans accès Internet externe.
-
Téléchargez vos poids de modèle vers un bucket OCI Object Storage. Accédez à Stockage > Object Storage dans la console OCI et créez un bucket si vous n'en disposez pas déjà.
-
Créez une URL de demande pré-authentifiée pour votre bucket. Dans la console OCI, sélectionnez votre bucket, cliquez sur Demandes pré-authentifiées et créez une demande avec accès en lecture.
-
Mettez à jour
production_stack_specification.yamlpour utiliser l'URL de demande pré-authentifiée.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" -
Déployez avec les valeurs mises à jour (sans
--wait). Voir la tâche 9 pour savoir pourquoi).helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=custom-model --timeout=600s -
Vérifiez que le modèle est chargé à partir d'Object Storage en consultant les journaux de pod.
kubectl logs -f deployment/vllm-custom-model-deployment-vllm
Tâche 14 : Nettoyer les ressources
Supprimez toutes les ressources déployées pour éviter les frais continus.
-
Enlevez les ressources Kubernetes à l'aide du script de nettoyage.
cd production-stack/deployment_on_cloud/oci ./clean_up.shCette action désinstalle la version Helm, supprime toutes les ressources PersistentVolumeClaims, PersistentVolumes et vLLM personnalisées.
-
Supprimez le cluster OKE et toutes les ressources réseau OCI.
./entry_point.sh cleanupLes ressources suivantes sont supprimées dans l'ordre :
- Pool de noeuds GPU
- Cluster OKE
- Hôte bastion (si créé)
- Sous-réseaux (API, processus actif, équilibreur de charge, bastion)
- Listes de sécurité
- Passerelle de service, passerelle NAT et passerelle Internet
- Tables de routage
- VCN
-
Vérifiez que toutes les ressources ont été enlevées dans la console OCI sous Services de développeur > Clusters Kubernetes et Networking > Réseaux cloud virtuels.
./entry_point.sh cleanup
Remarque : assurez-vous que toutes les ressources sont supprimées pour éviter les frais continus. Les instances de GPU et les volumes de blocs entraînent des coûts, même en cas d'inactivité.
Etapes suivantes
Ce tutoriel a déployé une pile d'inférence fonctionnelle. Pour les charges de travail de production, envisagez les améliorations suivantes :
- Surveillance : vLLM expose les mesures Prometheus à l'adresse
/metricssur chaque pod de moteur. Connectez une pile Prometheus + Grafana pour suivre le temps passé au premier jeton, aux jetons par seconde, à l'utilisation du cache KV et à la profondeur de la file d'attente des demandes. - Redimensionnement automatique : configurez l'outil de redimensionnement automatique de pod horizontal Kubernetes avec des mesures personnalisées (profondeur de file d'attente de demande ou utilisation de GPU via DCGM) pour redimensionner automatiquement les répliques de moteur sous charge.
- Sécurisation de la sécurité : limitez
BASTION_CLIENT_CIDRà la plage d'adresses IP, ajoutez des stratégies réseau Kubernetes pour isoler l'espace de noms d'inférence et stockez les informations d'identification de modèle dans OCI Vault au lieu des clés secrètes Kubernetes. - Haute disponibilité : répartissez les noeuds de GPU sur plusieurs domaines de disponibilité et augmentez
replicaCountdans les valeurs Helm afin que le routeur puisse basculer entre les répliques de moteur. - Optimisation des coûts : utilisez
./entry_point.sh cleanuplorsqu'il n'est pas utilisé. Pour les charges de travail de développement/test, envisagez des instances de GPU préemptives. Surveillez l'utilisation des GPU pour adapter le pool de noeuds. - Infrastructure-as-Code : codifiez ce déploiement avec le fournisseur OCI Terraform ou OCI Resource Manager pour une infrastructure reproductible contrôlée par version.
Liens connexes
Accusés de réception
- Auteur - Federico Kamelhar (architecte principal senior, IA agénétique)
Ressources de formation supplémentaires
Explorez d'autres ateliers sur le site docs.oracle.com/learn ou accédez à d'autres contenus d'apprentissage gratuits sur le canal Oracle Learning YouTube. En outre, visitez le site education.oracle.com/learning-explorer pour devenir un explorateur Oracle Learning.
Pour obtenir de la documentation sur le produit, consultez Oracle Help Center.
Deploy OpenAI vLLM Production Stack on Oracle Kubernetes Engine (OKE)
G50934-01