Note :
- Ce tutoriel nécessite l'accès à Oracle Cloud. Pour vous inscrire à un compte gratuit, voir Démarrer avec le niveau gratuit d'Oracle Cloud Infrastructure.
- Il utilise des exemples de valeurs pour les données d'identification, la location et les compartiments d'Oracle Cloud Infrastructure. À la fin de votre laboratoire, remplacez ces valeurs par celles qui sont propres à votre environnement en nuage.
Déployer l'infrastructure de production vLLM OpenAI sur Oracle Kubernetes Engine (OKE)
Présentation
Les entreprises qui adoptent de grands modèles de langage (LLM) pour des 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 d'importants avantages : un contrôle complet de la conformité et de la confidentialité des données, une latence d'inférence de moins de 100 millisecondes en éliminant les allers-retours réseau, des coûts prévisibles à grande échelle et la liberté d'ajuster et de servir n'importe quel modèle à code source libre sans être lié à un fournisseur.
Toutefois, la création d'une pile d'inférence de grand modèle de production à partir de zéro est complexe. Il nécessite une orchestration de conteneurs sensible au GPU, un acheminement intelligent des demandes sur plusieurs répliques de modèle, un stockage persistant pour les poids importants du modèle et une surveillance continue, le tout intégré et fonctionnant de manière fiable.
Oracle Cloud Infrastructure offre plusieurs chemins pour l'inférence liée à l'IA. Le service IA générative d'OCI offre une expérience entièrement gérée avec des grappes d'IA dédiées isolées de votre location, idéale pour les équipes qui souhaitent démarrer rapidement avec les modèles pris en charge. Ce tutoriel adopte l'approche alternative suivante : 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èles et les paramètres de service, ou les équipes qui forment et peaufinent des modèles personnalisés et veulent les servir directement. OCI fournit des instances de processeur graphique sans système d'exploitation avec des processeurs graphiques NVIDIA A10, A100 et H100, connectés par un réseau en grappe RDMA à très faible latence, ce qui vous donne le même niveau de contrôle matériel que celui sur place tout en bénéficiant de l'élasticité du nuage.
La pile de production vLLM résout la complexité de l'inférence auto-hébergée en fournissant une plate-forme à code source libre 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 plus élevé que les cadres de service standard grâce à une gestion efficace de la mémoire GPU et à l'optimisation de la mémoire cache KV. Combiné aux formes OKE et OCI GPU, vous obtenez une plate-forme d'inférence prête pour la production 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 pile de production vLLM.
Ce tutoriel 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.
Note : Ce tutoriel provisionne les ressources étape par étape à l'aide de l'interface de ligne de commande OCI pour vous aider à comprendre le flux complet des ressources en nuage OCI requises pour un déploiement d'inférence de processeur graphique. Pour les environnements de production, il est recommandé de codifier cette infrastructure à l'aide de Terraform ou du gestionnaire de ressources OCI (Shepherd) pour les déploiements reproductibles contrôlés par version.

Les services OCI suivants sont utilisés dans ce tutoriel :
| Service | Objet |
|---|---|
| Oracle Kubernetes Engine (OKE) | Grappe Kubernetes gérée pour l'orchestration de conteneurs et la programmation de charges de travail GPU |
| OCI Compute (formes de processeur graphique) | Instances de processeur graphique NVIDIA A10 (24 Go) et A100 (80 Go) pour l'inférence de modèle |
| Volumes par blocs OCI | Stockage persistant pour les pondérations de modèle avec niveaux de performance configurables |
| Réseau en nuage virtuel (VCN) OCI | Infrastructure réseau, y compris les sous-réseaux, les passerelles et les listes de sécurité |
| Équilibreur de charge OCI | Accès externe aux points d'extrémité d'inférence |
| Service d'hôte bastion pour OCI | Tunnels SSH gérés pour l'accès à une grappe privée |
| Service de stockage d'objets pour OCI | Autre source de modèle utilisant des URL de demande préauthentifiée |
Objectifs
Dans ce tutoriel, vous allez :
- Déployer une grappe OKE avec des groupes 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 plugiciel d'appareil NVIDIA pour la programmation GPU
- Développez le système de fichiers de noeud GPU pour utiliser toute la capacité du volume de démarrage
- Déployer la pile de production vLLM avec un point d'extrémité d'inférence compatible avec OpenAI
- Tester l'inférence de GML avec des demandes d'API par rapport au modèle OpenAI GPT-OSS-20B
- Configurer le parallélisme de tenseur multiprocesseur pour des modèles plus volumineux sur des formes sans système d'exploitation
- Utiliser le stockage d'objets OCI comme source de modèle alternative
- Nettoyer toutes les ressources OCI pour éviter les frais continus
Conditions requises
- Un compte Oracle Cloud Infrastructure avec les éléments suivants :
- Compartiment autorisé à créer et à gérer des grappes 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 GPU, demandez-le au moyen d'un ticket de soutien - Autorisations de création de VCN, de sous-réseaux, de passerelles Internet et d'équilibreurs de charge
- Accès aux volumes par blocs OCI pour le stockage persistant
- Accès au service de stockage d'objets pour OCI (pour la section de chargement de modèle avancé)
Note : Les exemples de sorties et de captures d'écran de ce tutoriel utilisent us-chicago-1. Vous pouvez effectuer un déploiement dans n'importe quelle région prise en charge en définissant
OCI_REGION. La capacité du processeur graphique varie selon la région et le domaine de disponibilité. Vérifiez donc que la forme du processeur graphique cible est disponible avant le déploiement. Vérifiez la disponibilité des formes de processeur graphique par région et soyez prêt à essayer un autre domaine de disponibilité (GPU_AD_INDEX) si vous rencontrez des erreurs de capacité.
Note : Ce tutoriel provisionne des ressources GPU payantes (par exemple,
VM.GPU.A10.1). Il ne s'agit pas d'une charge de travail OCI de type Toujours gratuit. 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é
-
Paire de clés SSH (par exemple,
~/.ssh/id_rsaet~/.ssh/id_rsa.pub) pour l'accès à l'hôte bastion. Générez-en un avecssh-keygen -t rsa -b 4096si nécessaire -
Connaissance des concepts de Kubernetes (pods, services, déploiements, groupes de noeuds)
Note : Ce tutoriel déploie
openai/gpt-oss-20b, un modèle sous licence Apache 2.0 à partir de OpenAI. Aucun jeton Face Hugging n'est requis. Si vous voulez 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 votre compartiment dans la console OCI. Naviguez jusqu'à 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" -
Facultativement, remplacez la configuration par défaut en définissant l'une des variables d'environnement suivantes.
Variable Valeur par défaut Description OCI_REGIONus-ashburn-1Région OCI pour déploiement OCI_PROFILEDEFAULTProfil de configuration de l'interface de ligne de commande OCI CLUSTER_NAMEproduction-stackNom de la grappe OKE GPU_SHAPEVM.GPU.A10.1Forme de calcul GPU pour le groupe de noeuds GPU_NODE_COUNT1Nombre de noeuds GPU dans le groupe GPU_BOOT_VOLUME_GB200Taille du volume de démarrage en Go pour les noeuds GPU CPU_BOOT_VOLUME_GB100Taille du volume de démarrage en Go pour les noeuds d'UC GPU_AD_INDEX1Index de domaine de disponibilité (basé sur 0) pour le positionnement de GPU PRIVATE_CLUSTERtrueRégler à falsepour un point d'extrémité d'API Kubernetes publicKUBERNETES_VERSIONv1.31.10Version de Kubernetes pour la grappe OKE Par exemple, pour 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" -
Vérifiez les formes de processeur graphique disponibles et sélectionnez-en une en fonction de vos exigences de taille de modèle.
Forme Processeurs graphiques Type de GPU Mémoire du processeur graphique Recommandé pour VM.GPU.A10.11 NVIDIA A10 24 Go Modèles de paramètres 7B–13B VM.GPU.A10.22 NVIDIA A10 48 Go Tensor parallèle aux petits modèles BM.GPU4.88 NVIDIA A100 40 GB 320 Go Modèles 70B, économiques BM.GPU.A100-v2.88 NVIDIA A100 80 GB 640 Go 70B+ modèles de paramètre BM.GPU.H100.88 NVIDIA H100 640 Go Les plus grands modèles, prise en charge RDMA Note : Les formes sans système d'exploitation (
BM.*) fournissent du matériel dédié sans frais généraux de virtualisation et prennent en charge le parallélisme de tenseur multi-GPU. Les formes de machine virtuelle (VM.*) sont plus rentables pour les modèles plus petits.Note : 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 s'adaptent généralement à 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éployer à l'aide du script automatisé (démarrage rapide)
La pile de production vLLM comprend un script de déploiement automatisé qui provisionne toutes les ressources OCI et qui 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.
-
Cloner 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 votre compartiment.
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
Exécutez le script de déploiement.
./entry_point.sh setup
Pour les grappes publiques (
PRIVATE_CLUSTER=false), la configuration crée toute l'infrastructure et déploie la pile vLLM dans 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 grappes privées (par défaut), la configuration crée l'infrastructure mais ne peut pas accéder 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 podsRésultat attendu :
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
Note : Si les deux pods affichent le statut
Running, votre déploiement est prêt. Passez à Tâche 10 : Tester le point d'extrémité d'inférence.
Note : Les instances de GPU sont soumises à des contraintes de capacité OCI. Si le script reste dans la boucle "En attente du noeud GPU" pendant plus de 15 minutes, la forme GPU peut ne pas être disponible dans le domaine de disponibilité sélectionné. Vérifiez le statut du groupe de noeuds avec
oci ce node-pool getet recherchez les erreurs de capacité d'hôte insuffisante. Pour résoudre ce problème, nettoyez-le avec./entry_point.sh cleanupet redéployez-le avec un domaine de disponibilité différent (par exemple,GPU_AD_INDEX=0ouGPU_AD_INDEX=2) ou une forme de GPU différente (par exemple,GPU_SHAPE=VM.GPU.A10.2).
Note : Le script de déploiement utilise des instances GPU qui entraînent des coûts importants (~50 $ par jour pour un seul GPU A10). Exécutez toujours
./entry_point.sh cleanuplorsque vous avez terminé pour éviter les frais en cours.
Tâche 3 : Créer un VCN et un réseau
Créez l'infrastructure de réseau OCI requise pour la grappe OKE. Cela inclut un réseau en nuage virtuel (VCN), des passerelles, des tables de routage, des listes de sécurité et des sous-réseaux. Chaque ressource de réseau est créée en quelques secondes; l'ensemble complet des commandes se termine en moins de 2 minutes.
-
Créez un réseau 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éer une passerelle de service pour accéder à Oracle Services Network. Le contrôleur de nuage OKE utilise Oracle Services pour initialiser les noeuds de travail (définir les étiquettes de domaine de disponibilité, supprimer les tintes d'initialisation). Sans passerelle de service, les noeuds GPU peuvent rester à l'état non initialisé et le provisionnement des volumes par blocs échoue.
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éer des tables de routage pour les sous-réseaux privés et publics.
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)Note : La table de routage privée comporte deux règles : une route de passerelle NAT pour l'accès Internet général (extraction d'images de conteneur, téléchargement de modèles) et une route de passerelle de service pour l'accès direct à Oracle Services Network. La route de la passerelle de service est critique. Sans cela, le contrôleur OKE Cloud ne peut pas initialiser les noeuds de travail, ce qui empêche le provisionnement des volumes par blocs. La table de routage public utilise la passerelle Internet pour l'accès à l'équilibreur de charge.
-
Créez une liste de sécurité avec les règles de trafic entrant et sortant 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)Note de sécurité : Cet exemple de liste de sécurité est volontairement large pour simplifier. Pour la production, limitez SSH au sous-réseau de l'hôte bastion et à votre intervalle d'adresses IP, et préférez des listes de sécurité ou des groupes de sécurité de réseau distincts par sous-réseau afin que le sous-réseau de l'é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 en attachant des règles SSH uniquement au sous-réseau de l'hôte bastion. Vous pouvez conserver les blocs CIDR de pod/service Kubernetes sur le sous-réseau de traitement et omettre SSH entièrement à partir du sous-réseau de l'équilibreur de charge.
Fractionnement facultatif (recommandé) : Créez une petite liste de sécurité SSH seulement pour le sous-réseau d'hôte bastion et une liste distincte pour les sous-réseaux de travailleurs/équilibreurs de charge.
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)Note : 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 bloc CIDR de VCN). Utilisation : AttachezBASTION_SL_IDau sous-réseau de l'hôte bastion,WORKER_SL_IDaux sous-réseaux de l'API/du travailleur etLB_SL_IDau sous-réseau de l'équilibreur de charge.Note : Les règles CIDR des pods Kubernetes (
10.244.0.0/16) et CIDR des services (10.96.0.0/16) sont requises pour que les noeuds de travail GPU puissent s'enregistrer auprès de la grappe. La règle ICMP de type 3 code 4 permet la détection de MTU de chemin, ce qui évite les problèmes de fragmentation des paquets. -
Créez des sous-réseaux. La grappe nécessite quatre sous-réseaux : un pour le point d'extrémité de l'API Kubernetes, un pour les noeuds de travail, un pour les équilibreurs de charge et un pour l'hôte bastion utilisé pour accéder à la grappe privée.
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é Objet Point d'extrémité d'API 10.0.0.0/28Privée Serveur d'API Kubernetes Noeuds de travail 10.0.10.0/24Privée Noeuds de calcul GPU Équilibreurs de charge 10.0.20.0/24Publique Accès au service externe Hôte bastion 10.0.30.0/24Publique Tunnel SSH pour l'accès à la grappe privée
Tâche 4 : Créer la grappe OKE
Déployer une grappe Kubernetes gérée sur OKE à l'aide des ressources de réseau créées dans la tâche 3. Le provisionnement de la grappe prend environ 10 minutes. Ce tutoriel crée une grappe privée (la valeur par défaut du script), qui ne consomme pas d'adresse IP publique réservée pour le point d'extrémité de l'API Kubernetes. Les grappes privées 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 la grappe OKE avec un point d'extrémité privé.
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 retourne un ID demande de travail. Obtenez l'ID grappe à partir de la liste des grappes.
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}"Note : Les grappes privées ne requièrent pas d'adresse IP publique réservée. Les noeuds de travail accèdent toujours à Internet au moyen de 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 au moyen de l'hôte bastion (configuré dans les étapes suivantes). -
Attendez que le cluster devienne actif. Cette étape dure environ 10 minutes.
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputInterrogez la commande jusqu'à ce que la sortie retourne
ACTIVE.Facultatif : Affichez un sommaire de statut concis (y compris le point d'extrémité de l'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 hôte bastion OCI pour accéder à la grappe privée. L'hôte bastion fournit un tunnel SSH géré vers le point d'extrémité d'API Kubernetes privé.
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)Note : Remplacez
YOUR_PUBLIC_IP/32par votre adresse IP publique courante. 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-outputNote de sécurité : Pour la production, n'utilisez pas
0.0.0.0/0. Restreignez--client-cidr-listà votre adresse IP publique ou à votre bloc CIDR d'entreprise (par exemple,"YOUR_PUBLIC_IP/32"), sinon toute personne sur Internet peut tenter une session d'hôte bastion. -
Téléchargez le fichier kubeconfig à l'aide du point d'extrémité privé.
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 du point d'extrémité privé 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 d'hôte bastion. Vous aurez besoin d'un fichier de clé publique 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 transmet le port local 6443 à 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.comNote : 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 votre agent SSH. -
Mettez à jour le fichier kubeconfig pour vous connecter au moyen du 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=trueNote : L'indicateur
--insecure-skip-tls-verifyest requis car le certificat de grappe a été émis pour l'adresse IP du point d'extrémité privé, et non127.0.0.1. Cela est sûr car le trafic est chiffré via le tunnel SSH. -
Si vous utilisez un profil d'interface de ligne de commande OCI non par défaut (par exemple,
API_KEY_AUTH), mettez à jour le fichier kubeconfig pour l'utiliser. Par défaut, le kubeconfig généré est le profilDEFAULTpour la génération de jetons.kubectl config set-credentials \ $(kubectl config view --minify -o jsonpath='{.users[0].name}') \ --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}Conseil : Les étapes 6 à 9 sont automatisées par
./entry_point.sh tunnel, qui s'enregistre également automatiquement si le tunnel SSH tombe en panne lors d'opérations de longue durée telles que l'expansion de disque. Exécutez-le dans un terminal distinct et laissez-le en cours d'exécution pendant toute la durée de votre session. -
Vérifiez l'accès aux grappes.
kubectl get nodes
À ce stade, la sortie n'affiche aucun noeud, car le groupe de noeuds GPU n'a pas encore été ajouté.
No resources found
Tâche 5 : Ajouter un groupe de noeuds GPU
Ajoutez un groupe de noeuds avec des instances de calcul GPU à la grappe OKE.
-
Recherchez la dernière image de noeud OKE compatible GPU. OKE nécessite 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 appropriée 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}"Note : Les filtres d'interrogation pour les images de processeur graphique 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é comportant 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}"Note : La capacité du processeur graphique varie selon la région et le domaine de disponibilité. Si la création du groupe de noeuds échoue avec une erreur de capacité d'hôte insuffisante, essayez un autre domaine de disponibilité (
GPU_AD_INDEX) ou une autre forme de GPU, ou demandez de la capacité au moyen de votre processus OCI normal. -
Créez le groupe de noeuds GPU avec un volume de démarrage 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"}]'Note : Les étiquettes de noeud
app=gpuetnvidia.com/gpu=truesont utilisées plus tard par le graphique Helm vLLM pour programmer des pods d'inférence sur les noeuds GPU. Le volume de démarrage de 200 Go fournit de l'espace pour l'image de conteneur vLLM (~10 Go) et les poids du modèle, mais le système de fichiers doit être développé avant l'utilisation (voir la tâche 8). -
Attendez que les noeuds GPU deviennent prêts. Cela prend généralement 5 à 10 minutes pendant que le noeud provisionne, démarre, installe les pilotes GPU et s'enregistre auprès du cluster.
Note : Les instances de GPU sont soumises à des contraintes de capacité. Si le groupe 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 de 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é au moyen de la console OCI ou d'un ticket de soutien.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'Résultat attendu :
NAME GPUs 10.0.10.x 1 -
Appliquez le correctif CoreDNS pour programmer sur les noeuds GPU. Les noeuds GPU OKE ont une teinte
nvidia.com/gpu=present:NoSchedule. Dans les grappes qui n'ont que des noeuds GPU, les pods système tels que CoreDNS ne peuvent pas être programmés 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 corednsNote : Si votre grappe comporte un groupe de noeuds d'UC dédié pour les charges de travail du système, cette étape n'est pas nécessaire. Ce correctif n'est nécessaire que lorsque les noeuds GPU sont les seuls noeuds de la grappe.
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 : Installer le plugiciel d'appareil NVIDIA
Installez le plugiciel d'appareil NVIDIA afin que Kubernetes puisse détecter et programmer les charges de travail sur le processeur graphique.
-
Appliquez le plugiciel 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 plugiciel soient prêts.
kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300sNote : Certaines images de noeud de processeur graphique OKE incluent un plugiciel d'appareil NVIDIA préinstallé (
nvidia-gpu-device-plugin). Si l'image l'inclut déjà, l'application de la commande DaemonSet en amont crée une deuxième instance qui n'entraîne pas de conflit. 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 de noeud. -
Vérifiez que le processeur graphique peut être alloué par Kubernetes.
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'Résultat attendu :
NAME GPUs 10.0.10.x 1 -
Appliquez le correctif CoreDNS pour tolérer les tintes de noeud GPU. Dans les grappes où les noeuds GPU sont les seuls noeuds de travail, les pods CoreDNS ne peuvent pas programmer car les noeuds GPU OKE portent une teinte
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=60sNote : Cette étape n'est nécessaire que lorsque les noeuds GPU sont les seuls noeuds de travail de la grappe. Si vous disposez d'un groupe de noeuds d'UC dédié pour les charges de travail du système, CoreDNS y est programmé par défaut et ce correctif n'est pas nécessaire.
Tâche 7 : Configurer le stockage
Appliquez le volume par blocs OCI StorageClass pour fournir un stockage persistant pour les pondérations 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 Performance Cas d'utilisation oci-block-storage-encÉquilibré ( vpusPerGB: 10)Par défaut, rentable pour la plupart des modèles oci-block-storage-hpHaute performance ( vpusPerGB: 20)Chargement plus rapide des modèles plus volumineux -
Vérifiez que StorageClasses est disponible.
kubectl get storageclass
Note : Pour les déploiements à plusieurs noeuds nécessitant un stockage partagé dans plusieurs pods, utilisez le service de stockage de fichiers (NFS) OCI avec le mode d'accès
ReadWriteManyau lieu du service de volumes par blocs.
Tâche 8 : Développer le système de fichiers du noeud GPU
Les volumes de démarrage OCI ont une partition fixe d'environ 47 Go, quelle que soit la taille du volume de démarrage que vous spécifiez. L'image du conteneur vLLM est à elle seule d'environ 10 Go et les poids du modèle nécessitent de l'espace supplémentaire. Vous devez développer le système de fichiers avant de déployer vLLM pour éviter les expulsions DiskPressure.
Note : Il s'agit d'une exigence propre à OCI. Le volume de démarrage 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 courante 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 affichera un total d'environ 47 Go, confirmant que l'expansion est nécessaire.
-
Créez un pod privilégié sur le noeud GPU pour exécuter les commandes d'expansion.
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'expansion dans une seule commande
kubectl exec. Les exécuter ensemble évite le risque quekubectl execretourne le code de sortie 137 (SIGKILL) entre les étapes, ce qui peut se produire pendant les 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 Objet 1 growpart /dev/sda 3Développez la partition 3 pour utiliser le disque complet 2 pvresize /dev/sda3Redimensionner le volume physique LVM 3 lvextend -l +100%FREE /dev/ocivolume/rootÉtendre le volume logique 4 xfs_growfs /Développer le système de fichiers XFS pour remplir le volume Note : Les quatre opérations sont idempotentes. Si l'exécute 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 afin 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 --forceNote : La commande
nsenterentre l'espace de noms PID de l'hôte pour accéder à systemd. Unchroot /host systemctl restart kubeletbrut échoue car il ne peut pas se connecter au bus systemd à partir d'une racine croisée.La sortie attendue doit 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 -
Vérifiez 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"Note : Le modèle
openai/gpt-oss-20best un modèle Mixture of Experts (MoE) avec 20B paramètres totaux et 3.6B paramètres actifs par transmission. Il est publié sous la licence Apache 2.0, donc aucun jeton Hugging Face n'est requis. L'image de conteneurvllm/vllm-openaifournit un serveur d'API compatible avec OpenAI, permettant aux clients d'utiliser des appels de trousse SDK OpenAI standard pour votre point d'extrémité auto-hébergé. -
Déployez la pile. N'utilisez pas
--waitici, car le module de réseautage du routeur CrashLoop jusqu'à ce qu'il soit corrigé à 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 corrigé ensuite).
kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600sNote : Le pod du moteur prend plusieurs minutes pour devenir Prêt, car il télécharge les pondérations 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. -
Corriger le déploiement du routeur. Le routeur a besoin d'une tolérance de GPU (de sorte qu'il peut programmer lorsque les nœuds GPU sont les seuls nœuds avec capacité) et des limites de mémoire accrues (la valeur par défaut de 500 Mi peut causer 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"} ]'Note : La tolérance de GPU est nécessaire, car les noeuds GPU OKE ont une teinte
nvidia.com/gpu=present:NoSchedulequi empêche la programmation des charges de travail autres que GPU. Comme le routeur n'utilise pas de GPU mais doit s'exécuter quelque part, cette tolérance lui permet de planifier sur des noeuds GPU. Dans les grappes avec des groupes de noeuds d'UC dédiés, cette tolérance n'est pas nécessaire. -
Vérifiez que la version de Helm est déployée.
helm list
-
Vérifiez que les pods sont en cours d'exécution.
kubectl get podsRésultat attendu :
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 du 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 : Tester le point d'extrémité d'inférence
Vérifiez que le déploiement répond aux demandes d'inférence. La pile de production vLLM expose une API compatible avec OpenAI au moyen du service de routeur, de sorte que toute commande client de trousse SDK OpenAI ou curl peut interagir avec celle-ci.
Le diagramme suivant présente 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 retour en tant que réponse en continu.

-
Répertoriez les modèles disponibles pour vérifier que le déploiement est sain.
kubectl get svc vllm-router-serviceLe service de routeur fournit la passerelle d'API à tous les modèles déployés. Comme la grappe utilise un point d'extrémité privé, vous accédez au service au moyen de
kubectl port-forward. -
Démarrez un port-forward 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:80Cela mappe
localhost:8080sur votre machine au port 80 sur le service de routeur dans la grappe.Note de sécurité :
kubectl port-forwardlie localement et n'expose pas le service publiquement. Il s'agit du moyen le plus sûr de le tester lors de l'utilisation d'une grappe privée sur un tunnel d'hôte bastion.Note : 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 le point d'extrémité des modèles.
curl -s http://localhost:8080/v1/models | python3 -m json.toolRésultat attendu :
{ "object": "list", "data": [ { "id": "openai/gpt-oss-20b", "object": "model", "created": 1234567890, "owned_by": "vllm" } ] } -
Envoyer une demande de saisie semi-automatique.
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.toolSortie attendue (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 d'achèvement de clavardage. Il s'agit du même format utilisé par la trousse SDK Python OpenAI et il s'agit du moyen le plus courant d'interagir avec les LLM par programmation.
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.toolSortie attendue (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 du jeton et une valeurfinish_reasondestop(le modèle s'est terminé naturellement) oulength(la sortie a été tronquée àmax_tokens).Note : Le nom du modèle dans la demande d'API est le chemin complet du modèle (
openai/gpt-oss-20b), qui correspond au champmodelURLdans les valeurs Helm. Tout client compatible OpenAI peut utiliser ce point d'extrémité en réglantbase_urlàhttp://localhost:8080/v1.
-
Mesurer la latence de bout en bout pour une demande courte.
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 1 à 3 secondes de bout en bout pour des achèvements courts. Le délai pour le premier jeton (TTFT) est généralement de 50 à 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 : Présenter au moyen de l'équilibreur de charge OCI (facultatif)
Rendez le point d'extrémité d'inférence accessible à l'externe au moyen d'un équilibreur de charge OCI.
Note de sécurité : L'API d'inférence est exposée à l'Internet public par défaut. Ne l'activez pas en production sans TLS, authentification (clé d'API/JWT/mTLS) et liste d'autorisation IP ou contrôles WAF. Si vous devez l'exposer, vous pouvez le présenter avec un contrôleur de trafic entrant ou une passerelle d'API qui applique des limites d'authentification et de débit, ou utiliser un équilibreur de charge interne.
-
Appliquez des correctifs au service de routeur pour qu'il utilise un type LoadBalancer.
kubectl patch svc vllm-router-service \ -p '{"spec": {"type": "LoadBalancer"}}'Note : Si vous voulez un équilibreur de charge interne, ajoutez l'annotation OCI au service (exemple ci-dessous). Le point d'extrémité reste privé 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 -wSortie attendue 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 le point d'extrémité 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 Tensor Multi-GPU (avancé)
Déployez des modèles plus volumineux sur plusieurs processeurs graphiques sur des formes sans système d'exploitation.
Le parallélisme de tenseur divise un modèle en plusieurs GPU sur un seul noeud. Cela est requis 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é de n'importe quel GPU, mais s'adapte sur 8x A100 80 Go ou 8x H100 GPU.
-
Créez une clé secrète Kubernetes avec votre jeton Face Hugging. 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,
--waitn'utilisez pas . Le routeur CrashLoop jusqu'à ce qu'il soit corrigé.helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900sAppliquez ensuite un correctif 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 utilisés.
kubectl get pods kubectl logs -f deployment/vllm-llama70b-deployment-vllm
Note : Assurez-vous que votre grappe OKE comporte un groupe de noeuds avec la forme GPU sans système d'exploitation appropriée (par exemple,
BM.GPU.H100.8) avant de déployer une configuration multi-GPU.
Tâche 13 : Utiliser le stockage d'objets OCI pour les modèles (avancé)
Chargez les pondérations de modèle à partir du stockage d'objets OCI au lieu du téléchargement à partir du visage Hugging. Cette fonction est utile pour les modèles privés, les téléchargements plus rapides dans OCI ou les environnements sans accès Internet externe.
-
Chargez vos pondérations de modèle dans un seau de stockage d'objets OCI. Naviguez jusqu'à Stockage > Stockage d'objets dans la console OCI et créez un seau si vous n'en avez pas déjà un.
-
Créez une URL de demande préauthentifiée pour votre seau. Dans la console OCI, sélectionnez votre seau, cliquez sur Demandes préauthentifiées et créez une nouvelle demande avec accès en lecture.
-
Mettez à jour
production_stack_specification.yamlpour utiliser l'URL de la 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 les chargements de modèle à partir du stockage d'objets 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.
-
Supprimez les ressources Kubernetes à l'aide du script de nettoyage.
cd production-stack/deployment_on_cloud/oci ./clean_up.shCette action désinstalle la version de Helm, supprime toutes les ressources PersistentVolumeClaims, PersistentVolumes et vLLM personnalisées.
-
Supprimez la grappe OKE et toutes les ressources de réseau OCI.
./entry_point.sh cleanupLes ressources suivantes sont supprimées dans l'ordre :
- Groupe de noeuds GPU
- Grappe OKE
- Hôte bastion (si créé)
- Sous-réseaux (API, programme, équilibreur de charge, hôte 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é supprimées dans la console OCI sous Services de développement > Grappes Kubernetes et Réseau > Réseaux en nuage virtuels.
./entry_point.sh cleanup
Note : Assurez-vous que toutes les ressources sont supprimées pour éviter les frais en cours. Les instances de GPU et les volumes par blocs entraînent des coûts même en cas d'inactivité.
Étape suivante
Ce tutoriel a déployé une pile d'inférence fonctionnelle. Pour les charges de travail de production, tenez compte des 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 d'accès au premier jeton, aux jetons par seconde, à l'utilisation du cache KV et à la profondeur de la file d'attente des demandes. - Ajustement automatique : Configurez le HPA de Kubernetes avec des mesures personnalisées (profondeur de la file d'attente des demandes ou utilisation de GPU au moyen de DCGM) pour ajuster automatiquement les répliques de moteur sous charge.
- Durcissement de la sécurité : Restreignez
BASTION_CLIENT_CIDRà votre intervalle d'adresses IP, ajoutez des politiques de réseau Kubernetes pour isoler l'espace de noms d'inférence et stockez les données d'identification du modèle dans le service de chambre forte OCI au lieu des clés secrètes Kubernetes. - Haute disponibilité : Répartissez les noeuds 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, tenez compte des instances GPU préemptives. Surveillez l'utilisation des processeurs graphiques pour dimensionner à droite votre groupe de noeuds. - Infrastructure en tant que code : Codifiez ce déploiement avec le fournisseur Terraform pour OCI ou le gestionnaire de ressources OCI pour une infrastructure reproductible contrôlée par version.
Liens connexes
Remerciements
- Auteur - Federico Kamelhar (architecte principal principal, Agentic AI)
Ressources d'apprentissage supplémentaires
Explorez d'autres laboratoires sur le site docs.oracle.com/learn ou accédez à plus de contenu d'apprentissage gratuit sur le canal Oracle Learning YouTube. De plus, visitez education.oracle.com/learning-explorer pour devenir un explorateur Oracle Learning.
Pour obtenir la documentation sur le produit, visitez Oracle Help Center.
Deploy OpenAI vLLM Production Stack on Oracle Kubernetes Engine (OKE)
G50935-01