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推論サービスをクラウドネイティブの真のファーストクラスシチズンにしよう。