KubernetesネイティブのAI推論オペレーターKubeAIによる運用実践

1. 背景と目的:なぜKubernetes上でAI推論を管理する必要があるのか

大規模言語モデル(LLM)や音声認識モデルを本番環境にデプロイする際、多くのエンジニアが直面する課題があります。開発環境では正常に動作していたモデルが、Kubernetesクラスターに移行すると、GPUリソースの割当不均衡、モデル起動遅延、リクエストキューの肥大化、スケーリングの非効率性といった問題が発生します。特にvLLMやOllamaなど複数の推論エンジンを併用する場合、それぞれの設定差異に対応するYAMLマニフェストの作成は非常に煩雑です。

こうした運用負荷を軽減するために登場したのがKubeAIです。これは単なる推論フレームワークではなく、Kubernetes上で動作するカスタムオペレーター(Operator)であり、AIモデルのライフサイクル管理を宣言的に自動化します。ユーザーは「Qwen2-7Bを2つのGPUで実行し、負荷に応じてスケールイン/アウト可能」といった意図をCRD(Custom Resource Definition)として記述するだけで、背後でのリソース確保、Podのスケジューリング、モデルのダウンロード、ロードバランシングなどの一連の処理が自動で行われます。

最大の特徴は、**状態を持つLLMワークロードに特化した最適化**です。標準的なKubernetes Serviceのラウンドロビン方式では、会話のコンテキスト(KVキャッシュ)が分散され、再計算コストが増大します。KubeAIはこの問題を解決するため、「プレフィックス認識型ロードバランサー」を内蔵しており、同一セッションのリクエストを常に同じバックエンドPodにルーティングすることで、キャッシュヒット率を向上させ、初回トークン生成時間(TTFT)を大幅に短縮します。これは高スループットかつ低遅延が求められるプロダクション環境において極めて重要な機能です。

2. アーキテクチャの核心:ProxyとControllerの協調動作

KubeAIは二つの主要コンポーネントから構成されており、関心の分離(Separation of Concerns)に基づいた設計になっています。

2.1 モデルプロキシ:スマートなフロントエンドゲートウェイ

モデルプロキシは、外部からのすべてのAPIリクエストを受け取るエントリポイントです。OpenAI API互換のエンドポイント(/v1/chat/completionsなど)を提供し、クライアントアプリケーションの変更なしに既存システムに統合できます。

  • コンテキスト保持型ロードバランシング:リクエスト内の特定フィールド(例:session_iduser_id)をキーとしてハッシュ計算を行い、同じキーを持つリクエストを常に同一のモデルインスタンスに転送します。これにより、KVキャッシュの有効活用が可能となり、繰り返しのコンテキスト再計算が削減されます。
  • ゼロスケール対応キューイングminReplicas: 0の設定時、モデルPodが存在しない状態でもリクエストは内部キューに蓄積されます。その後、オペレーターがPodの起動をトリガーし、準備完了後にキュー内のリクエストが順次処理されます。
  • マルチバックエンド抽象化:vLLM、Ollama、Faster-Whisperなどの異なる推論エンジンを背後で操作しながら、クライアントには統一されたRESTインターフェースを提供します。

2.2 モデルコントローラー:Kubernetesネイティブなオートメーション

モデルコントローラーは、Modelというカスタムリソース(CRD)の状態を監視し、望ましい状態(Desired State)と実際の状態(Actual State)の差分を解消する役割を担います。これは典型的なKubernetes Operatorのパターンです。

  • モデル資産の管理:Hugging FaceやS3バケットなど指定されたURIからモデルファイルを自動でダウンロードし、共有ストレージ(PVC)に配置します。複数のPod間でモデルを共有できるため、起動時の重複ダウンロードが不要になります。
  • ダイナミックスケーリング:HPA(Horizontal Pod Autoscaler)と連携し、CPU使用率やリクエストスループット(RPS)に基づいてPodの数を自動調整します。スケールイン時は待機中のリクエストを考慮して適切なタイミングで実行されます。
  • アダプターのライフサイクル制御:LoRAやAdapterのような微調整モジュールについても、その取得・マウント・切り替えを宣言的に管理可能です。これにより、A/Bテストやマルチテナント環境での柔軟な展開が実現します。

2.3 コンポーネント間のデータフロー

クライアント → [KubeAI Proxy] ⇄ (gRPC/HTTP) ⇄ [Model Controller]
                     ↓
           [Model Pods (vLLM/Ollama等)]
                     ↑
             [Model CRD ステータス]

プロキシはリアルタイムで利用可能なモデルエンドポイントをコントローラーから取得し、健全性チェックを経てルーティングを行います。また、スケールイベントやエラー状態は双方向で通信され、システム全体の整合性が保たれます。

3. 実践:Minikubeを使った最小構成での導入

ローカル環境でKubeAIの基本動作を確認する手順です。Minikubeを使用して簡易クラスターを構築します。

3.1 環境セットアップとインストール

# Minikubeクラスターの起動(8GB RAM, 4コア)
minikube start --memory=8192 --cpus=4 --driver=docker

# KubeAI Helmリポジトリの追加
helm repo add kubeai https://www.kubeai.org
helm repo update

# kubeai名前空間に全コンポーネントをインストール
helm install kubeai kubeai/kubeai -n kubeai --create-namespace --wait

インストール後、以下のコマンドでステータスを確認します。

kubectl get pods -n kubeai
# 出力例:
# kubeai-controller-manager-7d6f8c9b4-pq2xk   1/1     Running
# kubeai-proxy-5f9d7b6c84-kz8w2                1/1     Running

3.2 モデルの定義と展開

ここで、CPUベースの小型モデルをデプロイする例を示します。設定ファイルlocal-models.yamlを作成します。

catalog:
  tiny-llm-cpu:
    enabled: true
    engine: Ollama
    url: 'ollama://tinyllama'
    features: [TextGeneration]
    minReplicas: 1
    resourceProfile: 'cpu-small'
  embedding-cpu:
    enabled: true
    engine: Infinity
    features: [Embeddings]
    minReplicas: 0
    resourceProfile: 'cpu-small'

これをHelmで適用します。

helm install my-deploy kubeai/models -n kubeai -f local-models.yaml

数分後、kubectl get models -n kubeaiコマンドでモデルの状態がReadyになることを確認できます。

3.3 クライアント接続のテスト

プロキシサービスをローカルにポートフォワードし、Pythonクライアントで接続を試みます。

kubectl port-forward svc/openai -n kubeai 8080:80

Pythonコード:

import requests

base_url = "http://localhost:8080/v1"
headers = {"Authorization": "Bearer dummy", "Content-Type": "application/json"}

# チャット補完リクエスト
chat_payload = {
    "model": "tiny-llm-cpu",
    "messages": [{"role": "user", "content": "こんにちは!元気?"}]
}
resp = requests.post(f"{base_url}/chat/completions", json=chat_payload, headers=headers)
print(resp.json()["choices"][0]["message"]["content"])

# 埋め込み生成
embed_payload = {
    "model": "embedding-cpu",
    "input": "KubeAIは素晴らしい"
}
embed_resp = requests.post(f"{base_url}/embeddings", json=embed_payload, headers=headers)
print("Embedding dim:", len(embed_resp.json()["data"][0]["embedding"]))

4. 生産環境向けの高度な設定

本番稼働を想定した重要な設定項目を紹介します。

4.1 GPUリソースの指定とスケジューリング

GPUモデルの場合、リソース要件を正確に定義する必要があります。

apiVersion: ai.kubeai.com/v1alpha1
kind: Model
metadata:
  name: llama3-8b-gpu
spec:
  engine: VLLM
  url: 'huggingface://meta-llama/Meta-Llama-3-8B-Instruct'
  minReplicas: 1
  resourceProfile: 'nvidia-a100:1'  # 事前定義プロファイル
  # または個別に指定
  resources:
    limits:
      nvidia.com/gpu: 1
      memory: 40Gi
    requests:
      cpu: 4
      memory: 40Gi

4.2 スマートオートスケーリングの設計

AIワークロードに適したスケーリングポリシーを設定します。

spec:
  minReplicas: 0
  maxReplicas: 8
  scaling:
    metrics:
      - type: External
        external:
          metric:
            name: kubeai_requests_per_second
          target:
            type: AverageValue
            averageValue: "30"
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 300
        policies:
          - type: Percent
            value: 30
            periodSeconds: 60

この設定により、急激なトラフィック変化に対して過剰反応せず、冷間起動の影響を最小限に抑えながら安定したスケーリングが可能になります。

4.3 永続ストレージとモデルキャッシング

大規模モデルのダウンロード時間を回避するため、共有ストレージの利用が不可欠です。

spec:
  cache:
    enabled: true
    size: 200Gi
    storageClassName: aws-efs-sc  # ReadWriteMany対応ストレージ

storageClassNameにはNFS、EFS、Filestoreなど複数のPodから同時に読み取り可能なRWX(ReadWriteMany)モードをサポートするストレージクラスを指定します。

4.4 LoRAアダプターによるA/Bテスト

一つの基底モデルに対して複数の微調整済みアダプターを動的に切り替える設定例です。

spec:
  engine: VLLM
  url: 'huggingface://meta-llama/Llama-3-8B'
  adapters:
    - name: customer-support-lora
      source:
        http:
          url: "https://models.example.com/lora/support-v1.safetensors"
      mountPath: /adapters/support
    - name: sales-lora
      source:
        http:
          url: "https://models.example.com/lora/sales-v2.safetensors"
      mountPath: /adapters/sales

クライアントはリクエスト時にextra_body.adapter_name=sales-loraを指定することで、該当するアダプターを即座に適用できます。

5. 運用監視とトラブルシューティング

本番環境における信頼性を確保するための手法です。

5.1 高可用性構成

コントローラーとプロキシを複数インスタンスで構成し、単一障害点を排除します。

controllerManager:
  replicaCount: 3
proxy:
  replicaCount: 3
  autoscaling:
    enabled: true
    minReplicas: 2

5.2 監視指標の収集

Prometheusとの統合により、以下の指標を取得可能です。

  • kubeai_proxy_request_duration_seconds:P95/P99レイテンシ
  • kubeai_model_replicas:各モデルのアクティブなレプリカ数
  • vllm_running_requests:vLLM内部の進行中リクエスト数

Grafanaダッシュボードにこれらの指標を可視化し、SLA違反や異常な挙動を早期に検出します。

5.3 主なトラブルシューティング手順

PodがPendingのままになる場合
kubectl describe pod <pod-name>でイベントログを確認。主な原因は「Insufficient nvidia.com/gpu」やPVCの未バインドです。ノードのリソース状況やStorageClassの設定を見直してください。

リクエストがタイムアウトする場合
まずプロキシとモデルPodの両方のログを確認。kubectl logs deployment/kubeai-proxyで「no healthy upstream」などのメッセージがないか調べます。また、モデルの起動ログでModel loading completedが出力されているかも重要です。

性能が期待通りに出ない場合
nvidia-smiでGPU利用率を確認。低ければ、リクエストのバッチサイズが小さいか、プレフィックス認識ルーティングが正しく機能していない可能性があります。セッションIDの伝播が保たれているかを検証してください。

6. エコシステムとの統合

KubeAIは他のクラウドネイティブ技術と連携可能です。

6.1 Istioとの統合

Istio VirtualServiceを使って外部ドメインからKubeAIプロキシにルーティングできます。

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
spec:
  hosts: ["api.ai.example.com"]
  http:
    - route:
        - destination:
            host: kubeai-proxy.kubeai.svc.cluster.local

6.2 Kafkaイベント駆動連携

推論結果を非同期にKafkaトピックに送信する機能を備えています。

eventSinks:
  - type: kafka
    brokers: ["kafka-0:9092", "kafka-1:9092"]
    topic: inference-results
    eventType: completion

これにより、バッチ処理パイプラインやリアルタイム分析システムと連携できます。

6.3 カスタム推論エンジンの統合

独自の推論サーバーを「エンジンプラグイン」として統合可能です。必要な条件は:

  • 環境変数KUBEAI_MODEL_URLからモデルパスを取得
  • /healthエンドポイントでヘルスチェックを実装
  • OpenAI形式またはgRPC形式のAPIを提供

これにより、社内専用の最適化ランタイムや特殊ハードウェア向けの推論をKubeAIの枠組みで管理できます。

タグ: KubeAI Kubernetes LLM オペレーター プロキシ

6月9日 20:38 投稿