KubeAIによるKubernetes環境でのLLM推論システムの構築

先日初めて知ったのですが、Kubernetes上で生成AIモデル(LLM)による推論システムを構築する際に便利なKubernetes Operatorがあるようです。

AI Inference Operator for Kubernetes. The easiest way to serve ML models in production. Supports VLMs, LLMs, embeddings, and speech-to-text.

比較的新しいプロダクトということもあって、まだ日本語記事がほぼ無いのと、AIに聞いてもプロダクト概要程度しかわからないようなので、自身で調査・構築してみることにしました。
(※ Kubernetes自体の知識についてはここでは前提として省略します)

2025年7月現在、KubeAIはCNCFのSandboxプロジェクトとして提案されており、まだ非常に開発が活発な段階にあるので公式ドキュメントやインストール方法は変更される可能性が高いかもしれません。

KubeAIとは

KubeAIは、高性能なLLM推論ライブラリ/サーバ実装であるvLLMをKubernetes上にデプロイ・運用するためのKubernetes Operatorです。vLLMの機能をKubernetesのオーケストレーション能力と組み合わせることで、LLMの推論サービスを大規模かつ効率的に提供することを目指して開発されています。vLLM対応をアピールしていますが、他にはOllamaサーバもサポートされているようです。個人でローカルLLM使う時にはOllammaを使うことはそこそこあるかもしれませんが、プロダクション用途だとvLLMの方が採用されるのではないでしょうか。

KubeAIはKubernetes Operatorとして提供されているので、モデルサーバーとしてのPod構成やライフサイクル(デプロイ、スケーリング、アップグレード、ヘルスチェックなど)をk8sのスケジューラと各種コントローラが全て管理してくれます。さらに、ロードバランシング機能はKubeAI固有の工夫が入っているようで、標準のKubernetes Serviceのロードバランシング機能は、vLLMのようにKVキャッシュの状態によってパフォーマンスが大きく左右されるステートフルなモデルには適していません。KubeAIでは推論パフォーマンスを向上させるために最適化されたロードバランシング機能を提供する独自のプロキシが組み込まれているそうです↓

また、NVIDIA GPUだけでなくAMDやIntelのGPU、AWS Trainiumなどマルチハードウェア環境で動作します。最近はAMDのMI300シリーズは有力視されているので期待できますね。もちろんCPU環境でも動くので、GPUサーバが手元に無くても開発環境や動作確認用途ではCPUモードを使うという選択ができます。

アプリケーション統合の文脈だと、KubeAIはOpenAI API互換性(/v1/chat/completions/v1/embeddings等のエンドポイント)を提供しています。これによりOpenAI APIを使用するように構築された既存のアプリケーションを、Kubernetes上でホストされている独自のモデルに簡単に切り替えることができます。とはいってもvLLM本体にOpenAI API互換があるのでKubeAI固有のアピールポイントではないですが、OpenAI API互換があるかどうかはアプリケーション開発者にとっては重要かと思います。

vLLM

vLLMの紹介も軽くしておきます。LLM推論サーバとしてはONNX RuntimeやNVIDIAのTriton Inference Serverなども有名で実績がありますが、vLLMはPagedAttensionによる効率的なキャッシュ機構を備えており性能面において特に優秀で、それだけでなくOpenAI API互換やHuggingFace Modelsとの連携機能などのアプリケーション統合面でも扱いやすいので採用実績が増えてきているプロダクトです。

vLLMの素晴らしさはもっといろいろあるのでアピールしたいところではありますが、このエントリーはKubeAIについての紹介なのでここでは省略します。

KubeAIの導入

KubeAIはKubernetes Operatorとして提供されているので当然ながらk8sのCRD(Custom Resource Definition)操作権限が必要です。自分でk8sクラスタ管理していない場合はAdminに依頼するか、CRD操作のRoleを付与してもらう必要があります。ここではオンプレ上のk8sクラスタに構築してみますが、もちろんパブリッククラウドのマネージドKubernetesサービス(AKS/EKS/GKE)への導入も簡単です。

Kubernetes:
  Client Version: v1.33.3
  Server Version: v1.33.1
  Kustomize Version: v5.6.0
  Helm Version: v3.18.4
GPU:
  NVIDIA A100-SXM4

Operatorのインストールは公式のhelmチャートがあるのでhelmでインストールするのが一番推奨されます。動作確認目的なので一時的なNamespaceを作ってそこで作業します。

# Namespaceの作成
$ kubectl create ns kubeai
$ kubectl config set-context --current --namespace=kubeai
# HelmでKubeAIのインストール
$ helm repo add kubeai https://www.kubeai.org
$ helm repo update
$ helm install kubeai kubeai/kubeai
NAME: kubeai
LAST DEPLOYED: Fri Jul 25 07:08:54 2025
NAMESPACE: kubeai
STATUS: deployed
REVISION: 1
TEST SUITE: None

上記のhelm installで以下のリソースが作成されました。

$ kubectl get deploy
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
kubeai   1/1     1            1           12m
$ kubectl get po
NAME                      READY   STATUS    RESTARTS   AGE
kubeai-7dc7f588c5-jdjjp   1/1     Running   0          12m
open-webui-0              1/1     Running   0          12m
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubeai       ClusterIP   10.110.209.73           80/TCP    12m
open-webui   ClusterIP   10.108.51.145           80/TCP    12m

どうやらOpen WebUIのPodもデフォルトで付いてくるようです。便利ですが必須ではない気はしますね。

LLMモデルのインストール

KubeAIはModel管理用のOperatorも提供しており、LLMモデルもHelm経由でインストールできます。KubeAIはHuggingFaceと直接連携できるのでトークンを設定しておくと便利です。

$ export HF_TOKEN=huggingface token here...
$ helm upgrade --install kubeai kubeai/kubeai --set secrets.huggingface.token=$HF_TOKEN
# Secret登録確認
$ kubectl get secret
NAME                           TYPE                 DATA   AGE
kubeai-huggingface             Opaque               1      11m

新しいCRDがインストールされているので内容を確認しておきます。

$ kubectl get crd models.kubeai.org
NAME                CREATED AT
models.kubeai.org   2025-07-25T07:08:55Z
$ kubectl explain models.kubeai.org
GROUP:      kubeai.org
KIND:       Model
VERSION:    v1

DESCRIPTION:
    Model resources define the ML models that will be served by KubeAI.
    
FIELDS:
  apiVersion    string
    APIVersion defines the versioned schema of this representation of an object.
    Servers should convert recognized schemas to the latest internal value, and
    may reject unrecognized values. More info:
    https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

  kind  string
    Kind is a string value representing the REST resource this object
    represents. Servers may infer this from the endpoint the client submits
    requests to. Cannot be updated. In CamelCase. More info:
    https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

  metadata  objectmeta  
    Standard object's metadata. More info:
    https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

  spec  object
    ModelSpec defines the desired state of Model.

  status  object
    ModelStatus defines the observed state of Model.

Modelというリソースが追加されており、モデル定義用のマニフェストファイルを作って登録することになります。ここではELYZAの8Bモデルを使います。HuggingFaceのアクセストークンは前述で作ったSecretを指定しておきます。

apiVersion: kubeai.org/v1
kind: Model
metadata:
  name: llama-3-elyza-jp-8b
  namespace: kubeai
spec:
  features: [TextGeneration]
  url: hf://elyza/Llama-3-ELYZA-JP-8B
  engine: VLLM
  args:
    - --trust-remote-code
    - --enable-chunked-prefill
    - --max-model-len=8192
    - --dtype=auto
    - --max_num_batched_tokens=1024
    - --gpu-memory-utilization=0.9
    - --tensor-parallel-size=1
    - --disable-log-requests
  envFrom:
    - secretRef:
        name: kubeai-huggingface
  scaleDownDelaySeconds: 300
  targetRequests: 10
  minReplicas: 1
  resourceProfile: nvidia-gpu-a100-80gb:1

Modelリソースの仕様は以下公式の仕様書を参考に作りましょう。
https://www.kubeai.org/reference/kubernetes-api/#model

作成したリソースを登録・確認します。

$ kubectl apply -f elyza-model.yaml
model.kubeai.org/llama-3-elyza-jp-8b created

$ kubectl get model
NAME                  AGE
llama-3-elyza-jp-8b   6m13s

# 推論サーバ(vLLM)のPodを確認
$ kubectl get po
NAME                                        READY   STATUS    RESTARTS   AGE
kubeai-7dc7f588c5-cpr27                     1/1     Running   0          6h12m
model-llama-3-elyza-jp-8b-f664d47f9-bsgkq   1/1     Running   0          4m56s
open-webui-0                                1/1     Running   0          6h12

ちなみに、モデルはHuggingFaceからダウンロード取得していますが、k8sのPersistentVolume(PV)上に置いてあるモデルも読み込むことができます。

動作確認

ここまでの手順でKubernetesにvLLMの推論サーバが稼働してLlama-3-ELYZA-JP-8Bのモデルが読み込まれたことになるので、次はクライアントとして動作確認しておきます。前述の通り、KubeAIにはOpen WebUIがおまけで付属しているので簡単に動作確認できます。Open WebUIのServiceはClusterIPで作成されるので外部から接続するためにポートフォワードします。Service定義を編集してNodePortに変えても良いかと思います。

$ kubectl port-forward svc/open-webui 8000:80

ブラウザからlocalhost:8000にアクセスして動作確認します。

問題なく動きました。同様にkubeai Serviceに対してプログラムから動作確認します(ポートフォワード設定は割愛)。適当なHTTPクライアントでも確認できますが、OpenAI API互換があるのでopenaiライブラリを使うのが楽かと思います。

from openai import OpenAI

# OpenAIクライアントを初期化
# vLLMサーバーのURLをbase_urlに指定
# vLLMは認証を必要としないためapi_keyはダミーの文字列でOK
BASE_URL="http://localhost:8000/openai/v1"
client = OpenAI(base_url=BASE_URL, api_key="vllm")

def request_chat_completion_streaming():
    """
    KubeAIのChat Completions APIをストリーミングモードで呼び出す
    """
    print("\n--- Chat Completions ストリーミングリクエスト ---")
    
    # OpenAIクライアントを初期化
    # vLLMサーバーのURLをbase_urlに指定, 認証を必要としないためapi_keyはダミーの文字列でOK
    BASE_URL="http://localhost:8000/openai/v1"
    client = OpenAI(base_url=BASE_URL, api_key="vllm")
    
    try:
        content = "仕事の熱意を取り戻すためのアイデアを5つ挙げてください"
        stream = client.chat.completions.create(
            model="llama-3-elyza-jp-8b",  # 注意: HuggingFace上のモデル名ではなく、KubeAIのModelリソースで定義した名前を指定すること
            messages=[
                {"role": "user", "content": content }
            ],
            max_tokens=500,
            temperature=0.8,
            stream=True
        )

        print("生成されたテキスト:")
        for chunk in stream:
            # completions APIの .text ではなく、chat completions APIの .delta.content を使用
            content = chunk.choices[0].delta.content
            if content:
                print(content, end='', flush=True)
        print()

    except Exception as e:
        print(f"error: {e}")


if __name__ == "__main__":
    request_chat_completion_streaming()

* 出力結果

--- Chat Completions ストリーミングリクエスト ---
生成されたテキスト:
仕事の熱意を取り戻すためのアイデアを5つ挙げます。

1. 「なぜこの仕事をしているのか」という原点に戻る:
仕事の目的や意味を再認識することで、モチベーションが高まります。自身の成し遂げたいことや、仕事を通じて実現したいビジョンを思い出してみま
  ょう。

2. 目標を再設定する:
現在の目標や、達成すべき数字などを再確認します。目標が曖昧な場合、モチベーションが下がる要因になるため、明確な目標を設定することで、熱意
  戻る可能性があります。

3. 新しいスキルや知識を習得する:
新しいことを学ぶと、脳が活性化し、仕事に対する熱意が高まります。研修やセミナーに参加、書籍やオンラインコースで学ぶなど、自己投資をしてみ
  しょう。

4. 職場の環境を整える:
職場の環境が整っていないと、仕事の熱意が下がる要因になることがあります。整理整頓、作業スペースの改善、コミュニケーションの改善など、環境
  整えることで、仕事のパフォーマンスが向上する可能性があります。

5. 「小さな成功体験」を積み重ねる:
小さな成功体験を積み重ねることで、自信がつき、仕事の熱意が高まります。具体的には、日々の小さな目標を達成する、タスクを完了する、など小さ
  成功体験を積み重ねることで、モチベーションが高まる可能性があります。

以上のアイデアを試し、仕事の熱意を取り戻すことができます。

こちらも問題ありません。

おわりに

今回はKubeAIの使い方を紹介しました。k8s上にLLM推論システムを構築するプロダクション需要は大きいかと思いますが、vLLMサーバをk8s上に構築することで推論サービスを大規模かつ効率的に展開することが可能になります。さらにKubeAIのModelカスタムリソースによってOllamaのような簡易的なモデル管理機能を備えていたり、Open WebUIがデフォルトで付属するのでプロンプトの検証など動作確認も簡単です。

手作業でマニフェストファイルを全部揃えることで似たような環境を実現することはできますが、KVキャッシュ効率を考慮したKubeAI Proxyと同等のものを作るのは大変ですし、構築時間がかかるだけでなく保守運用コストもかさんでしまいます。

KubeAIはまだ機能は少ないですが、対応フレームワークも今後は増えていくでしょうし、Kubernetes + LLMの構成を作るときの第一候補になるポテンシャルはあると思っています。個人的にはモデル管理機能がもっと洗練されると嬉しい気はしますね。KubeAIがCNCFに無事採択されることを期待しています。

あわせて読む:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です