2026年7月16日投稿 by Michael Troutman, LINBIT

この投稿で紹介するCNCFプロジェクト

Kubernetes logo llm-d logo Piraeus Datastore logo

大規模言語モデル(LLM)ワークロードを自社で運用することは、チームがマネージドAPIサービスと併用するパターンのひとつです。マネージドAPIサービスは便利で、多くのワークロードに適しています。自社ホスティングは、高リクエスト量でのコスト予測可能性、より優れたレイテンシ制御、契約上または規制上の要件を満たすためのデータ所在地の管理など、いくつかの理由で選択される補完的なオプションです。ハイブリッドアプローチ(高ボリュームまたは機密性の高いワークロードにはオープンソースモデルをローカルで実行し、それに適したタスクにはマネージドAPIコールを使用する)は、両方を組み合わせた実用的な中間案です。この記事では、Kubernetesラボ環境でそのようなセルフホスト型推論スタックをセットアップするために必要な手順を説明します。この例では、推論にvLLM、永続ストレージにLINSTOR®を使用しています。

vLLMの背景

vLLMは、大規模言語モデル向けの高性能オープンソース推論エンジンです。クラスター環境で多数の同時リクエストを効率的に処理できるように設計されています。

このユースケースにおけるvLLMの重要な特徴は、OpenAI互換のREST APIを公開している点です。OpenAI APIとすでに連携しているもの(LangChain、LlamaIndex、またはOpenAI SDKを呼び出す独自のコード)は、URLを変更するだけでセルフホスト型vLLMインスタンスを指すことができます。この互換性により、前述のハイブリッドアーキテクチャが実現可能な代替手段となります。

セットアップの概要

本記事の手順では、LINSTORがLINSTOR Container Storage Interface(CSI)ドライバーを介して永続ストレージを提供するKubernetesクラスターを使用します。Kubernetesはスタック全体のオーケストレーションレイヤーであり、ストレージ統合はクラウドネイティブエコシステム全体で使用される標準的なCSIに従います。LINSTORはDRBD®を基盤とするオープンソースのソフトウェア定義ストレージソリューションで、ノード間でレプリケートされたブロックストレージを提供します。レプリケートストレージは、ポッドの再起動やノード障害に耐える必要がある大規模なモデル重みファイルの保存に適しています。

このブログで使用するモデルはmeta-llama/Llama-3.2-1B-Instructで、Metaが提供する、ユーザーの指示に従うようファインチューニングされた小型ながら高性能なモデルです。パラメータ数が10億のため、専用GPUノードのないラボでもCPU上で実行可能(重要)であり、セットアップのテストに十分な実用性があります。

前提条件

Kubernetesに何かをデプロイする前に、モデル自体へのアクセスが必要です。MetaのLlamaモデルはHugging Faceでゲートされており、ダウンロードする前にアクセスをリクエストする必要があります。

  1. huggingface.coでアカウントを作成します。
  2. Llama-3.2-1B-Instructモデルページに移動してアクセスリクエストを送信します。
  3. 承認後、Hugging Faceアカウント設定に移動し、read権限を持つアクセストークンを作成します。
  4. 次のセクションで必要になるため、そのトークンを手元に保管しておきます。

また、LINSTORをKubernetesにデプロイし、KubernetesワークロードがPersistentVolumeClaimsを要求するためのStorageClassを用意する必要があります。オープンソースのPiraeus Operatorは、LINSTORとLINSTOR CSIドライバーをKubernetesにデプロイし、セットアップ方法を文書化しています。次のセクションでは、linstor-csi-lvm-thin-r2という名前のLINSTOR StorageClassからPersistentVolumeClaimを作成します。

デプロイ

デプロイは3つのKubernetesリソースで構成されます。モデルストレージ用のPersistentVolumeClaim、Hugging Faceトークン用のSecret、推論サーバーを実行するためのDeploymentとServiceです。

PVCとSecretの作成

PVCはlinstor-csi-lvm-thin-r2ストレージクラスを使用し、クラスター全体に2つのレプリカを持つシンプロビジョニングされたLVMボリュームをプロビジョニングします。これにより冗長性とディスクスペースの効率的な利用が提供され、モデル重みファイルが数十ギガバイトを簡単に消費する可能性がある場合に重要です。

コンテナのmountPathは/root/.cache/huggingfaceです。ここにvLLMコンテナがダウンロードしたモデル重みをキャッシュします。このパスを永続ボリュームでバックアップすることで、モデルはHugging Faceから1回だけダウンロードされ、その後のポッド再起動ではLINSTORが提供する永続ストレージに保持されているため、ダウンロードを完全にスキップできます。

以下のコマンドを入力してPVCとHugging FaceトークンSecretを作成します。

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: vllm-models
spec:
  storageClassName: linstor-csi-lvm-thin-r2
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 50Gi
---
apiVersion: v1
kind: Secret
metadata:
  name: hf-token-secret
type: Opaque
stringData:
  token: "REPLACE_WITH_YOUR_TOKEN"
EOF

IMPORTANT: この設定を適用する前に、REPLACE_WITH_YOUR_TOKENを実際のHugging Faceアクセストークンに置き換え、StorageClass名が異なる場合は変更してください。

vLLMのデプロイ

以下のコマンドを入力して、vLLM Kubernetesデプロイメントドキュメントを基にしたvLLM推論サーバーとそのサービスをデプロイします。

VLLM_IMAGE=public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:latest
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: vllm
  template:
    metadata:
      labels:
        app.kubernetes.io/name: vllm
    spec:
      containers:
      - name: vllm
        image: $VLLM_IMAGE
        command: ["/bin/sh", "-c"]
        args: [
          "vllm serve meta-llama/Llama-3.2-1B-Instruct --gpu-memory-utilization 0.80"
        ]
        env:
        - name: HF_TOKEN
          valueFrom:
            secretKeyRef:
              name: hf-token-secret
              key: token
        ports:
          - containerPort: 8000
        volumeMounts:
          - name: llama-storage
            mountPath: /root/.cache/huggingface
      volumes:
      - name: llama-storage
        persistentVolumeClaim:
          claimName: vllm-models
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-server
spec:
  selector:
    app.kubernetes.io/name: vllm
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  type: ClusterIP
EOF

📝 NOTE: CPUのみのデプロイメントでも、vLLMはリクエスト処理のためにどれだけ積極的にメモリを予約するかを制御する--gpu-memory-utilizationフラグを使用します。このフラグがない場合、vLLMは利用可能メモリの92%をデフォルトで使用するため、テスト環境で起動に失敗しました。0.80に設定することで、エンジンが正常に初期化できる十分な余裕が私の環境で得られました。

ログの監視

初回起動には数分かかります。vLLMはHugging Faceからモデル重みをダウンロード(このモデルでは約2.5GB)し、エンジンを初期化する必要があります。ログを追跡するには以下のコマンドを入力します。

kubectl logs -f deployment/vllm-server

「INFO: Application startup complete」のようなメッセージが表示されたら、テストの準備が整いました。

デプロイメントのテスト

クラスター内からテストする最も簡単な方法は、使い捨てのcurlポッドをデプロイすることです。以下のコマンドを入力して作成します。

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl-client
  namespace: default
spec:
  containers:
  - name: curl
    image: curlimages/curl:latest
    command: ["sleep", "infinity"]
  restartPolicy: Never
EOF

ポッド内でインタラクティブシェル環境を開始します。

kubectl exec -it curl-client -- sh

クラスター内DNS名を使用してvLLMサービスにリクエストを送信します。

curl http://vllm-server.default.svc.cluster.local:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-3.2-1B-Instruct",
    "messages": [
      {"role": "user", "content": "What is the capital of France?"}
    ]
  }'

すべてが正常に動作している場合、以下のような馴染みのあるOpenAI形式のレスポンスが返されます。

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "The capital of France is Paris."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 15,
    "completion_tokens": 9,
    "total_tokens": 24
  }
}

テストが完了したら、curlポッドを削除します。

kubectl delete pod curl-client

デプロイメントの一時停止

PVCや設定を失わずにトラブルシューティングのためにデプロイメントを一時停止する必要がある場合は、レプリカを0にスケールします。

kubectl scale deployment vllm-server --replicas=0

💡 TIP: デプロイメントを0にスケールすると、PVCとキャッシュされたモデル重みは保持されます。デプロイメントを再度スケールアップしても、モデルを再ダウンロードする必要はありません。

元に戻すには以下を実行します。

kubectl scale deployment vllm-server --replicas=1

結論

このラボセットアップは、レプリケートされたLINSTOR管理の永続ストレージに支えられ、標準的なOpenAI互換APIを通じてアクセス可能な、Kubernetes上で動作するセルフホスト型LLMの基本パターンを示しています。モデル重みをLINSTORボリュームにキャッシュすることで、ポッドの再起動が高速になり、DRBDバックアップのレプリケーションにより、ボリュームが単一障害点にならないことが保証されます。

ここから、専用GPUノードをクラスターに追加して大幅にパフォーマンスを向上させたり、独自のプライベートデータでモデルをさらにトレーニングして特定のユースケースに適したものにしたりすることができます。ハイブリッドAIを目指す場合、自然な次のステップは、ローカルデプロイメントとマネージドAPIの間でコスト、レイテンシ、または機能要件に基づいてリクエストを振り分ける推論ルーターを導入することです。llm-dプロジェクトは、まさにこの種のセットアップ向けに設計されたKubernetes用のオープンソースリクエストルーターです。これらの次のステップは、このラボで扱った設定ではなく、さらなる探求のための方向性です。

vLLMやLINSTORを使用したKubernetesでのコンテナ化アプリケーションの実行、またはLINBIT®ソフトウェアのその他のユースケースについてご質問がある場合は、LINBIT Community Forumにご参加ください。