クラウドネイティブAIデプロイ完全ガイド:Docker+K8s+GPUスケジューリング

技术架构本番環境の AI Agent

2026年、AIをクラウドに載せなければ存在しないのと同じ

モデル訓練はラボで、推論サービスはクラウドで——これがAIエンジニアリングの鉄則。

過酷な現実:7BモデルはノートPCで爆速だが、本番環境ではGPUスケジューリングの誤設定、コンテナ設定エラー、オートスケールの不具合で全面崩壊。AIデプロイはDockerfileを書くだけでは済まない。

クラウドネイティブAIデプロイ全景図

┌─────────────────────────────────────────────────────────────────────┐
│              クラウドネイティブAIデプロイ全体アーキテクチャ             │
├─────────────┬─────────────┬──────────────┬─────────────────────────┤
│ モデルサービ │ ベクトルSvc │ マイクロSvc  │   インフラ層            │
│             │             │              │                         │
│ vLLM/Triton │ Milvus      │ Spring Boot  │ K8s + GPU Operator     │
│ TGI/Ollama  │ Elastic-    │ AI Gateway   │ NVIDIA MIG + タイムスライス│
│ モデルルート │ search      │ Ingress      │ Prometheus + Grafana    │
│ HPAスケール │ Embedding   │ Service Mesh │ ArgoCD + Kustomize     │
├─────────────┴─────────────┴──────────────┴─────────────────────────┤
│                Docker コンテナ化 + K8s オーケストレーション          │
│     マルチステージビルド · GPU認識スケジューリング · 弾性スケール    │
│                        · GitOps デリバリ                            │
└─────────────────────────────────────────────────────────────────────┘

AIデプロイの3つの課題

課題1:GPU希少性——計算力は硬通貨

┌──────────────────────────────────────────────────┐
│           GPUリソース需給ギャップ                   │
│                                                    │
│   需要:100推論サービス × 1 GPU = 100 GPU           │
│   現実:クラスタは 8 × A100 = 8 GPU のみ            │
│   不足:92 GPU ❌                                  │
│                                                    │
│   ┌──────┐  MIGスライス  ┌──────────────┐          │
│   │ A100 │ ────────→    │ 7 × MIG      │          │
│   │ 80GB │  タイムスライス│ + タイム再利用 │          │
│   └──────┘ ────────→    │ = 14+ vGPU   │          │
│                         └──────────────┘          │
└──────────────────────────────────────────────────┘
GPU型番 VRAM MIGスライス数 適用モデル規模 月額コスト
A100 80GB 80GB 7 × 10GB 7B-13B ¥210,000
A100 40GB 40GB 4 × 10GB 7B ¥140,000
H100 80GB 80GB 7 × 10GB 7B-70B ¥350,000
L40S 48GB 48GB MIG非対応 7B-13B ¥85,000
T4 16GB 16GB MIG非対応 1B-3B ¥28,000

課題2:モデルサイズの爆発——ストレージと転送の悪夢

モデル パラメータ数 FP16サイズ INT8サイズ INT4サイズ
Qwen2.5-7B 7B 14GB 7GB 3.5GB
Llama3.1-13B 13B 26GB 13GB 6.5GB
Qwen2.5-72B 72B 144GB 72GB 36GB
DeepSeek-V3-671B 671B 1.3TB 671GB 335GB

モデルプル時間:72BモデルFP16をDocker Registryからプルするのに30分以上、INT4量子化後はわずか8分。

課題3:レイテンシとスループットのトレードオフ

┌─────────────────────────────────────────────────────┐
│        レイテンシ vs スループット トレードオフ曲線     │
│                                                       │
│  スルー │         ╱                                    │
│  プット │        ╱                                     │
│  (req/ │       ╱    ← バッチサイズ増大でスループット向上 │
│  sec)  │      ╱                                       │
│        │     ╱                                        │
│        │    ╱                                         │
│        │───╱────────────────────────                  │
│        │  ╱    ← しかしレイテンシも増大!               │
│        └─────────────────────── レイテンシ(ms)         │
│                                                       │
│  最適戦略:動的バッチング + Continuous Batching         │
└─────────────────────────────────────────────────────┘
戦略 レイテンシ スループット ユースケース
単一リクエスト直列 最低 最低 リアルタイムチャット
静的バッチング オフライン推論
Continuous Batching オンラインサービ
Speculative Decoding 更に低 リアルタイム+高スループット

AIモデルサービング:4フレームワーク比較

vLLM vs Triton vs TGI vs Ollama

次元 vLLM Triton Inference Server TGI (Text Generation Inference) Ollama
開発元 UC Berkeley NVIDIA HuggingFace コミュニティ
コア強み PagedAttention マルチフレームワーク Flash Attention シンプルさ
Continuous Batching ✅ ネイティブ ✅ 動的バッチング ✅ ネイティブ
Tensor Parallel
ストリーミグ出力 ✅ SSE ✅ SSE
OpenAI互換API アダプタ必要
量子化対応 AWQ/GPTQ/GGUF INT8/FP8 AWQ/GPTQ/bitsandbytes GGUF
GPU利用率 90%+ 80%+ 85%+ 60%+
K8s親和性 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐
本番対応 ⚠️ 開発/テスト
マルチモデルサービ インスタンス単一 インスタンス複数 インスタンス単一 インスタンス複数
ホットロード

アーキテクチャ比較図

┌─────────────────────────────────────────────────────────────────┐
│                      vLLM アーキテクチャ                          │
│  ┌─────────┐    ┌──────────────┐    ┌─────────────────┐        │
│  │ Request  │───→│ Scheduler    │───→│ PagedAttention  │        │
│  │ Queue    │    │ (Continuous  │    │ (KV Block Mgmt) │        │
│  │          │    │  Batching)   │    │                 │        │
│  └─────────┘    └──────────────┘    └─────────────────┘        │
│       ↑               ↑                     ↑                   │
│    OpenAI API    Dynamic Batch         GPU HBM                  │
│    Compatible    Size Control          KV Cache                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Triton アーキテクチャ                          │
│  ┌─────────┐    ┌──────────────┐    ┌─────────────────┐        │
│  │ Model    │───→│ Dynamic      │───→│ Backend         │        │
│  │ Repo     │    │ Batcher     │    │ (PyTorch/TF/    │        │
│  │          │    │              │    │  ONRT/TensorRT) │        │
│  └─────────┘    └──────────────┘    └─────────────────┘        │
│       ↑               ↑                     ↑                   │
│    マルチモデル   優先度キュー       マルチフレームワークエンジン  │
└─────────────────────────────────────────────────────────────────┘

vLLMクイックスタート

from vllm import LLM, SamplingParams

llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    tensor_parallel_size=2,
    gpu_memory_utilization=0.9,
    max_model_len=8192,
    quantization="awq",
)

params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=2048,
)

outputs = llm.generate(["クラウドネイティブAIデプロイのコア課題を説明して"], params)
for output in outputs:
    print(output.outputs[0].text)

TGI起動例

docker run --gpus all -p 8080:80 \
  -v $PWD/data:/data \
  ghcr.io/huggingface/text-generation-inference:2.3.0 \
  --model-id Qwen/Qwen2.5-7B-Instruct \
  --quantize awq \
  --max-input-length 4096 \
  --max-total-tokens 8192 \
  --cuda-memory-fraction 0.9

選定ガイド

┌──────────────────────────────────────────────────────┐
│        モデルサービングフレームワーク選定デシジョンツリー  │
│                                                        │
│  GPU利用率を最大化したい?                               │
│    ├─ はい → vLLM (PagedAttention, 90%+利用率)        │
│    └─ いいえ ↓                                        │
│  マルチフレームワーク/マルチモデル共存が必要?            │
│    ├─ はい → Triton (1インスタンスで複数モデル)        │
│    └─ いいえ ↓                                        │
│  HuggingFaceエコシステムのシームレス統合が必要?         │
│    ├─ はい → TGI (Flash Attention + HF Hub)           │
│    └─ いいえ ↓                                        │
│  ローカル開発/テストのみ?                               │
│    └─ はい → Ollama (1コマンドで起動)                  │
└──────────────────────────────────────────────────────┘

Docker化AIアプリケーション:マルチステージビルド + Compose

マルチステージDockerfile

# ===== Stage 1: モデルダウンロード =====
FROM python:3.11-slim AS model-downloader

RUN pip install --no-cache-dir huggingface-hub

ARG MODEL_ID=Qwen/Qwen2.5-7B-Instruct
ARG QUANTIZATION=awq

RUN huggingface-cli download \
    ${MODEL_ID} \
    --local-dir /models/${MODEL_ID} \
    --exclude "*.safetensors" \
    && echo "Model config downloaded"

# ===== Stage 2: 推論ランタイム =====
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 AS runtime

ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y --no-install-recommends \
    python3.11 python3-pip python3.11-venv \
    && rm -rf /var/lib/apt/lists/*

RUN python3.11 -m venv /opt/vllm
ENV PATH="/opt/vllm/bin:${PATH}"

RUN pip install --no-cache-dir \
    vllm==0.8.0 \
    transformers>=4.45.0 \
    accelerate

COPY --from=model-downloader /models /models

ARG MODEL_ID=Qwen/Qwen2.5-7B-Instruct
ENV MODEL_ID=${MODEL_ID}

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

ENTRYPOINT ["python3.11", "-m", "vllm.entrypoints.openai.api_server"]
CMD ["--model", "/models/Qwen/Qwen2.5-7B-Instruct", \
     "--host", "0.0.0.0", \
     "--port", "8000", \
     "--tensor-parallel-size", "2", \
     "--gpu-memory-utilization", "0.9", \
     "--max-model-len", "8192"]

Docker Composeローカル開発環境

version: "3.8"

services:
  vllm-server:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        MODEL_ID: Qwen/Qwen2.5-7B-Instruct
        QUANTIZATION: awq
    ports:
      - "8000:8000"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 2
              capabilities: [gpu]
    environment:
      - VLLM_WORKER_MULTIPROC_METHOD=spawn
    volumes:
      - model-cache:/root/.cache/huggingface
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 5
    restart: unless-stopped

  milvus-standalone:
    image: milvusdb/milvus:v2.4.17
    ports:
      - "19530:19530"
      - "9091:9091"
    environment:
      - ETCD_USE_EMBED=true
      - COMMON_STORAGETYPE=local
    volumes:
      - milvus-data:/var/lib/milvus
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 15s
      timeout: 5s
      retries: 3

  elasticsearch:
    image: elasticsearch:8.15.0
    ports:
      - "9200:9200"
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    volumes:
      - es-data:/usr/share/elasticsearch/data
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health"]
      interval: 15s
      timeout: 5s
      retries: 3

  springboot-ai:
    build:
      context: ../springboot-ai-service
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - SPRING_AI_OPENAI_BASE-URL=http://vllm-server:8000/v1
      - SPRING_AI_OPENAI_API-KEY=not-needed
      - MILVUS_HOST=milvus-standalone
      - MILVUS_PORT=19530
      - ELASTICSEARCH_URIS=http://elasticsearch:9200
    depends_on:
      vllm-server:
        condition: service_healthy
      milvus-standalone:
        condition: service_healthy
      elasticsearch:
        condition: service_healthy

volumes:
  model-cache:
  milvus-data:
  es-data:

Dockerイメージ最適化テクニック

手法 効果
マルチステージビルド イメージサイズ-60%+ ビルドツールを実行時に含めない
モデル量子化 サイズ-50%〜-75% FP16→INT4
.dockerignore ビルドコンテキスト削減 .git, __pycache__を除外
レイヤーキャッシュ再利用 ビルド時間-80% 依存レイヤー不変なら再利用
ベースイメージ選択 10倍のサイズ差 slim vs full
外部モデルボリューム イメージにモデルデータ不含 実行時にマウント

K8s GPUスケジューリング:NVIDIA GPU Operator + MIG + タイムスライス

NVIDIA GPU Operatorインストール

apiVersion: v1
kind: Namespace
metadata:
  name: gpu-operator
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: gpu-operator
  namespace: gpu-operator
spec:
  repo: https://helm.ngc.nvidia.com/nvidia
  chart: gpu-operator
  targetNamespace: gpu-operator
  valuesContent: |
    driver:
      enabled: true
      version: "550.54.15"
    toolkit:
      enabled: true
      version: "v1.15.0"
    devicePlugin:
      enabled: true
      config:
        name: mig-config
        shared:
          timeSlicing:
            resources:
              - name: nvidia.com/gpu
                replicas: 4
    migManager:
      enabled: true
      config:
        name: mig-partition-config

MIGパーティション設定

apiVersion: v1
kind: ConfigMap
metadata:
  name: mig-partition-config
  namespace: gpu-operator
data:
  config.yaml: |
    version: v1
    mig-configs:
      all-balanced:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.10gb": 7
      all-1g.5gb:
        - devices: all
          mig-enabled: true
          mig-devices:
            "1g.5gb": 14
      mixed:
        - devices: [0]
          mig-enabled: true
          mig-devices:
            "2g.20gb": 2
            "1g.10gb": 3
        - devices: [1]
          mig-enabled: false

GPUタイムスライス設定

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config
  namespace: gpu-operator
data:
  a100-80gb.yaml: |
    version: v1
    sharing:
      timeSlicing:
        resources:
          - name: nvidia.com/gpu
            replicas: 4
            devices: all
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin-daemonset
  namespace: gpu-operator
spec:
  selector:
    matchLabels:
      name: nvidia-device-plugin-ds
  template:
    spec:
      containers:
        - name: nvidia-device-plugin
          image: nvcr.io/nvidia/k8s-device-plugin:v0.16.2
          args:
            - --config-file=/etc/nvidia-plugin/config.yaml
          volumeMounts:
            - name: config
              mountPath: /etc/nvidia-plugin
      volumes:
        - name: config
          configMap:
            name: time-slicing-config

MIGインスタンスを使用するPod

apiVersion: v1
kind: Pod
metadata:
  name: vllm-mig-pod
spec:
  containers:
    - name: vllm
      image: vllm/vllm-openai:v0.8.0
      resources:
        limits:
          nvidia.com/mig-1g.10gb: 1
      command:
        - python3
        - -m
        - vllm.entrypoints.openai.api_server
        - --model
        - Qwen/Qwen2.5-7B-Instruct-AWQ
        - --max-model-len
        - "4096"
        - --gpu-memory-utilization
        - "0.9"

GPUスケジューリング戦略比較

戦略 原理 メリット デメリット ユースケース
排他GPU 1 Pod = 1 GPU ゼロ干渉、安定性能 リソース浪費 大規模モデル推論
MIGパーティション ハードウェアレベル分離 真の分離、設定可能サイズ A100/H100のみ 複数小モデルサービ
タイムスライス ソフトウェアレベル多重化 全GPUで対応 コンテキストスイッチオーバーヘッド 低優先度タスク
MPS 共有GPUコンテキスト 低オーバーヘッド共有 分離なし、1つのクラッシュが全体に影響 同一チームマルチタスク

モデル推論サービスデプロイ:vLLM + HPAオートスケーリング

vLLM Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen7b
  namespace: ai-inference
  labels:
    app: vllm-qwen7b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm-qwen7b
  template:
    metadata:
      labels:
        app: vllm-qwen7b
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchLabels:
                    app: vllm-qwen7b
                topologyKey: kubernetes.io/hostname
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.8.0
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: 2
            requests:
              nvidia.com/gpu: 2
              cpu: "4"
              memory: 16Gi
          env:
            - name: MODEL_NAME
              value: "Qwen/Qwen2.5-7B-Instruct-AWQ"
          command:
            - python3
            - -m
            - vllm.entrypoints.openai.api_server
          args:
            - --model
            - Qwen/Qwen2.5-7B-Instruct-AWQ
            - --host
            - "0.0.0.0"
            - --port
            - "8000"
            - --tensor-parallel-size
            - "2"
            - --gpu-memory-utilization
            - "0.9"
            - --max-model-len
            - "8192"
            - --enable-prefix-caching
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 60
            periodSeconds: 10
          volumeMounts:
            - name: model-cache
              mountPath: /root/.cache/huggingface
            - name: shm
              mountPath: /dev/shm
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-cache-pvc
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 8Gi
      nodeSelector:
        gpu-type: a100-80gb
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule

HPAオートスケーリング

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-qwen7b-hpa
  namespace: ai-inference
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-qwen7b
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: Pods
      pods:
        metric:
          name: vllm_num_requests_running
        target:
          type: AverageValue
          averageValue: "10"
    - type: Pods
      pods:
        metric:
          name: vllm_gpu_cache_usage_perc
        target:
          type: AverageValue
          averageValue: "70"
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 1
          periodSeconds: 120

Service + PodMonitor

apiVersion: v1
kind: Service
metadata:
  name: vllm-qwen7b-svc
  namespace: ai-inference
spec:
  selector:
    app: vllm-qwen7b
  ports:
    - name: http
      port: 8000
      targetPort: 8000
  type: ClusterIP
---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: vllm-qwen7b-monitor
  namespace: ai-inference
spec:
  selector:
    matchLabels:
      app: vllm-qwen7b
  podMetricsEndpoints:
    - port: http
      path: /metrics
      interval: 15s

vLLM主要監視メトリクス

メトリクス 説明 アラート閾値
vllm:num_requests_running 処理中のリクエスト数 > 80% max_num_seqs
vllm:num_requests_waiting 待機キューのリクエスト数 > 10 が5分間継続
vllm:gpu_cache_usage_perc KV Cache使用率 > 90%
vllm:avg_generation_throughput 平均生成スループット < 100 tok/s
vllm:e2e_request_latency_seconds エンドツーエンドレイテンシ P99 > 10s
vllm:num_preemptions プリエンプション回数 > 0

RAGベクトルサービスK8sデプロイ:Milvusクラスタ + Elasticsearch

全体アーキテクチャ

┌──────────────────────────────────────────────────────────────────┐
│                   RAGベクトルサービスアーキテクチャ                  │
│                                                                    │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐        │
│  │  ユーザー  │───→│ Spring Boot  │───→│ vLLM Embedding   │        │
│  │  クエリ   │    │ AI Gateway   │    │ (ベクトル生成)    │        │
│  └──────────┘    └──────┬───────┘    └──────────────────┘        │
│                         │                                        │
│              ┌──────────┼──────────┐                              │
│              ▼                     ▼                              │
│     ┌──────────────┐    ┌──────────────┐                         │
│     │   Milvus     │    │Elasticsearch │                         │
│     │  (ベクトル   │    │ (全文検索)   │                         │
│     │   検索)     │    │  BM25+Dense  │                         │
│     │  HNSW/IVF   │    │              │                         │
│     └──────────────┘    └──────────────┘                         │
│            │                    │                                │
│            └────────┬───────────┘                                │
│                     ▼                                            │
│              ┌──────────────┐    ┌──────────────────┐           │
│              │  ハイブリッド │───→│ vLLM LLM         │           │
│              │  検索結果    │    │ (回答生成)       │           │
│              │  Rerank      │    │                  │           │
│              └──────────────┘    └──────────────────┘           │
└──────────────────────────────────────────────────────────────────┘

Milvusクラスタデプロイ

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: milvus
  namespace: ai-rag
spec:
  serviceName: milvus-headless
  replicas: 3
  selector:
    matchLabels:
      app: milvus
  template:
    metadata:
      labels:
        app: milvus
    spec:
      containers:
        - name: milvus
          image: milvusdb/milvus:v2.4.17
          ports:
            - containerPort: 19530
            - containerPort: 9091
          resources:
            requests:
              cpu: "4"
              memory: 8Gi
            limits:
              cpu: "8"
              memory: 16Gi
          env:
            - name: ETCD_ENDPOINTS
              value: "etcd-0.etcd:2379,etcd-1.etcd:2379,etcd-2.etcd:2379"
            - name: MINIO_ADDRESS
              value: "minio:9000"
            - name: COMMON_STORAGETYPE
              value: "remote"
          volumeMounts:
            - name: milvus-data
              mountPath: /var/lib/milvus
          livenessProbe:
            httpGet:
              path: /healthz
              port: 9091
            initialDelaySeconds: 30
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /healthz
              port: 9091
            initialDelaySeconds: 15
            periodSeconds: 10
  volumeClaimTemplates:
    - metadata:
        name: milvus-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 100Gi
---
apiVersion: v1
kind: Service
metadata:
  name: milvus-svc
  namespace: ai-rag
spec:
  selector:
    app: milvus
  ports:
    - name: grpc
      port: 19530
      targetPort: 19530
    - name: metrics
      port: 9091
      targetPort: 9091
  type: ClusterIP

Elasticsearchデプロイ

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: ai-rag
spec:
  serviceName: elasticsearch-headless
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      initContainers:
        - name: sysctl
          image: busybox
          command: ["sysctl", "-w", "vm.max_map_count=262144"]
          securityContext:
            privileged: true
      containers:
        - name: elasticsearch
          image: elasticsearch:8.15.0
          ports:
            - containerPort: 9200
          resources:
            requests:
              cpu: "2"
              memory: 4Gi
            limits:
              cpu: "4"
              memory: 8Gi
          env:
            - name: cluster.name
              value: "ai-rag-cluster"
            - name: discovery.seed_hosts
              value: "elasticsearch-0.elasticsearch-headless,elasticsearch-1.elasticsearch-headless,elasticsearch-2.elasticsearch-headless"
            - name: cluster.initial_master_nodes
              value: "elasticsearch-0,elasticsearch-1,elasticsearch-2"
            - name: xpack.security.enabled
              value: "false"
            - name: ES_JAVA_OPTS
              value: "-Xms4g -Xmx4g"
          volumeMounts:
            - name: es-data
              mountPath: /usr/share/elasticsearch/data
  volumeClaimTemplates:
    - metadata:
        name: es-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 200Gi

Milvus Collection作成

from pymilvus import MilvusClient, DataType

client = MilvusClient(uri="http://milvus-svc.ai-rag.svc:19530")

schema = client.create_schema(auto_id=True, enable_dynamic_field=True)

schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=65535)
schema.add_field(field_name="embedding", datatype=DataType.FLOAT_VECTOR, dim=1024)
schema.add_field(field_name="source", datatype=DataType.VARCHAR, max_length=512)
schema.add_field(field_name="timestamp", datatype=DataType.INT64)

index_params = client.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="HNSW",
    metric_type="COSINE",
    params={"M": 32, "efConstruction": 256}
)
index_params.add_index(
    field_name="text",
    index_type="INVERTED"
)

client.create_collection(
    collection_name="knowledge_base",
    schema=schema,
    index_params=index_params,
)

ベクトル検索 vs 全文検索 vs ハイブリッド検索

検索方式 原理 メリット デメリット ユースケース
ベクトル検索 意味的類似度 意味を理解 完全一致が弱い 意味Q&A
全文検索 BM25キーワード 完全一致 意味理解なし キーワード検索
ハイブリッド検索 ベクトル+全文融合 両方の長所 計算コスト大 本番RAG
Rerank クロスエンコーダ再順位付け 最高精度 低速 高精度シナリオ

Spring Boot AIマイクロサービスのK8sデプロイ

Spring Boot AIアプリDockerfile

FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY gradle/ gradle/
COPY gradlew build.gradle settings.gradle ./
RUN ./gradlew dependencies --no-daemon
COPY src/ src/
RUN ./gradlew bootJar --no-daemon -x test

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/build/libs/*.jar app.jar

ENV JAVA_OPTS="-XX:+UseZGC -XX:MaxRAMPercentage=75.0"
ENV SPRING_PROFILES_ACTIVE=k8s

EXPOSE 8080

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD wget -qO- http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar app.jar"]

K8s Deployment + Service + Ingress

apiVersion: apps/v1
kind: Deployment
metadata:
  name: springboot-ai-service
  namespace: ai-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: springboot-ai-service
  template:
    metadata:
      labels:
        app: springboot-ai-service
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/actuator/prometheus"
    spec:
      containers:
        - name: app
          image: registry.example.com/springboot-ai-service:1.0.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
            limits:
              cpu: "2"
              memory: 4Gi
          env:
            - name: SPRING_AI_OPENAI_BASE-URL
              value: "http://vllm-qwen7b-svc.ai-inference.svc:8000/v1"
            - name: SPRING_AI_OPENAI_API-KEY
              valueFrom:
                secretKeyRef:
                  name: ai-api-keys
                  key: openai-key
            - name: MILVUS_HOST
              value: "milvus-svc.ai-rag.svc"
            - name: MILVUS_PORT
              value: "19530"
            - name: ELASTICSEARCH_URIS
              value: "http://elasticsearch-svc.ai-rag.svc:9200"
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 5
          volumeMounts:
            - name: config
              mountPath: /app/config
      volumes:
        - name: config
          configMap:
            name: springboot-ai-config
---
apiVersion: v1
kind: Service
metadata:
  name: springboot-ai-svc
  namespace: ai-app
spec:
  selector:
    app: springboot-ai-service
  ports:
    - name: http
      port: 80
      targetPort: 8080
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: springboot-ai-ingress
  namespace: ai-app
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-AI-Service: springboot-ai";
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - ai-api.example.com
      secretName: ai-api-tls
  rules:
    - host: ai-api.example.com
      http:
        paths:
          - path: /api/ai
            pathType: Prefix
            backend:
              service:
                name: springboot-ai-svc
                port:
                  number: 80

Spring Boot AIコア設定

spring:
  ai:
    openai:
      base-url: http://vllm-qwen7b-svc.ai-inference.svc:8000/v1
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: Qwen/Qwen2.5-7B-Instruct-AWQ
          temperature: 0.7
          max-tokens: 2048
    vectorstore:
      milvus:
        client:
          host: ${MILVUS_HOST:milvus-svc.ai-rag.svc}
          port: ${MILVUS_PORT:19530}
        database-name: default
        collection-name: knowledge_base
        embedding-dimension: 1024
        metric-type: COSINE
        index-type: HNSW

  elasticsearch:
    uris: ${ELASTICSEARCH_URIS:http://elasticsearch-svc.ai-rag.svc:9200}

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
  metrics:
    tags:
      application: springboot-ai-service
  tracing:
    sampling:
      probability: 1.0
  otlp:
    metrics:
      export:
        url: http://otel-collector.observability.svc:4318/v1/metrics
    tracing:
      endpoint: http://otel-collector.observability.svc:4318/v1/traces

オブザーバビリティ:Prometheus + Grafana + OpenTelemetry

監視アーキテクチャ

┌──────────────────────────────────────────────────────────────────┐
│              フルスタックオブザーバビリティアーキテクチャ             │
│                                                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │ vLLM     │  │ Milvus   │  │ES        │  │Spring    │        │
│  │ /metrics │  │ /metrics │  │/_prom    │  │/actuator │        │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘        │
│       │              │              │              │              │
│       └──────────────┴──────────────┴──────────────┘              │
│                            │                                      │
│                   ┌────────▼────────┐                             │
│                   │  OTel Collector │                             │
│                   │ (受信+処理+     │                             │
│                   │  エクスポート)  │                             │
│                   └────────┬────────┘                             │
│              ┌─────────────┼─────────────┐                        │
│              ▼             ▼             ▼                        │
│     ┌──────────────┐ ┌──────────┐ ┌──────────┐                  │
│     │  Prometheus   │ │  Jaeger  │ │  Loki    │                  │
│     │  (メトリクス) │ │ (トレース)│ │ (ログ)   │                  │
│     └──────┬───────┘ └──────────┘ └──────────┘                  │
│            ▼                                                      │
│     ┌──────────────┐                                              │
│     │   Grafana    │                                              │
│     │  (ダッシュ   │                                              │
│     │   ボード)    │                                              │
│     └──────────────┘                                              │
└──────────────────────────────────────────────────────────────────┘

OpenTelemetry Collectorデプロイ

apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  namespace: observability
spec:
  replicas: 2
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
        - name: otel-collector
          image: otel/opentelemetry-collector-contrib:0.110.0
          ports:
            - containerPort: 4318
            - containerPort: 4317
          resources:
            requests:
              cpu: "500m"
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          volumeMounts:
            - name: config
              mountPath: /etc/otelcol-contrib
      volumes:
        - name: config
          configMap:
            name: otel-collector-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: observability
data:
  config.yaml: |
    receivers:
      otlp:
        protocols:
          http:
            endpoint: 0.0.0.0:4318
          grpc:
            endpoint: 0.0.0.0:4317
      prometheus:
        config:
          scrape_configs:
            - job_name: "vllm"
              scrape_interval: 15s
              static_configs:
                - targets: ["vllm-qwen7b-svc.ai-inference.svc:8000"]
            - job_name: "milvus"
              scrape_interval: 15s
              static_configs:
                - targets: ["milvus-svc.ai-rag.svc:9091"]

    processors:
      batch:
        timeout: 10s
        send_batch_size: 1024
      memory_limiter:
        check_interval: 5s
        limit_mib: 400

    exporters:
      prometheusremotewrite:
        endpoint: http://prometheus.observability.svc:9090/api/v1/write
      otlphttp:
        endpoint: http://jaeger-collector.observability.svc:4318
      loki:
        endpoint: http://loki.observability.svc:3100/loki/api/v1/push

    service:
      pipelines:
        metrics:
          receivers: [otlp, prometheus]
          processors: [memory_limiter, batch]
          exporters: [prometheusremotewrite]
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [otlphttp]
        logs:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [loki]

Prometheusアラートルール

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: ai-inference-alerts
  namespace: observability
spec:
  groups:
    - name: vllm-alerts
      rules:
        - alert: VLLMHighQueueDepth
          expr: vllm_num_requests_waiting > 20
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "vLLMリクエストキュー深度過大"
            description: "インスタンス {{ $labels.instance }} 待機キュー {{ $value }} リクエスト"

        - alert: VLLMHighLatency
          expr: histogram_quantile(0.99, rate(vllm_e2e_request_latency_seconds_bucket[5m])) > 10
          for: 3m
          labels:
            severity: critical
          annotations:
            summary: "vLLM P99レイテンシ過大"
            description: "インスタンス {{ $labels.instance }} P99レイテンシ {{ $value }}s"

        - alert: VLLMGPUCacheFull
          expr: vllm_gpu_cache_usage_perc > 0.9
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "vLLM GPUキャッシュ使用率過大"
            description: "インスタンス {{ $labels.instance }} キャッシュ使用率 {{ $value }}%"

        - alert: VLLMPreemptions
          expr: increase(vllm_num_preemptions[5m]) > 0
          for: 1m
          labels:
            severity: critical
          annotations:
            summary: "vLLMリクエストプリエンプション発生"
            description: "インスタンス {{ $labels.instance }} 5分間に{{ $value }}回プリエンプション"

    - name: milvus-alerts
      rules:
        - alert: MilvusHighQueryLatency
          expr: histogram_quantile(0.99, rate(milvus_proxy_sq_latency_bucket[5m])) > 2
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Milvusクエリレイテンシ過大"

        - alert: MilvusInsertRateLow
          expr: rate(milvus_proxy_insert_vectors_count[5m]) < 10
          for: 10m
          labels:
            severity: info
          annotations:
            summary: "Milvus挿入レート異常低値"

Grafanaダッシュボード主要パネル

パネル メトリクス 可視化タイプ
推論QPS rate(vllm_num_requests_total[1m]) 折れ線グラフ
P50/P90/P99レイテンシ histogram_quantile 折れ線グラフ
GPUキャッシュ使用率 vllm_gpu_cache_usage_perc ゲージ
リクエストキュー深度 vllm_num_requests_waiting 棒グラフ
ベクトル検索レイテンシ milvus_proxy_sq_latency 折れ線グラフ
Spring Boot JVM jvm_memory_used_bytes 面グラフ
HTTPエラー率 rate(http_server_requests_seconds_count{status=~"5.."}[5m]) 折れ線グラフ
分散トレース Jaeger統合 テーブル+リンク

コスト最適化:量子化 + MIG再利用 + 動的スケーリング + Spotインスタンス

モデル量子化比較

方式 精度損失 サイズ圧縮比 推論高速化 互換性
FP16 (ベースライン) 0% 1x 1x 全て
INT8 (PTQ) <1% 2x 1.5x A100/H100
INT4 (GPTQ) 1-3% 4x 1.8x キャリブレーションデータ必要
INT4 (AWQ) 1-2% 4x 2x キャリブレーションデータ必要
INT4 (GGUF) 2-5% 4x 1.5x CPU/GPU汎用
FP8 <0.5% 2x 2x+ H100ネイティブ

コスト最適化戦略全景

┌──────────────────────────────────────────────────────────────────┐
│              コスト最適化の4本柱                                   │
│                                                                    │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────┐ │
│  │ 1.量子化     │  │ 2.MIG再利用  │  │ 3.動的       │  │4.Spot│ │
│  │              │  │              │  │   スケーリング│  │インス│ │
│  │ FP16→INT4   │  │ 1GPU→7MIG   │  │ HPA+VPA     │  │タンス│ │
│  │ サイズ↓75%  │  │ 利用率↑7x   │  │ アイドル縮小│  │入札  │ │
│  │ 推論↑2x     │  │ コスト↓85%  │  │ コスト↓40%  │  │↓70%  │ │
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────┘ │
│                                                                    │
│  総合効果:コストをベースラインの5%-15%に削減                      │
└──────────────────────────────────────────────────────────────────┘

コスト計算例

プラン GPU構成 月額コスト 備考
ベースライン:FP16排他 4 × A100 80GB ¥60,000 2レプリカ × 2 GPU
INT4量子化 2 × A100 80GB ¥30,000 2レプリカ × 1 GPU
INT4 + MIG 1 × A100 80GB ¥15,000 1レプリカ × 1 MIGインスタンス
INT4 + MIG + HPA 1 × A100 80GB(アイドル0.5) ¥10,000 アイドル時1レプリカに縮小
INT4 + MIG + HPA + Spot 1 × A100 Spot ¥3,000 Spot割引70%

Spotインスタンス戦略

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen7b-spot
  namespace: ai-inference
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm-qwen7b-spot
  template:
    metadata:
      labels:
        app: vllm-qwen7b-spot
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: cloud.google.com/gke-preemptible
                    operator: In
                    values: ["true"]
      tolerations:
        - key: cloud.google.com/gke-preemptible
          operator: Equal
          value: "true"
          effect: NoSchedule
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.8.0
          resources:
            limits:
              nvidia.com/gpu: 1
            requests:
              nvidia.com/gpu: 1
          env:
            - name: GRACEFUL_SHUTDOWN
              value: "true"
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 30"]

量子化モデルロード設定

from vllm import LLM

llm_awq = LLM(
    model="Qwen/Qwen2.5-7B-Instruct-AWQ",
    quantization="awq",
    gpu_memory_utilization=0.85,
    max_model_len=4096,
    enforce_eager=True,
)

llm_gptq = LLM(
    model="Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4",
    quantization="gptq",
    gpu_memory_utilization=0.85,
    max_model_len=4096,
)

GitOps:ArgoCD + Kustomize自動デリバリ

GitOpsワークフロー

┌──────────────────────────────────────────────────────────────────┐
│                    GitOps デリバリパイプライン                     │
│                                                                    │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│  │ 開発者   │───→│ CIビルド │───→│ イメージ │───→│ Gitリポ  │  │
│  │ コミット │    │ &テスト  │    │ プッシュ │    │ マニフェ │  │
│  └──────────┘    └──────────┘    └──────────┘    │ スト更新 │  │
│                                                   └────┬─────┘  │
│                                                        │          │
│                                              ┌────────▼───────┐  │
│                                              │   ArgoCD       │  │
│                                              │  変更を検出    │  │
│                                              │  自動Sync      │  │
│                                              └────────┬───────┘  │
│                                                       │          │
│                                    ┌──────────────────┼──────┐   │
│                                    ▼                  ▼      ▼   │
│                              ┌──────────┐   ┌──────────┐  ┌──┐ │
│                              │  Dev     │   │  Staging  │  │PR││
│                              │  環境    │   │  環境     │  │OD│ │
│                              └──────────┘   └──────────┘  └──┘ │
└──────────────────────────────────────────────────────────────────┘

Kustomizeディレクトリ構造

k8s/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── hpa.yaml
│   └── ingress.yaml
├── overlays/
│   ├── dev/
│   │   ├── kustomization.yaml
│   │   └── patch-replicas.yaml
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   └── patch-resources.yaml
│   └── production/
│       ├── kustomization.yaml
│       ├── patch-replicas.yaml
│       ├── patch-resources.yaml
│       └── patch-hpa.yaml

Base Kustomization

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml
  - ingress.yaml

commonLabels:
  app.kubernetes.io/part-of: ai-inference-platform
  app.kubernetes.io/managed-by: kustomize

images:
  - name: vllm-server
    newName: registry.example.com/vllm-openai
    newTag: v0.8.0

configMapGenerator:
  - name: vllm-config
    literals:
      - MODEL_NAME=Qwen/Qwen2.5-7B-Instruct-AWQ
      - MAX_MODEL_LEN=8192
      - GPU_MEMORY_UTILIZATION=0.9

secretGenerator:
  - name: ai-api-keys
    literals:
      - openai-key=placeholder

Production Overlay

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: ai-inference-production

resources:
  - ../../base

patches:
  - target:
      kind: Deployment
      name: vllm-qwen7b
    patch: |
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: vllm-qwen7b
      spec:
        replicas: 4
        template:
          spec:
            containers:
              - name: vllm
                resources:
                  limits:
                    nvidia.com/gpu: 2
                  requests:
                    nvidia.com/gpu: 2
                    cpu: "4"
                    memory: 16Gi

  - target:
      kind: HorizontalPodAutoscaler
      name: vllm-qwen7b-hpa
    patch: |
      apiVersion: autoscaling/v2
      kind: HorizontalPodAutoscaler
      metadata:
        name: vllm-qwen7b-hpa
      spec:
        minReplicas: 4
        maxReplicas: 16

ArgoCD Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ai-inference-platform
  namespace: argocd
  annotations:
    notifications.argoproj.io/subscribe.on-deployed.slack: ai-platform-deploys
spec:
  project: ai-platform
  source:
    repoURL: https://git.example.com/ai-platform/k8s-manifests.git
    targetRevision: main
    path: k8s/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: ai-inference-production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true
    retry:
      limit: 3
      backoff:
        duration: 30s
        factor: 2
        maxDuration: 5m

ArgoCD App of Appsパターン

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ai-platform-apps
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.example.com/ai-platform/argocd-apps.git
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: ai-platform
  namespace: argocd
spec:
  description: AI推論プラットフォームプロジェクト
  sourceRepos:
    - https://git.example.com/ai-platform/*
  destinations:
    - namespace: ai-inference-*
      server: https://kubernetes.default.svc
    - namespace: ai-rag-*
      server: https://kubernetes.default.svc
    - namespace: ai-app-*
      server: https://kubernetes.default.svc
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace
  namespaceResourceBlacklist:
    - group: ""
      kind: ResourceQuota

まとめ:クラウドネイティブAIデプロイチェックリスト

フェーズ チェック項目 ステータス
モデルサービ vLLM/Triton選定確定
モデルサービ 量子化戦略(INT4/AWQ)決定
Docker マルチステージDockerfile作成
Docker Docker Composeローカル検証
K8s GPU GPU Operatorインストール
K8s GPU MIG/タイムスライス設定
推論デプロイ vLLM Deploymentデプロイ
推論デプロイ HPAオートスケーリング設定
RAGサービス Milvusクラスタデプロイ
RAGサービス Elasticsearchデプロイ
マイクロサービス Spring Boot AI K8sデプロイ
マイクロサービス Ingressルーティング設定
オブザーバビリティ Prometheus + Grafana
オブザーバビリティ OpenTelemetry分散トレーシング
オブザーバビリティ アラートルール設定
コスト最適化 モデル量子化検証
コスト最適化 Spotインスタンス戦略
GitOps ArgoCD + Kustomize
GitOps マルチ環境Overlay設定

クラウドネイティブAIデプロイは終点ではなく起点。 Dockerコンテナ化からK8sオーケストレーション、GPUスケジューリングからオブザーバビリティまで、各ステップはエンジニアリングの蓄積。覚えておいて:監視なしのデプロイは盲目飛行、自動化なしの運用は手作業。 GitOpsで全てを自動化し、AI推論サービスをクラウドネイティブの真のファーストクラスシチズンにしよう。

ブラウザローカルツールを無料で試す →

#Kubernetes#Docker#GPU调度#vLLM#云原生#AI部署#ArgoCD