ノート:
- このチュートリアルでは、Oracle Cloudへのアクセスが必要です。無料アカウントにサインアップするには、Oracle Cloud Infrastructure Free Tierの開始を参照してください。
- Oracle Cloud Infrastructureの資格証明、テナンシおよびコンパートメントの値の例を使用します。ラボを完了する際は、これらの値をクラウド環境に固有の値に置き換えてください。
Oracle Kubernetes Engine (OKE)へのOpenAI vLLM本番スタックのデプロイ
はじめに
本番ワークロードに大規模言語モデル(LLM)を採用する組織は、サードパーティの推論APIに依存するか、自己ホスト型の推論スタックをデプロイするという、インフラストラクチャの重要な決定に直面しています。自己ホスト型のデプロイメントには、完全なデータ・プライバシとコンプライアンス制御、ネットワーク・ラウンドトリップの排除による100ミリ秒未満の推論レイテンシ、大規模な予測可能なコスト、ベンダー・ロックインなしで任意のオープンソース・モデルを微調整して提供する自由といった大きな利点があります。
ただし、本番レベルのLLM推論スタックを最初から構築することは複雑です。GPU対応コンテナ・オーケストレーション、複数のモデル・レプリカにわたるインテリジェントなリクエスト・ルーティング、大規模なモデル重みに対応した永続ストレージ、継続的な監視がすべて統合され、確実に実行されている必要があります。
Oracle Cloud Infrastructureは、AI推論のための複数のパスを提供します。OCI Generative AI Serviceは、テナンシに分離された専用AIクラスタでフルマネージド・エクスペリエンスを提供し、サポートされているモデルを迅速に開始したいチームに最適です。このチュートリアルでは、独自の推論スタックをOKEにデプロイするという代替のアプローチをとります。このパスは、GPUドライバ、CUDAバージョン、モデル構成、サービングパラメーターを正確に制御する必要があるチーム、またはカスタムモデルのトレーニングと微調整を行い、直接サービスを提供したいチーム向けに設計されています。OCIは、超低レイテンシのRDMAクラスタ・ネットワーキングによって接続されたNVIDIA A10、A100、およびH100 GPUを搭載したベアメタルGPUインスタンスを提供し、クラウドの弾力性からメリットを得ながら、オンプレミスと同じレベルのハードウェア制御を実現します。
vLLM Production Stackは、vLLM上に構築されたオープンソースのKubernetesネイティブ・プラットフォーム、Meta、Mistral AI、IBMなどの組織による本番で使用される高スループットの推論エンジンを提供することで、自己ホスト推論の複雑さを解決します。効率的なGPUメモリー管理およびKVキャッシュ最適化により、標準サービス・フレームワークと比較して最大24xのスループットが向上します。OKEおよびOCI GPUシェイプと組み合せると、エンタープライズ・グレードのネットワーキング、ストレージおよびセキュリティを備えた本番環境に対応した推論プラットフォームが提供されます。このチュートリアルで使用するOCIデプロイメント・スクリプトは、公式のvLLM本番スタック・リポジトリにコントリビュートされ、維持されます。
このチュートリアルでは、インフラストラクチャ・プロビジョニングから最初の推論リクエストの実行まで、OKEへのvLLM本番スタックのデプロイについて説明します。
ノート:このチュートリアルでは、GPU推論デプロイメントに必要なOCIクラウド・リソースのフロー全体を理解するために、OCI CLIを使用してリソースを段階的にプロビジョニングします。本番環境では、バージョン管理された繰返し可能なデプロイメントにTerraformまたはOCI Resource Manager (Shepherd)を使用して、このインフラストラクチャをコード化することをお薦めします。

このチュートリアルでは、次のOCIサービスを使用します。
| サービス | 目 的 |
|---|---|
| Oracle Kubernetesエンジン(OKE) | コンテナ・オーケストレーションおよびGPUワークロード・スケジューリングのためのマネージドKubernetesクラスタ |
| OCIコンピュート(GPUシェイプ) | モデル推論用のNVIDIA A10 (24GB)およびA100 (80GB) GPUインスタンス |
| OCIブロック・ボリューム | 構成可能なパフォーマンス層によるモデルの重み付けのための永続ストレージ |
| OCI仮想クラウド・ネットワーク(VCN) | サブネット、ゲートウェイおよびセキュリティ・リストを含むネットワーク・インフラストラクチャ |
| OCIロード・バランサ | 推論エンドポイントへの外部アクセス |
| OCI Bastion | プライベート・クラスタ・アクセスの管理対象SSHトンネル |
| OCIオブジェクト・ストレージ | 事前認証済リクエスト(PAR) URLを使用した代替モデル・ソース |
目的
このチュートリアルでは、次のことを実行します。
- OCI CLIを使用したGPU対応ノード・プールを含むOKEクラスタのデプロイ
- Kubernetesワークロード用のOCIネットワーキング(VCN、サブネット、ゲートウェイ)の構成
- GPUスケジューリング用のNVIDIAデバイス・プラグインをインストールおよび構成します
- 完全なブート・ボリューム容量を使用するようにGPUノード・ファイルシステムを拡張します。
- OpenAI互換の推論エンドポイントを含むvLLM本番スタックをデプロイします
- OpenAI GPT-OSS-20Bモデルに対するAPIリクエストによるLLM推論のテスト
- ベア・メタル・シェイプ上の大規模モデルに対するマルチGPUテンソル並列度を構成します
- OCI Object Storageを代替モデル・ソースとして使用
- すべてのOCIリソースをクリーンアップして継続的な料金を回避
前提条件
- 次を含むOracle Cloud Infrastructureアカウント。
- OKEクラスタを作成および管理する権限を持つコンパートメント
- 目的のシェイプのGPUコンピュート割当て(たとえば、
VM.GPU.A10.1またはBM.GPU.A100-v2.8)。GPU割当て制限がない場合は、サポート・チケットを介してリクエストします - VCN、サブネット、インターネット・ゲートウェイおよびロード・バランサを作成する権限
- 永続ストレージ用のOCI Block Volumesへのアクセス
- OCI Object Storageへのアクセス(拡張モデル・ロード・セクション用)
ノート:このチュートリアルの出力例およびスクリーンショットでは、us-chicago-1を使用します。
OCI_REGIONを設定することで、サポートされている任意のリージョンにデプロイできます。GPU容量はリージョンおよびアベイラビリティ・ドメインによって異なりますので、デプロイする前にターゲットGPUシェイプが使用可能であることを確認してください。リージョン別のGPUシェイプの可用性を確認し、容量エラーが発生した場合は別の可用性ドメイン(GPU_AD_INDEX)を試す準備をします。
ノート:このチュートリアルでは、有料GPUリソース(たとえば、
VM.GPU.A10.1)をプロビジョニングします。OCI Always Freeワークロードではありません。継続的な料金を回避するために、完了時に必ずクリーンアップ・ステップを実行します。
-
oci setup configを使用してインストールおよび構成されたOCI CLI -
JSON解析用にインストールされたjq
-
kubectlがインストールされている
-
Helmがインストールされている
-
要塞アクセス用のSSHキー・ペア(たとえば、
~/.ssh/id_rsaおよび~/.ssh/id_rsa.pub)。必要に応じて、ssh-keygen -t rsa -b 4096を使用して生成します。 -
Kubernetesの概念(ポッド、サービス、デプロイメント、ノード・プール)に精通している
ノート:このチュートリアルでは、Apache 2.0ライセンス・モデルである
openai/gpt-oss-20bをOpenAIからデプロイします。Hugging Faceトークンは必要ありません。Meta Llama 3.1などのゲート付きモデルをデプロイする場合は、APIトークンを含むHugging Faceアカウントが必要です。
タスク1: 環境変数の構成
インフラストラクチャをデプロイする前に、必要なOCI構成を設定します。
-
OCIコンソールでコンパートメントOCIDを検索します。「アイデンティティとセキュリティ」→「コンパートメント」にナビゲートし、ターゲット・コンパートメントをクリックしてOCIDをコピーします。
oci iam compartment list --query 'data[].{name:name,id:id}' --output table
-
必要な環境変数をエクスポートします。
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
オプションで、次のいずれかの環境変数を設定して、デフォルトの構成をオーバーライドします。
可変 デフォルト 説明 OCI_REGIONus-ashburn-1デプロイメント用のOCIリージョン OCI_PROFILEDEFAULTOCI CLI構成プロファイル CLUSTER_NAMEproduction-stackOKEクラスタの名前 GPU_SHAPEVM.GPU.A10.1ノード・プールのGPUコンピュート・シェイプ GPU_NODE_COUNT1プール内のGPUノードの数 GPU_BOOT_VOLUME_GB200GPUノード用のブート・ボリューム・サイズ(GB) CPU_BOOT_VOLUME_GB100CPUノード用のブート・ボリューム・サイズ(GB) GPU_AD_INDEX1GPU配置用の可用性ドメイン索引(0ベース) PRIVATE_CLUSTERtrueパブリックKubernetes APIエンドポイントの場合は falseに設定しますKUBERNETES_VERSIONv1.31.10OKEクラスタのKubernetesバージョン たとえば、2つのA100 GPUノードを使用してデプロイするには:
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" export GPU_SHAPE="BM.GPU.A100-v2.8" export GPU_NODE_COUNT="2" -
使用可能なGPUシェイプを確認し、モデル・サイズ要件に基づいて1つ選択します。
形状 GPU GPUタイプ GPUメモリー 推奨対象 VM.GPU.A10.11 NVIDIA A10 24GB 7B–13Bパラメータ・モデル VM.GPU.A10.22 NVIDIA A10 48GB 小型モデルと平行なテンソル BM.GPU4.88 NVIDIA A100 40 GB 320GB 70Bモデル、コスト効率が高い BM.GPU.A100-v2.88 NVIDIA A100 80 GB 640GB 70B+パラメータ・モデル BM.GPU.H100.88 NVIDIA H100 640GB 最大モデル、RDMAサポート ノート:ベア・メタル・シェイプ(
BM.*)は、仮想化オーバーヘッドのない専用ハードウェアを提供し、マルチGPUテンソル並列処理をサポートします。仮想マシン・シェイプ(VM.*)は、小規模なモデルではコスト効率が高くなります。ノート:このチュートリアルでは、
VM.GPU.A10.1(24 GB GPUメモリーを備えた単一のNVIDIA A10)を使用してopenai/gpt-oss-20bをデプロイします。これは、通常、単一のA10 GPUに適合する3.6Bアクティブ・パラメータを持つMixture of Experts (MoE)モデルです。高度なセクションでは、Llama 3.1 70Bなどの大規模なモデルに対してBM.GPU.H100.8を使用したマルチGPU構成を示します。
タスク2: 自動スクリプトを使用したデプロイ(クイック・スタート)
vLLM本番スタックには、すべてのOCIリソースをプロビジョニングし、単一のコマンドで推論スタックをデプロイする自動デプロイメント・スクリプトが含まれています。このアプローチは、迅速なデプロイメントに使用します。タスク3から10は、プロセスをカスタマイズするユーザーの各ステップを個別にカバーします。
-
vLLM Production Stackリポジトリをクローニングします。
git clone https://github.com/vllm-project/production-stack.git cd production-stack/deployment_on_cloud/oci -
コンパートメントOCIDをエクスポートします。
export OCI_COMPARTMENT_ID="ocid1.compartment.oc1..xxxxx" -
デプロイメント・スクリプトを実行します。
./entry_point.sh setup
パブリック・クラスタ(
PRIVATE_CLUSTER=false)の場合、設定によってすべてのインフラストラクチャが作成され、1つのコマンドでvLLMスタックがデプロイされます。Helm値ファイルを2番目の引数として渡します:PRIVATE_CLUSTER=false ./entry_point.sh setup ./production_stack_specification.yamlプライベート・クラスタ(デフォルト)の場合、設定によってインフラストラクチャが作成されますが、Kubernetes APIに直接到達できません。別のターミナルを開き、トンネルを起動して、デプロイします:
# 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 -
デプロイメントが実行中であることを確認します。
kubectl get pods予期される出力:
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
ノート:両方のポッドのステータスが
Runningの場合、デプロイメントの準備ができています。「タスク10: 推論エンドポイントのテスト」に進みます。
ノート: GPUインスタンスはOCI容量制約の対象となります。スクリプトが15分を超えて「GPUノードの待機中」ループにとどまっている場合、選択した可用性ドメインでGPUシェイプが使用できない可能性があります。
oci ce node-pool getを使用してノード・プールのステータスを確認し、「Out of host capacity」エラーを確認します。これを解決するには、./entry_point.sh cleanupを使用してクリーン・アップし、別の可用性ドメイン(GPU_AD_INDEX=0やGPU_AD_INDEX=2など)または別のGPUシェイプ(GPU_SHAPE=VM.GPU.A10.2など)で再デプロイします。
ノート:デプロイメント・スクリプトでは、大幅なコストが発生するGPUインスタンスが使用されます(単一のA10 GPUの場合は1日当たり50ドル)。継続料金を回避するために、
./entry_point.sh cleanupを必ず実行してください。
タスク3: VCNとネットワーキングの作成
OKEクラスタに必要なOCIネットワーク・インフラストラクチャを作成します。これには、Virtual Cloud Network (VCN)、ゲートウェイ、ルート表、セキュリティ・リストおよびサブネットが含まれます。各ネットワーキング・リソースは数秒で作成され、コマンドのフル・セットは2分以内に完了します。
-
10.0.0.0/16CIDRブロックを含むVCNを作成します。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) -
パブリック・サブネット・ルーティング用のインターネット・ゲートウェイを作成します。
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) -
プライベート・サブネットからのアウトバウンド・トラフィック用のNAT Gatewayを作成します。
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) -
Oracle Services Networkにアクセスできるサービス・Gatewayを作成します。OKEクラウド・コントローラは、Oracle Servicesを使用してワーカー・ノードを初期化します(可用性ドメイン・ラベルの設定、初期化テイントの削除)。サービス・ゲートウェイがない場合、GPUノードは初期化されていない状態のままになり、ブロック・ボリュームのプロビジョニングは失敗します。
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) -
プライベート・サブネットおよびパブリック・サブネットのルート表を作成します。
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)ノート:プライベート・ルート表には、一般的なインターネット・アクセス用のNAT Gatewayルート(コンテナ・イメージのプル、モデルのダウンロード)と、Oracle Services Networkへの直接アクセス用のサービス・ゲートウェイ・ルートの2つのルールがあります。サービス・ゲートウェイ・ルートがクリティカルです。これがないと、OKEクラウド・コントローラはワーカー・ノードを初期化できず、ブロック・ボリュームのプロビジョニングを防ぎます。パブリック・ルート表は、ロード・バランサ・アクセスにインターネット・ゲートウェイを使用します。
-
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)セキュリティ・ノート:このセキュリティ・リストの例は、わかりやすくするために意図的に幅広くなっています。本番環境では、SSHを要塞サブネットおよびIP範囲に制限し、ロード・バランサ・サブネットで
0.0.0.0/0からのSSHが許可されないように、サブネットごとに個別のセキュリティ・リストまたはNSGを優先します。セキュアなデフォルト:まず、SSHをパブリックIPに制限し、SSHルールを要塞サブネットにのみアタッチします。Kubernetesポッド/サービスCIDRをワーカー・サブネットに保持し、ロード・バランサ・サブネットからSSHを完全に省略できます。
オプション(推奨)の分割:要塞サブネット用にSSHのみの小さなセキュリティ・リストを作成し、ワーカー/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)ノート:内部ロード・バランサのみを使用する場合は、前述の
0.0.0.0/0ソースを10.0.0.0/16(またはVCN CIDR)に置き換えてください。使用法:BASTION_SL_IDを要塞サブネットに、WORKER_SL_IDをAPI/workerサブネットに、LB_SL_IDをロードバランササブネットにアタッチします。ノート: GPUワーカー・ノードをクラスタに登録するには、KubernetesポッドCIDR (
10.244.0.0/16)およびサービスCIDR (10.96.0.0/16)ルールが必要です。ICMPタイプ3コード4ルールはパスMTU検出を有効にし、パケットの断片化の問題を防止します。 -
サブネットを作成します。クラスタには4つのサブネットが必要です。1つはKubernetes APIエンドポイント用、もう1つはワーカー・ノード用、もう1つはロード・バランサ用、もう1つはプライベート・クラスタへのアクセスに使用される要塞ホスト用です。
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)サブネット CIDR 表示 目 的 APIエンドポイント 10.0.0.0/28プライベート Kubernetes APIサーバー ワーカー・ノード 10.0.10.0/24プライベート GPUコンピュート・ノード ロード・バランサ 10.0.20.0/24パブリック 外部サービス・アクセス 要塞 10.0.30.0/24パブリック プライベート・クラスタ・アクセス用のSSHトンネル
タスク4: OKEクラスタの作成
タスク3で作成したネットワーキング・リソースを使用して、管理対象KubernetesクラスタをOKEにデプロイします。クラスタのプロビジョニングには約10分かかります。このチュートリアルでは、Kubernetes APIエンドポイントの予約済パブリックIPを使用しないプライベート・クラスタ(スクリプトのデフォルト)を作成します。APIサーバーがパブリック・インターネットに公開されていないため、プライベート・クラスタは本番ワークロードの推奨アプローチです。
-
プライベート・エンドポイントを含むOKEクラスタを作成します。
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 falseこのコマンドは、作業リクエストIDを返します。クラスタ・リストからクラスタIDを取得します。
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}"ノート:プライベート・クラスタには、予約済パブリックIPは必要ありません。ワーカー・ノードは、引き続きNAT Gatewayを介してインターネットにアクセスし、コンテナ・イメージをプルしてモデルをダウンロードします。
kubectlアクセスのみに要塞を通るSSHトンネルが必要です(次のステップで構成)。 -
クラスタがACTIVEになるのを待ちます。このステップには約10分かかります。
oci ce cluster get \ --cluster-id "${CLUSTER_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-output出力が
ACTIVEを返すまでコマンドをポーリングします。オプション: 簡潔なステータス・サマリー(プライベートAPIエンドポイントを含む)を表示します。
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
-
プライベート・クラスタにアクセスするためのOCI要塞を作成します。要塞は、プライベートKubernetes APIエンドポイントへの管理対象SSHトンネルを提供します。
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)ノート:
YOUR_PUBLIC_IP/32を現在のパブリックIPに置き換えます。共有ネットワークの場合は、かわりに企業のCIDRブロックを使用してください。要塞がアクティブになるまで待機します(約1分)。
oci bastion bastion get \ --bastion-id "${BASTION_ID}" \ --query "data.\"lifecycle-state\"" \ --raw-outputセキュリティ・ノート:本番環境では、
0.0.0.0/0を使用しないでください。--client-cidr-listをパブリックIPまたは企業CIDR ("YOUR_PUBLIC_IP/32"など)に制限します。そうしないと、インターネット上のすべてのユーザーが要塞セッションを試行できます。 -
プライベート・エンドポイントを使用してkubeconfigをダウンロードします。
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 -
SSHトンネルのプライベート・エンドポイントIPアドレスを取得します。
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}" -
要塞ポート転送セッションを作成します。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)セッションがACTIVEになるまで待ってから、SSHコマンドを取得します。
oci bastion session get \ --session-id "${SESSION_ID}" \ --query "data.{state:\"lifecycle-state\", ssh:\"ssh-metadata\".command}" 2>&1 -
別の端末を開き、前のステップのコマンドを使用してSSHトンネルを開始します。トンネルは、ローカル・ポート6443をプライベートKubernetes APIに転送します。
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.comノート:
<PRIVATE_IP>、<SESSION_OCID>および<REGION>を、前のステップの値に置き換えます。セッション中は、このターミナルを開いたままにします。-o IdentitiesOnly=yesフラグは、SSHエージェントに複数のキーがロードされている場合に「認証失敗が多すぎる」エラーを防止します。 -
kubeconfigを更新して、ローカル・トンネルを介して接続します。
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=trueノート:クラスタ証明書が
127.0.0.1ではなくプライベート・エンドポイントIPに対して発行されたため、--insecure-skip-tls-verifyフラグが必要です。トラフィックはSSHトンネルを介して暗号化されるため、これは安全です。 -
デフォルト以外のOCI CLIプロファイル(
API_KEY_AUTHなど)を使用している場合は、それを使用するようにkubeconfigを更新します。生成されたkubeconfigは、トークン生成用のDEFAULTプロファイルにデフォルト設定されます。kubectl config set-credentials \ $(kubectl config view --minify -o jsonpath='{.users[0].name}') \ --exec-env=OCI_CLI_PROFILE=${OCI_PROFILE}ヒント:ステップ6から9は、
./entry_point.sh tunnelによって自動化されます。これは、ディスク拡張などの長時間実行操作中にSSHトンネルがドロップした場合にも自動再接続されます。別の端末で実行し、セッション中は実行したままにします。 -
クラスタ・アクセスを確認します。
kubectl get nodes
GPUノード・プールがまだ追加されていないため、この時点で出力にはノードが表示されません。
No resources found
タスク5: GPUノード・プールの追加
GPUコンピュート・インスタンスを含むノード・プールをOKEクラスタに追加します。
-
最新のGPU互換OKEノード・イメージを検索します。OKEには、kubeletおよびノード登録コンポーネントが事前にインストールされている特定のイメージが必要です。
node-pool-optionsAPIを使用して、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}"ノート: Kubernetesバージョン(
OKE-1.31.10など)と一致するOracle Linux 8.10 GPUイメージの問合せフィルタ。ARMベースのイメージが必要な場合は、8.10を適切なフィルタに置き換えます。 -
GPUシェイプを持つ可用性ドメインを確認します。すべての可用性ドメインにGPU容量があるわけではありません。
AD=$(oci iam availability-domain list \ --compartment-id "${OCI_COMPARTMENT_ID}" \ --query "data[${GPU_AD_INDEX}].name" \ --raw-output) echo "Availability Domain: ${AD}"ノート: GPU容量は、リージョンおよびアベイラビリティ・ドメインによって異なります。ノード・プールの作成が「ホスト容量不足」エラーで失敗した場合は、別の可用性ドメイン(
GPU_AD_INDEX)またはGPUシェイプを試すか、通常のOCIプロセスを介して容量をリクエストします。 -
200 GBのブート・ボリュームを使用してGPUノード・プールを作成します。
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"}]'ノート:ノード・ラベル
app=gpuおよびnvidia.com/gpu=trueは、GPUノードで推論ポッドをスケジュールするために、後でvLLM Helmチャートによって使用されます。200 GBブート・ボリュームは、vLLMコンテナ・イメージ(最大10 GB)およびモデルの重みのための領域を提供しますが、ファイル・システムは使用する前に拡張する必要があります(タスク8を参照)。 -
GPUノードが準備完了になるのを待ちます。これは通常、ノードがGPUドライバをプロビジョニング、ブート、インストールし、クラスタに登録する間に5~10分かかります。
ノート: GPUインスタンスは容量制約の対象となります。ノード・プールがCREATING状態のままの場合は、OCIコンソールまたは
oci ce node-pool getでノード・ステータスを確認します。「Out of host capacity(ホスト容量不足)」エラーは、その可用性ドメインで使用可能なGPUインスタンスがないことを意味します。これを解決するには、別の可用性ドメイン(GPU_AD_INDEX=0またはGPU_AD_INDEX=2)を試すか、別のGPUシェイプを試すか、OCIコンソールまたはサポート・チケットを使用して容量予約をリクエストします。kubectl get nodes -wノードの準備が整ったら、期待される出力:
NAME STATUS ROLES AGE VERSION 10.0.10.x Ready node 5m v1.31.10 -
GPUがノードで検出されていることを確認します。
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu'予期される出力:
NAME GPUs 10.0.10.x 1 -
CoreDNSにパッチを適用して、GPUノードでスケジュールします。OKE GPUノードには
nvidia.com/gpu=present:NoScheduletaintがあります。GPUノードのみを持つクラスタでは、CoreDNSのようなシステム・ポッドは、このtaintに対する許容範囲なしでスケジュールできません。DNSがないと、ポッドは外部ホスト名を解決してモデルをダウンロードできません。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"}}]'CoreDNSが実行されていることを確認します。
kubectl get pods -n kube-system | grep corednsノート:クラスタにシステム・ワークロード専用のCPUノード・プールがある場合、このステップは不要です。このパッチは、GPUノードがクラスタ内の唯一のノードである場合にのみ必要です。
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
タスク6: NVIDIAデバイス・プラグインのインストール
KubernetesがGPUでワークロードを検出してスケジュールできるように、NVIDIAデバイス・プラグインをインストールします。
-
NVIDIAデバイス・プラグインDaemonSetを適用します。
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.1/nvidia-device-plugin.yml -
プラグイン・ポッドの準備が完了するまで待ちます。
kubectl wait --for=condition=Ready pods -l name=nvidia-device-plugin-ds -n kube-system --timeout=300sノート:一部のOKE GPUノード・イメージには、事前インストールされたNVIDIAデバイス・プラグイン(
nvidia-gpu-device-plugin)が含まれています。イメージにすでにそれが含まれている場合、アップストリームDaemonSetを適用すると、競合が発生しない2番目のインスタンスが作成されます。自動スクリプト(entry_point.sh deploy-vllm)は、ノード・イメージのバージョンに関係なくGPU検出が機能するように常にインストールします。 -
GPUがKubernetesによって割り当て可能であることを確認します。
kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.allocatable.'nvidia\.com/gpu'予期される出力:
NAME GPUs 10.0.10.x 1 -
GPUノードのtaintを許容するようにCoreDNSにパッチを適用します。GPUノードが唯一のワーカー・ノードであるクラスタでは、OKE GPUノードに
nvidia.com/gpu=present:NoScheduletaintがあるため、CoreDNSポッドをスケジュールできません。DNSがないと、ポッドはイメージ・レジストリを解決したり、ダウンロードURLをモデル化できません。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=60sノート:このステップは、GPUノードがクラスタ内の唯一のワーカー・ノードである場合にのみ必要です。システム・ワークロード専用のCPUノード・プールがある場合、CoreDNSはデフォルトでそこをスケジュールし、このパッチは不要です。
タスク7: ストレージの構成
OCI Block Volume StorageClassを適用して、モデルの重み付けに永続ストレージを提供します。
-
StorageClass定義を適用します。
kubectl apply -f oci-block-storage-sc.yamlこのファイルは、次の2つのパフォーマンス層を定義します。
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 パフォーマンス ユース・ケース oci-block-storage-encバランス( vpusPerGB: 10)デフォルトで、ほとんどのモデルでコスト効率が高い oci-block-storage-hp高パフォーマンス( vpusPerGB: 20)より大規模なモデルへの迅速なモデル・ロード -
StorageClassesが使用可能であることを確認します。
kubectl get storageclass
ノート:複数のポッドにまたがる共有ストレージを必要とするマルチノード・デプロイメントの場合は、ブロック・ボリュームではなく、
ReadWriteManyアクセス・モードのOCI File Storage Service (NFS)を使用します。
タスク8: GPUノード・ファイルシステムの拡張
OCIブート・ボリュームには、指定したブート・ボリューム・サイズに関係なく、固定最大47 GBのパーティションがあります。vLLMコンテナ・イメージのみでは約10 GBであり、モデルの重みには追加の領域が必要です。DiskPressureの削除を回避するには、vLLMをデプロイする前にファイルシステムを拡張する必要があります。
ノート:これはOCI固有の要件です。ブート・ボリュームは200 GBにプロビジョニングされますが、オペレーティング・システムではデフォルトで最大47 GBのパーティションのみがプロビジョニングされます。残りの領域は手動で要求する必要があります。
-
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\":\"/\"}}]}}"出力には合計で約47 GBと表示され、拡張が必要であることが確認されます。
-
拡張コマンドを実行する権限付きポッドをGPUノードに作成します。
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\":\"/\"}}]}}"ポッドが起動するのを待ちます。
kubectl wait --for=condition=Ready pod/expand-disk --timeout=60s -
単一の
kubectl execコマンドで4つの拡張ステップをすべて実行します。これらを同時に実行すると、kubectl execがステップ間で終了コード137 (SIGKILL)を返すリスクが回避されます。これは、ホスト上の重いディスクI/O中に発生する可能性があります。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 / 'ステップ コマンド 目 的 1 growpart /dev/sda 3パーティション3を展開してフル・ディスクを使用します。 2 pvresize /dev/sda3LVM物理ボリュームのサイズ変更 3 lvextend -l +100%FREE /dev/ocivolume/root論理ボリュームの拡張 4 xfs_growfs /XFSファイルシステムを拡張してボリュームをいっぱいにする ノート: 4つの操作はすべて冪等です。execが終了コード137を返した場合は、ブロック全体を再実行できます。出力で
EXPANSION_COMPLETEを探し、成功を確認します。 -
kubeletを再起動して、ノードが更新された割当て可能ストレージを報告し、検証してクリーン・アップします。
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 --forceノート:
nsenterコマンドは、systemdにアクセスするためにホストのPIDネームスペースを入力します。プレーンchroot /host systemctl restart kubeletは、chroot内からsystemdバスに接続できないため、失敗します。予想される出力には、合計約189 GBが表示されます。
タスク9: vLLM本番スタックのデプロイ
Helmを使用してvLLM推論スタックをインストールします。
-
vLLM Helmリポジトリを追加します。
helm repo add vllm https://vllm-project.github.io/production-stack helm repo update -
Helm値ファイルをレビューします。
production_stack_specification.yamlは、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"ノート:
openai/gpt-oss-20bモデルは、エキスパートの混合(MoE)モデルで、フォワード・パスごとに20B合計パラメータおよび3.6Bアクティブ・パラメータがあります。Apache 2.0ライセンスでリリースされているため、Hugging Faceトークンは必要ありません。vllm/vllm-openaiコンテナ・イメージは、OpenAI互換APIサーバーを提供し、クライアントが自己ホスト・エンドポイントに対して標準のOpenAI SDKコールを使用できるようにします。 -
スタックをデプロイします。ルーター・ポッドは次のステップでパッチが適用されるまでCrashLoopになるため、ここでは
--waitを使用しないでください。helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yamlvLLMエンジンポッドが開始されるまで待ちます(次にルータにパッチが適用されます)。
kubectl wait --for=condition=Ready pods -l model=gpt-oss --timeout=600sノート:エンジン・ポッドは、初回起動時にモデルの重みがダウンロードされるため、準備完了になるまでに数分かかります。ポッドが
ContainerCreatingにとどまる場合、コンテナ・イメージ(~10 GB)はまだプルされています。進行状況を確認するには、kubectl describe pod <pod-name>を使用します。 -
ルータの配置にパッチを適用します。ルーターには、GPU許容値(GPUノードが容量を持つ唯一のノードである場合にスケジュールできるように)とメモリ制限(デフォルトの500 Miが 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"} ]'ノート: OKE GPUノードには、非GPUワークロードのスケジューリングを妨げる
nvidia.com/gpu=present:NoScheduleテイントがあるため、GPU許容範囲が必要です。ルータはGPUを使用せず、どこかで実行する必要があるため、この許容値によりGPUノードでスケジュールできます。専用CPUノード・プールがあるクラスタでは、この許容値は必要ありません。 -
Helmリリースがデプロイされていることを確認します。
helm list
-
ポッドが実行されていることを確認します。
kubectl get pods予期される出力:
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
-
ポッド・ログのモデル・ロードの進行状況を確認します。
kubectl logs -f deployment/vllm-gpt-oss-deployment-vllmモデルがロードされ、サーバーがリクエストを受け入れる準備ができていることを示すメッセージが表示されるまで待ちます。
タスク10: 推論エンドポイントのテスト
デプロイメントが推論リクエストを処理していることを検証します。vLLMプロダクション・スタックは、ルーター・サービスを介してOpenAI互換APIを公開するため、OpenAI SDKクライアントまたはcurlコマンドと対話できます。
次の図は、推論リクエストのライフサイクルを示しています。クライアント・リクエストからルーターのエンジン選択ロジック、vLLMエンジンのプリフィル・フェーズとデコード・フェーズ、およびストリームされたレスポンスとして戻ります。

-
使用可能なモデルをリストして、デプロイメントが正常であることを確認します。
kubectl get svc vllm-router-serviceルーター・サービスは、デプロイされたすべてのモデルにAPIゲートウェイを提供します。クラスタはプライベート・エンドポイントを使用するため、
kubectl port-forwardを介してサービスにアクセスします。 -
ローカルマシンからルータサービスへのポート転送を開始します。新しいターミナルを開き(もう一方のターミナルで実行されているSSHトンネルを保持)、次を実行します:
kubectl port-forward svc/vllm-router-service 8080:80これにより、マシンの
localhost:8080が、クラスタ内のルーター・サービスのポート80にマップされます。セキュリティ・ノート:
kubectl port-forwardはローカルでバインドされ、サービスを公開しません。これは、要塞トンネルを介してプライベート・クラスタを使用する際にテストする最も安全な方法です。ノート: port-forwardコマンドはフォアグラウンドで実行されます。テスト中は、この端末を開いたままにしておきます。完了したら、Ctrl+Cを押して停止します。
-
別の端末で、モデル・エンドポイントを問い合せて、モデルが使用可能であることを確認します。
curl -s http://localhost:8080/v1/models | python3 -m json.tool期待される出力:
{ "object": "list", "data": [ { "id": "openai/gpt-oss-20b", "object": "model", "created": 1234567890, "owned_by": "vllm" } ] } -
テキスト入力リクエストを送信します。
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.tool期待される出力(省略形):
{ "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 } } -
チャット完了リクエストを送信します。これは、OpenAI Python SDKで使用されるものと同じ形式であり、プログラムで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.tool期待される出力(省略形):
{ "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 } }どちらのレスポンスにも、
choices配列内のモデルの生成されたテキスト、トークン使用状況統計、およびstop(モデルが自然に終了)またはlength(出力はmax_tokensで切り捨てられました)のいずれかのfinish_reasonが含まれます。ノート: APIリクエストのモデル名は、Helm値の
modelURLフィールドと一致するフル・モデル・パス(openai/gpt-oss-20b)です。OpenAI互換のクライアントは、base_urlをhttp://localhost:8080/v1に設定することで、このエンドポイントを使用できます。
-
短いリクエストのエンドツーエンドのレイテンシを測定します。
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/null単一のA10 GPUでは、短い完了に対して1-3秒のエンドツーエンドが必要です。最初のトークンまでの時間(TTFT)は通常、プロンプトの長さに応じて50から200ミリ秒です。スループットを向上させるには、Helm値の
replicaCountを増やして、ルーターの背後にあるエンジン・レプリカを追加します。
タスク11: OCIロード・バランサを介した公開(オプション)
OCIロード・バランサを介して推論エンドポイントに外部からアクセスできるようにします。
セキュリティ・ノート:これにより、デフォルトで推論APIがパブリック・インターネットに公開されます。TLS、認証(APIキー/JWT/mTLS)、IP許可リストまたはWAFコントロールを使用しない本番では、これを有効にしないでください。公開する必要がある場合は、認証およびレート制限を強制するイングレス・コントローラまたはAPIゲートウェイで前面に配置するか、内部ロード・バランサを使用します。
-
LoadBalancerタイプを使用するようにルーター・サービスにパッチを適用します。
kubectl patch svc vllm-router-service \ -p '{"spec": {"type": "LoadBalancer"}}'ノート:内部ロード・バランサが必要な場合は、OCI注釈をサービスに追加します(次の例)。これにより、エンドポイントはVCN内でプライベートに保たれます。
kubectl annotate svc vllm-router-service \ "service.beta.kubernetes.io/oci-load-balancer-internal"="true" -
外部IPが割り当てられるのを待ちます。
kubectl get svc vllm-router-service -wロード・バランサがプロビジョニングされると、予期される出力:
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
-
外部エンドポイントをテストします。
curl http://<EXTERNAL-IP>/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "openai/gpt-oss-20b", "prompt": "Hello from OCI!", "max_tokens": 50 }'
タスク12: マルチGPUテンソル並列度の構成(拡張)
ベア・メタル・シェイプ上の複数のGPUにわたり、より大きなモデルをデプロイします。
Tensor Parallelismは、1つのノード上の複数のGPU間でモデルを分割します。これは、モデルのメモリー要件が単一のGPUを超える場合に必要です。たとえば、Meta Llama 3.1 70Bには約140 GBのGPUメモリーが必要ですが、これは単一のGPUの容量を超えていますが、8x A100 80 GBまたは8x H100 GPUにまたがります。
-
Hugging Faceトークンを使用してKubernetesシークレットを作成します。Llama 3.1 70Bなどのゲート付きモデルでは、認証が必要です。
kubectl create secret generic hf-token-secret \ --from-literal=token=YOUR_HUGGINGFACE_TOKEN -
production_stack_specification.yamlをマルチ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" -
更新された値でデプロイします。タスク9と同様に、
--waitを使用しないでください。ルーターはパッチが適用されるまでCrashLoopになります。helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=llama70b --timeout=900s次に、ルーターにパッチを適用して(タスク9、ステップ4と同じ)、次のことを確認します。
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"} ]' -
ポッドが実行中で、すべてのGPUが使用中であることを確認します。
kubectl get pods kubectl logs -f deployment/vllm-llama70b-deployment-vllm
ノート:マルチGPU構成をデプロイする前に、OKEクラスタに適切なベア・メタルGPUシェイプ(
BM.GPU.H100.8など)を持つノード・プールがあることを確認してください。
タスク13: モデルに対するOCIオブジェクト・ストレージの使用(拡張)
Hugging Faceからダウンロードするのではなく、OCI Object Storageからモデルの重みをロードします。これは、プライベート・モデル、OCI内の高速ダウンロード、または外部インターネット・アクセスのない環境に役立ちます。
-
モデルの重みをOCIオブジェクト・ストレージ・バケットにアップロードします。OCIコンソールで「ストレージ」→「オブジェクト・ストレージ」にナビゲートし、まだバケットがない場合は作成します。
-
バケットの事前認証済リクエスト(PAR) URLを作成します。OCIコンソールで、バケットを選択し、「事前認証済リクエスト」をクリックして、読取りアクセス権を持つ新規リクエストを作成します。
-
PAR URLを使用するように
production_stack_specification.yamlを更新します。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" -
更新された値(
--waitなし)でデプロイします。理由については、タスク9を参照してください)。helm upgrade -i \ vllm vllm/vllm-stack \ -f production_stack_specification.yaml kubectl wait --for=condition=Ready pods -l model=custom-model --timeout=600s -
ポッド・ログをチェックして、オブジェクト・ストレージからのモデルのロードを確認します。
kubectl logs -f deployment/vllm-custom-model-deployment-vllm
タスク14: リソースのクリーン・アップ
継続料金を回避するために、デプロイされたすべてのリソースを削除します。
-
クリーンアップ・スクリプトを使用してKubernetesリソースを削除します。
cd production-stack/deployment_on_cloud/oci ./clean_up.shこれにより、Helmリリースがアンインストールされ、すべてのPersistentVolumeClaims、PersistentVolumesおよびカスタムvLLMリソースが削除されます。
-
OKEクラスタおよびすべてのOCIネットワーキング・リソースを削除します。
./entry_point.sh cleanupこれにより、次のリソースが順番に削除されます。
- GPUノード・プール
- OKEクラスタ
- 要塞ホスト(作成されている場合)
- サブネット(API、ワーカー、ロード・バランサ、要塞)
- セキュリティ・リスト
- サービス・ゲートウェイ、NAT Gatewayおよびインターネット・ゲートウェイ
- ルート表
- VCN
-
「開発者サービス」→「Kubernetesクラスタ」および「ネットワーキング」→「Virtual Cloud Networks」で、すべてのリソースがOCIコンソールで削除されていることを確認します。
./entry_point.sh cleanup
ノート:継続的な料金を回避するために、すべてのリソースが削除されていることを確認します。GPUインスタンスおよびブロック・ボリュームでは、アイドル状態でもコストがかかります。
次の手順
このチュートリアルでは、関数推論スタックをデプロイしました。本番ワークロードの場合は、次の機能改善を検討してください。
- モニタリング: vLLMは、各エンジン・ポッドのPrometheusメトリックを
/metricsで公開します。Prometheus + Grafanaスタックを接続して、最初のトークン、1秒当たりのトークン、KVキャッシュ使用率およびリクエスト・キューの深さまでの時間を追跡します。 - 自動スケーリング:カスタム・メトリック(リクエスト・キューの深さまたはDCGMを介したGPU使用率)を使用してKubernetes Horizontal Pod Autoscalerを構成し、エンジンのレプリカをロード時に自動的にスケーリングします。
- セキュリティ強化:
BASTION_CLIENT_CIDRをIP範囲に制限し、Kubernetesネットワーク・ポリシーを追加して推論ネームスペースを分離し、モデル資格証明をKubernetesシークレットではなくOCI Vaultに格納します。 - 高可用性: GPUノードを複数の可用性ドメインに分散し、Helm値の
replicaCountを増やして、ルーターがエンジン・レプリカ間でフェイルオーバーできるようにします。 - コスト最適化:使用していない場合は、
./entry_point.sh cleanupを使用します。開発/テスト・ワークロードの場合は、プリエンプティブルGPUインスタンスを検討してください。GPU使用率を監視して、ノード・プールの適切なサイズを設定します。 - Infrastructure as Code:このデプロイメントを、繰返し可能なバージョン管理型のインフラストラクチャのOCI TerraformプロバイダまたはOCI Resource Managerでコーディングします。
関連リンク
確認
- 著者 - Federico Kamelhar氏(Agentic AI担当シニア・プリンシパル・アーキテクト)
その他の学習リソース
docs.oracle.com/learnの他のラボを調べるか、Oracle Learning YouTubeチャネルで無料のラーニング・コンテンツにアクセスしてください。また、Oracle Learning Explorerになるには、education.oracle.com/learning-explorerにアクセスしてください。
製品ドキュメントについては、Oracle Help Centerを参照してください。
Deploy OpenAI vLLM Production Stack on Oracle Kubernetes Engine (OKE)
G50927-01