取り組みの背景
Gemini APIを活用したAIアプリケーションが成功を収め、ユーザー数が増加すると、次の課題が浮かび上がります。「どうやってサービスをスケールさせるか」「本番環境でどう安定稼働させるか」という問題です。
Cloud Runによるサーバーレス構成はコンセプト検証や小〜中規模サービスに最適ですが、より細かい制御が求められるエンタープライズユースケースや、複数のマイクロサービス群を統合的に管理するシナリオでは、Kubernetes(K8s) が真価を発揮します。
ここで扱うのはGemini APIを中核に据えたAIマイクロサービスを、KubernetesおよびGoogle Kubernetes Engine(GKE)上に本番デプロイするための完全ガイドを提供します。Dockerイメージのビルドから、Deployment・Service・Ingress・HPA設定、Secret管理、そしてPrometheus/Grafanaによる監視設定まで、実際のYAMLマニフェストとPythonコードを交えて解説します。
この記事で学べること:
- Gemini APIサービスを本番グレードでDockerコンテナ化する方法
- Kubernetes SecretsとExternal Secrets OperatorによるAPIキーのセキュアな管理
- DeploymentにReadiness/Liveness/Startup Probesを設定してゼロダウンタイムデプロイを実現する方法
- Horizontal Pod Autoscaler(HPA)でトラフィック急増に自動対応する設定
- PodDisruptionBudgetで計画メンテナンス中の可用性を確保する方法
- Prometheusによるリクエスト数・レイテンシ・キャッシュヒット率の監視
対象読者:
- Gemini APIを活用したアプリを本番スケールで運用したい方
- Kubernetesの基礎知識はあるが、AIワークロードへの適用に不慣れな方
- Cloud RunからよりコントローラブルなK8s環境へのマイグレーションを検討しているエンジニア
アーキテクチャ設計の考え方
なぜKubernetesか
Gemini APIを組み込んだアプリケーションをKubernetesで運用することには、以下のメリットがあります。
細粒度のリソース管理
AIワークロードは通常のWebアプリと異なり、リクエストごとのCPU/メモリ消費が変動します。KubernetesのResource RequestsとLimitsを使うことで、Gemini APIへのリクエスト処理中のメモリスパイクを適切に制御できます。
水平スケーリングの柔軟性
Horizontal Pod Autoscaler(HPA)を使えば、リクエスト数やCPU使用率に応じてPodの数を自動調整できます。急激なトラフィック増加にも対応できる設計が可能です。
ゼロダウンタイムデプロイ
Rolling Updateを使うことで、Gemini APIのモデルバージョンアップや機能追加を無停止でデプロイできます。旧バージョンのPodが稼働し続ける中で、新バージョンのPodが順次起動してトラフィックを引き継ぎます。
マルチサービス統合
認証サービス、レート制限サービス、セマンティックキャッシュサービスなど、複数のマイクロサービスを同一クラスターで管理し、Kubernetes Serviceによる内部通信が実現できます。
サービス全体構成
[クライアント]
↓ HTTPS
[GKE Ingress / Cloud Load Balancer]
↓
[Gemini Chat Service] ← テキスト生成(Gemini 2.5 Flash)
[Gemini Analysis Service] ← ドキュメント解析(Gemini 2.5 Pro)
[Gemini Embedding Service] ← ベクトル生成(text-embedding-004)
↓
[Redis Cache Service] ← セマンティックキャッシュ(ClusterIP)
[PostgreSQL StatefulSet] ← 会話履歴・ユーザーデータ
Step 1: Dockerイメージの作成
まず、Gemini APIサービスをコンテナ化します。マルチステージビルドを使うことで、開発依存パッケージを含まない軽量な本番イメージを生成できます。
# Dockerfile(本番最適化版 — マルチステージビルド)
# --- Stage 1: ビルドステージ ---
FROM python:3.12-slim AS builder
WORKDIR /app
# 依存関係のみを先にコピーしてキャッシュを最大活用
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# --- Stage 2: 本番ステージ ---
FROM python:3.12-slim AS production
# セキュリティ: 非rootユーザーで実行
RUN useradd --system --create-home appuser
WORKDIR /app
# ビルドステージからsite-packagesのみコピー
COPY --from=builder /root/.local /home/appuser/.local
# アプリケーションコードをコピー
COPY --chown=appuser:appuser . .
USER appuser
# 環境変数(値はKubernetes Secretから注入)
ENV PYTHONPATH=/home/appuser/.local/lib/python3.12/site-packages
ENV PATH=/home/appuser/.local/bin:$PATH
# ヘルスチェック用エンドポイント
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health')"
EXPOSE 8000
# --workers 2 は通常2vCPU Podに適切(CPUに合わせて調整)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
# requirements.txt
google-generativeai>=0.8.0
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
httpx>=0.27.0
redis>=5.2.0
pydantic>=2.9.0
prometheus-client>=0.21.0
次に、FastAPIを使ったGemini Chatサービスの本体コードです。
# main.py — Gemini Chat Service(Kubernetes対応版)
import os
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, PlainTextResponse
from pydantic import BaseModel
import google.generativeai as genai
import redis.asyncio as redis_async
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import json
import hashlib
import time
# --- Prometheus メトリクス定義 ---
REQUEST_COUNT = Counter(
"gemini_requests_total",
"Gemini API requests total",
["model", "status"]
)
REQUEST_DURATION = Histogram(
"gemini_request_duration_seconds",
"Gemini API request duration",
["model"],
buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
)
CACHE_HIT_COUNT = Counter(
"gemini_cache_hits_total",
"Cache hits total"
)
redis_client = None
gemini_model = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""アプリケーション起動・終了時の処理(Kubernetes Probe対応)"""
global redis_client, gemini_model
# Gemini API初期化(Kubernetes SecretからAPIキーを取得)
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise RuntimeError("GEMINI_API_KEY environment variable is required")
genai.configure(api_key=api_key)
gemini_model = genai.GenerativeModel(
model_name="gemini-2.5-flash",
generation_config=genai.GenerationConfig(
temperature=0.7,
max_output_tokens=2048,
)
)
# Redis接続(Kubernetes内部DNS: redis-service.ai-services.svc.cluster.local)
redis_url = os.environ.get("REDIS_URL", "redis://redis-service:6379")
redis_client = await redis_async.from_url(redis_url, decode_responses=True)
print("✅ Gemini Chat Service initialized")
yield
if redis_client:
await redis_client.aclose()
print("🛑 Gemini Chat Service shutting down")
app = FastAPI(title="Gemini Chat Service", version="1.0.0", lifespan=lifespan)
class ChatRequest(BaseModel):
message: str
session_id: str | None = None
use_cache: bool = True
class ChatResponse(BaseModel):
response: str
cached: bool = False
model: str = "gemini-2.5-flash"
duration_ms: float
def get_cache_key(message: str) -> str:
return f"gemini:chat:{hashlib.sha256(message.encode()).hexdigest()[:16]}"
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""Gemini APIへのチャットリクエスト(Redisキャッシュ付き)"""
start_time = time.time()
cache_key = get_cache_key(request.message)
# キャッシュチェック
if request.use_cache and redis_client:
cached = await redis_client.get(cache_key)
if cached:
CACHE_HIT_COUNT.inc()
REQUEST_COUNT.labels(model="gemini-2.5-flash", status="cache_hit").inc()
return ChatResponse(
response=json.loads(cached),
cached=True,
duration_ms=(time.time() - start_time) * 1000
)
# Gemini API呼び出し
try:
with REQUEST_DURATION.labels(model="gemini-2.5-flash").time():
response = await asyncio.to_thread(
gemini_model.generate_content,
request.message
)
result_text = response.text
REQUEST_COUNT.labels(model="gemini-2.5-flash", status="success").inc()
# Redisにキャッシュ保存(TTL: 1時間)
if redis_client:
await redis_client.setex(cache_key, 3600, json.dumps(result_text))
return ChatResponse(
response=result_text,
duration_ms=(time.time() - start_time) * 1000
)
except Exception as e:
REQUEST_COUNT.labels(model="gemini-2.5-flash", status="error").inc()
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health():
"""ヘルスチェックエンドポイント(Kubernetes Liveness/Readiness Probe用)"""
try:
# Redisへの接続確認
if redis_client:
await redis_client.ping()
return {"status": "healthy", "service": "gemini-chat"}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Unhealthy: {e}")
@app.get("/metrics")
async def metrics():
"""Prometheusメトリクスエンドポイント"""
return PlainTextResponse(
generate_latest().decode(),
media_type=CONTENT_TYPE_LATEST
)
Step 2: Kubernetes Secretによるセキュアなキー管理
Gemini APIキーを安全に管理するためにKubernetes Secretsを使います。GitリポジトリにAPIキーをハードコードすることは絶対に避けましょう。
# secret.yaml(直接作成 — Gitにコミットしない!)
apiVersion: v1
kind: Secret
metadata:
name: gemini-api-secret
namespace: ai-services
type: Opaque
stringData:
GEMINI_API_KEY: "YOUR_GEMINI_API_KEY" # 実際のキーに置き換え
REDIS_URL: "redis://redis-service:6379"
# ✅ より安全な方法: kubectlコマンドで直接作成(yamlファイル不要)
kubectl create namespace ai-services
kubectl create secret generic gemini-api-secret \
--from-literal=GEMINI_API_KEY="${GEMINI_API_KEY}" \
--from-literal=REDIS_URL="redis://redis-service:6379" \
--namespace ai-services
# 確認
kubectl get secret gemini-api-secret -n ai-services
# 出力例: gemini-api-secret Opaque 2 5s
GKEを使用している場合は、External Secrets Operator + Google Secret Manager との統合がさらに安全です。
# external-secret.yaml(Secret Managerとの統合)
# 前提: External Secrets Operatorがインストール済みであること
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gemini-api-external-secret
namespace: ai-services
spec:
refreshInterval: 1h # 1時間ごとにSecret Managerから同期
secretStoreRef:
name: gcp-secret-store
kind: ClusterSecretStore
target:
name: gemini-api-secret
data:
- secretKey: GEMINI_API_KEY
remoteRef:
key: gemini-api-key # Google Secret Managerのシークレット名
Step 3: Deploymentマニフェストの作成
本番グレードのDeploymentには、Resource管理・HealthProbe・セキュリティコンテキスト・グレースフルシャットダウンの設定が不可欠です。
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gemini-chat-service
namespace: ai-services
labels:
app: gemini-chat-service
version: "1.0.0"
spec:
replicas: 2 # 初期Pod数(HPAが自動調整するため最小値を設定)
selector:
matchLabels:
app: gemini-chat-service
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # アップデート中の最大追加Pod数
maxUnavailable: 0 # ゼロダウンタイムデプロイ(Pod数を減らさない)
template:
metadata:
labels:
app: gemini-chat-service
version: "1.0.0"
annotations:
# Prometheus自動スクレイプを有効化
prometheus.io/scrape: "true"
prometheus.io/port: "8000"
prometheus.io/path: "/metrics"
spec:
# セキュリティコンテキスト(非rootユーザー強制)
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
# グレースフルシャットダウン: 進行中リクエストを完了させる
terminationGracePeriodSeconds: 60
containers:
- name: gemini-chat
image: gcr.io/YOUR_PROJECT/gemini-chat-service:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8000
name: http
# 環境変数(Kubernetes Secretから注入)
env:
- name: GEMINI_API_KEY
valueFrom:
secretKeyRef:
name: gemini-api-secret
key: GEMINI_API_KEY
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: gemini-api-secret
key: REDIS_URL
- name: ENV
value: "production"
# リソース設定(AIワークロードに合わせて調整)
resources:
requests:
memory: "256Mi" # 最低保証メモリ
cpu: "100m" # 0.1 vCPU 保証
limits:
memory: "512Mi" # 最大メモリ(超過するとOOM Kill)
cpu: "500m" # 0.5 vCPU 上限
# Readiness Probe: トラフィックを受け付けられる状態を確認
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30 # 起動後30秒待ってから開始
periodSeconds: 10
failureThreshold: 3 # 3回失敗でトラフィック切断
# Liveness Probe: 異常状態を検知して自動再起動
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 20
failureThreshold: 5 # 5回失敗でPod再起動
# Startup Probe: 起動完了まで他のProbeを無効化
startupProbe:
httpGet:
path: /health
port: 8000
failureThreshold: 12 # 最大 12×10秒 = 2分の起動猶予
periodSeconds: 10
Step 4: ServiceとIngressの設定
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: gemini-chat-service
namespace: ai-services
labels:
app: gemini-chat-service
spec:
selector:
app: gemini-chat-service
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8000
type: ClusterIP # クラスター内部のみ(IngressからHTTPSで公開)
# ingress.yaml(GKE Ingress — HTTPS + マネージド証明書)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ai-services-ingress
namespace: ai-services
annotations:
kubernetes.io/ingress.class: "gce"
networking.gke.io/managed-certificates: "ai-services-cert"
kubernetes.io/ingress.global-static-ip-name: "ai-services-ip"
spec:
rules:
- host: api.your-domain.com
http:
paths:
- path: /chat
pathType: Prefix
backend:
service:
name: gemini-chat-service
port:
number: 80
- path: /analyze
pathType: Prefix
backend:
service:
name: gemini-analysis-service
port:
number: 80
- path: /embed
pathType: Prefix
backend:
service:
name: gemini-embedding-service
port:
number: 80
---
# マネージド証明書(GKEが自動でSSL証明書を発行・更新)
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: ai-services-cert
namespace: ai-services
spec:
domains:
- api.your-domain.com
Step 5: Horizontal Pod Autoscaler(HPA)の設定
Gemini APIへのリクエスト数に応じた自動スケーリングを設定します。AIワークロードは急激なトラフィックスパイクが発生しやすいため、スケールアップは素早く、スケールダウンは慎重に設定する点が肝心です。
# hpa.yaml(Kubernetes autoscaling/v2)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: gemini-chat-hpa
namespace: ai-services
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: gemini-chat-service
# Pod数の範囲
minReplicas: 2 # 最小2 Pod(高可用性のため複数ゾーンに分散)
maxReplicas: 20 # 最大20 Pod(コスト上限として機能)
metrics:
# CPU使用率ベースのスケーリング
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # CPU 60%超でスケールアウト
# メモリ使用率ベースのスケーリング
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # メモリ 70%超でスケールアウト
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 急激なスパイクへの即時対応
policies:
- type: Pods
value: 4
periodSeconds: 60 # 60秒ごとに最大4 Pod追加(迅速なスケールアップ)
scaleDown:
stabilizationWindowSeconds: 300 # 5分間安定してからスケールイン
policies:
- type: Pods
value: 1
periodSeconds: 60 # 慎重にスケールイン(コストとパフォーマンスのバランス)
HPAの動作を確認するコマンド:
# HPA の現在の状態を確認
kubectl get hpa -n ai-services
# 出力例:
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# gemini-chat-hpa Deployment/gemini-chat-service 23%/60%, 45%/70% 2 20 3
# 詳細なスケーリングイベントを確認
kubectl describe hpa gemini-chat-hpa -n ai-services
Step 6: PodDisruptionBudgetで可用性を確保
ノードのメンテナンスやアップグレード中でも、最低限のPodが稼働し続けるようにPodDisruptionBudget(PDB)を設定します。
# pdb.yaml(PodDisruptionBudget)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: gemini-chat-pdb
namespace: ai-services
spec:
minAvailable: 1 # 常に最低1 Podが稼働中であることを保証
selector:
matchLabels:
app: gemini-chat-service
また、名前空間全体のリソース上限を設定しておくことで、予期しないリソース消費を防げます。
# resource-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: ai-services-quota
namespace: ai-services
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "30"
Step 7: Prometheusによるモニタリング設定
Prometheus Operatorを使用している場合、ServiceMonitorリソースで自動的にスクレイプ設定を追加できます。
# servicemonitor.yaml(Prometheus Operator使用)
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: gemini-chat-monitor
namespace: monitoring # Prometheusと同じnamespace
labels:
release: kube-prometheus-stack # Prometheusのラベルセレクターに合わせる
spec:
selector:
matchLabels:
app: gemini-chat-service
namespaceSelector:
matchNames:
- ai-services
endpoints:
- port: http
path: /metrics
interval: 30s
Grafanaダッシュボードで監視すべき主要メトリクスとPromQLクエリ:
# 直近5分のリクエスト成功率
sum(rate(gemini_requests_total{status="success"}[5m])) /
sum(rate(gemini_requests_total[5m])) * 100
# p95レイテンシ(秒)
histogram_quantile(0.95, sum(rate(gemini_request_duration_seconds_bucket[5m])) by (le))
# キャッシュヒット率
sum(rate(gemini_cache_hits_total[5m])) /
sum(rate(gemini_requests_total[5m])) * 100
# Pod数の推移(HPAのスケーリングを可視化)
kube_horizontalpodautoscaler_status_current_replicas{horizontalpodautoscaler="gemini-chat-hpa"}
Step 8: CI/CDパイプラインとデプロイスクリプト
GitHub ActionsでCI/CDパイプラインを構築し、mainブランチへのマージで自動デプロイを実現します。GitHub ActionsとGemini APIを組み合わせたCI/CDパイプライン構築方法も参考にしてください。
#!/bin/bash
# deploy.sh — 本番デプロイスクリプト
set -euo pipefail
REGISTRY="gcr.io/your-project-id"
SERVICE="gemini-chat-service"
NAMESPACE="ai-services"
VERSION=$(git rev-parse --short HEAD)
IMAGE="${REGISTRY}/${SERVICE}:${VERSION}"
echo "🔨 Dockerイメージをビルド中... (${VERSION})"
docker build -t "${IMAGE}" .
docker push "${IMAGE}"
echo "🚀 Kubernetesにデプロイ中..."
# イメージタグを更新してローリングアップデートを開始
kubectl set image deployment/gemini-chat-service \
gemini-chat="${IMAGE}" \
--namespace "${NAMESPACE}"
# ロールアウトの完了を待機(タイムアウト: 5分)
kubectl rollout status deployment/gemini-chat-service \
--namespace "${NAMESPACE}" \
--timeout=300s
echo "✅ デプロイ完了!バージョン: ${VERSION}"
# デプロイ検証: 全Podが正常稼働中か確認
READY_PODS=$(kubectl get pods -n "${NAMESPACE}" \
-l app=gemini-chat-service \
--field-selector=status.phase=Running \
--no-headers | wc -l)
echo "🟢 稼働中のPod数: ${READY_PODS}"
# 動作確認: ヘルスエンドポイントにリクエスト
POD_NAME=$(kubectl get pod -n "${NAMESPACE}" \
-l app=gemini-chat-service \
-o jsonpath="{.items[0].metadata.name}")
kubectl exec -n "${NAMESPACE}" "${POD_NAME}" -- \
python -c "import httpx; r=httpx.get('http://localhost:8000/health'); print(r.json())"
よくある問題とトラブルシューティング
Q1: PodがCrashLoopBackOffになる
Gemini APIキーが見つからない、またはRedisへの接続が失敗している場合に発生します。
# ログを確認
kubectl logs -n ai-services -l app=gemini-chat-service --previous
# Secretが正しく作成されているか確認
kubectl get secret gemini-api-secret -n ai-services
# Podの環境変数を確認
kubectl exec -n ai-services <pod-name> -- env | grep -E "GEMINI|REDIS"
Q2: HPAがスケールアウトしない
Metrics Serverが未インストールの場合、HPAはメトリクスを取得できません。
# Metrics Serverのインストール確認
kubectl get deployment metrics-server -n kube-system
# GKEの場合は通常デフォルトでインストール済み
# セルフホストの場合はこちらでインストール:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# HPAの詳細確認
kubectl describe hpa gemini-chat-hpa -n ai-services
# "unable to fetch metrics" が出る場合はMetrics Server未インストール
Q3: Gemini APIレート制限エラー(429)が多発する
複数のPodが同時に大量リクエストを送信してAPIレート制限に引っかかる場合は、アプリケーション側でトークンバケット式のレート制限を実装します。Gemini APIのエラーハンドリングとレート制限の本番対策に詳しい実装パターンを解説しています。
# rate_limiter.py — Redisを使ったトークンバケット実装(全Podで共有)
import redis.asyncio as redis_async
import time
class GeminiRateLimiter:
def __init__(self, redis_client, max_requests_per_minute: int = 60):
self.redis = redis_client
self.max_rpm = max_requests_per_minute
async def acquire(self, user_id: str = "global") -> bool:
"""
スライディングウィンドウ方式でリクエストを制御
Returns: True=OK, False=レート制限中
"""
key = f"rate_limit:{user_id}"
now = time.time()
window_start = now - 60 # 1分間のウィンドウ
pipe = self.redis.pipeline()
pipe.zremrangebyscore(key, 0, window_start) # 古いリクエストを削除
pipe.zadd(key, {str(now): now}) # 新リクエストを追加
pipe.zcard(key) # カウント取得
pipe.expire(key, 60) # TTL設定
results = await pipe.execute()
request_count = results[2]
return request_count <= self.max_rpm
コスト最適化戦略
GKEでGemini APIサービスを運用する際のコスト削減ポイントをまとめます。
Spot Nodeの活用: ステートレスなGemini APIサービスはSpot(プリエンプティブル)インスタンスで運用できます。コストを最大80%削減できる一方、突然の中断に対してKubernetesのPod再スケジューリング機能が自動対応します。
セマンティックキャッシュの徹底: 同じ(または意味的に類似した)プロンプトに対してGemini APIを毎回呼び出すのは非効率です。Redisによるキャッシュで重複リクエストのAPI費用をゼロにすることが、最大のコスト削減策になります。
適切なモデルの選択: 全リクエストにGemini 2.5 Proを使う必要はありません。FAQ回答など単純なタスクにはGemini 2.5 Flashを使い、複雑な分析のみProを使うルーティング設計でコストを最適化できます。
Resource Quotaによる上限設定: 前述のResourceQuotaを設定することで、バグや意図しないループによる過剰なAPIコール・コンピューティングリソース消費を防げます。
ConfigMapによる設定管理
APIキーのような機密情報はSecretで管理しましたが、機密ではないアプリケーション設定にはConfigMapが適しています。ConfigMapを使うことで、Gemini APIのモデル設定やタイムアウト値などをコードやDockerイメージに焼き込まず、Kubernetes上で管理できます。
これの最大のメリットは、Podの再ビルドなしに設定を変更できることです。たとえば、モデルをgemini-2.5-flashからgemini-2.5-proに切り替えたい場合、ConfigMapを更新してPodを再起動するだけで対応できます。
# configmap.yaml — 機密性のない設定値を一元管理
apiVersion: v1
kind: ConfigMap
metadata:
name: gemini-chat-config
namespace: ai-services
data:
# Gemini APIのモデル設定
GEMINI_MODEL: "gemini-2.5-flash"
GEMINI_MAX_TOKENS: "2048"
GEMINI_TEMPERATURE: "0.7"
# キャッシュ設定
CACHE_TTL_SECONDS: "3600"
CACHE_MAX_SIZE_MB: "512"
# アプリケーション設定
LOG_LEVEL: "INFO"
WORKERS: "2"
TIMEOUT_SECONDS: "30"
DeploymentのenvironmentにConfigMapを追加します:
# deployment.yamlのcontainer.envに追加
env:
- name: GEMINI_MODEL
valueFrom:
configMapKeyRef:
name: gemini-chat-config
key: GEMINI_MODEL
- name: CACHE_TTL_SECONDS
valueFrom:
configMapKeyRef:
name: gemini-chat-config
key: CACHE_TTL_SECONDS
# ... Secretの設定もそのまま維持
- name: GEMINI_API_KEY
valueFrom:
secretKeyRef:
name: gemini-api-secret
key: GEMINI_API_KEY
また、ConfigMapをボリュームとしてマウントすることで、設定ファイル全体をコンテナ内に展開することもできます。Pydanticの設定クラスと組み合わせると、型安全な設定管理が実現します。
# config.py — Pydantic SettingsでConfigMap/Secretを一元管理
from pydantic_settings import BaseSettings
class GeminiServiceConfig(BaseSettings):
# ConfigMapからの設定(デフォルト値あり)
gemini_model: str = "gemini-2.5-flash"
gemini_max_tokens: int = 2048
gemini_temperature: float = 0.7
cache_ttl_seconds: int = 3600
log_level: str = "INFO"
# SecretからのAPIキー(必須)
gemini_api_key: str
redis_url: str = "redis://redis-service:6379"
class Config:
env_file = ".env" # ローカル開発用(本番ではK8sが注入)
# アプリ全体で共有する設定インスタンス
settings = GeminiServiceConfig()
Network Policyによるマイクロサービス間通信の保護
デフォルトのKubernetesでは、同一クラスター内の全Podが互いに通信できます。マルチテナント環境や厳格なセキュリティ要件がある場合、これは受け入れがたいリスクです。Network Policyを使って、Gemini APIサービスへのアクセスを必要なPodに限定しましょう。
たとえば、Gemini Chat Serviceには「APIゲートウェイ経由のトラフィックのみ許可」し、データベースへの直接アクセスは「同じnamespaceのPodのみ」に制限するといった設定が可能です。
# network-policy.yaml — Gemini Chat Serviceへのアクセス制御
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gemini-chat-network-policy
namespace: ai-services
spec:
podSelector:
matchLabels:
app: gemini-chat-service
policyTypes:
- Ingress
- Egress
ingress:
# APIゲートウェイからの着信のみ許可
- from:
- podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8000
# Prometheusからのメトリクス取得を許可
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 8000
egress:
# RedisへのアウトバウンドのみAI Servicesnamespace内に限定
- to:
- podSelector:
matchLabels:
app: redis-cache
ports:
- protocol: TCP
port: 6379
# 外部のGemini API(Google)へのアクセスを許可
- to: []
ports:
- protocol: TCP
port: 443
# DNS解決を許可
- to: []
ports:
- protocol: UDP
port: 53
Network Policyはデフォルトで「何も制限しない」状態です。制限を有効にするには、まずデフォルト拒否ポリシーを設定し、その後に必要な通信のみを許可するのがベストプラクティスです。
# default-deny.yaml — デフォルト拒否(allowリストで管理)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: ai-services
spec:
podSelector: {} # namespace内の全Podに適用
policyTypes:
- Ingress
- Egress
Network PolicyはCiliumやCalico等のCNI(Container Network Interface)プラグインがサポートしている必要があります。GKEではデフォルトでサポートされています。
Kustomizeを使ったマルチ環境デプロイ戦略
本番環境、ステージング環境、開発環境ではそれぞれ異なる設定が必要です。たとえば、開発環境ではgemini-2.5-flashを使いコストを抑え、本番環境ではgemini-2.5-proを使うといった差分管理が必要になります。
このような環境ごとの差分管理に最適なのがKustomizeです。KustomizeはKubernetes公式に組み込まれており、kubectl apply -k コマンドで利用できます。
# Kustomize ディレクトリ構成
k8s/
├── base/ # 共通のベースマニフェスト
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── hpa.yaml
│ └── kustomization.yaml
├── overlays/
│ ├── development/ # 開発環境の差分
│ │ ├── configmap-patch.yaml
│ │ └── kustomization.yaml
│ ├── staging/ # ステージング環境の差分
│ │ ├── configmap-patch.yaml
│ │ └── kustomization.yaml
│ └── production/ # 本番環境の差分
│ ├── configmap-patch.yaml
│ ├── hpa-patch.yaml
│ └── kustomization.yaml
# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- hpa.yaml
# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ai-services-prod # 本番専用namespace
bases:
- ../../base
patches:
- configmap-patch.yaml
- hpa-patch.yaml
images:
- name: gcr.io/YOUR_PROJECT/gemini-chat-service
newTag: "v1.2.0" # 本番用の固定バージョン
# k8s/overlays/production/hpa-patch.yaml(本番はより積極的なスケーリング)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: gemini-chat-hpa
spec:
minReplicas: 3 # 本番は最小3 Pod(高可用性)
maxReplicas: 50 # 本番はより大きな上限
# 本番環境へのデプロイ
kubectl apply -k k8s/overlays/production/
# 開発環境へのデプロイ
kubectl apply -k k8s/overlays/development/
# 差分プレビュー(実際には適用しない)
kubectl diff -k k8s/overlays/production/
Kustomizeを使うことで、Helmほど複雑なテンプレート機能を必要としない中規模プロジェクトでも、環境ごとの設定をシンプルかつ安全に管理できます。
構造化ログとトレーシングの実装
本番環境では構造化ログ(JSON形式) を採用することで、Cloud Logging(旧Stackdriver)やDatadogなどのログ管理システムへの統合が容易になります。人間が読みやすい単純なprint文ではなく、機械が解析しやすいJSON形式でログを出力することが本番運用の基本です。
また、複数のマイクロサービスをまたいだリクエストの追跡には分散トレーシングが不可欠です。OpenTelemetryを使えば、Gemini APIへのリクエストがどのサービスを経由し、どこで時間がかかっているかを可視化できます。
# logging_config.py — 構造化ログの設定
import logging
import json
import time
from typing import Any
class JSONFormatter(logging.Formatter):
"""Cloud Logging互換のJSON形式でログを出力するフォーマッター"""
def format(self, record: logging.LogRecord) -> str:
log_entry: dict[str, Any] = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(record.created)),
"severity": record.levelname,
"message": record.getMessage(),
"service": "gemini-chat-service",
"logger": record.name,
}
# エラーの場合はスタックトレースを含める
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
# 追加フィールド(リクエストID等)があれば含める
if hasattr(record, "request_id"):
log_entry["request_id"] = record.request_id
if hasattr(record, "model"):
log_entry["gemini_model"] = record.model
if hasattr(record, "duration_ms"):
log_entry["duration_ms"] = record.duration_ms
return json.dumps(log_entry, ensure_ascii=False)
def setup_logging(level: str = "INFO") -> logging.Logger:
"""アプリケーション全体のロガーを設定する"""
logger = logging.getLogger("gemini-chat")
logger.setLevel(getattr(logging, level.upper()))
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
return logger
# main.pyでの使用例
logger = setup_logging(settings.log_level)
# Gemini API呼び出し時のログ(リクエストID・モデル・レイテンシを含む)
logger.info(
"Gemini API request completed",
extra={
"request_id": request_id,
"model": "gemini-2.5-flash",
"duration_ms": duration_ms,
"cached": False,
"input_tokens": response.usage_metadata.prompt_token_count if response.usage_metadata else None,
}
)
Cloud Loggingを使っている場合、上記のJSON形式ログは自動的にパースされ、Severity・Timestamp・各フィールドが個別のカラムとして格納されます。これにより、duration_ms > 5000(5秒超のリクエスト)やseverity = ERRORでのフィルタリングが容易になります。
個人開発者の視点から(実体験メモ)
まとめ:本番Kubernetesの全体像
Dockerイメージ設計では、マルチステージビルドで本番イメージを軽量化し、非rootユーザーで実行することでセキュリティを確保しました。Secret管理では、APIキーをGitに含めず、Kubernetes Secrets(またはExternal Secrets Operator + Secret Manager)で安全に注入する仕組みを構築しました。
Deploymentの本番設定では、3種類のHealthProbeとRolling Updateでゼロダウンタイムデプロイを実現し、Resource Limits/RequestsでAIワークロードのリソースを適切に制御しました。HPAとPDBの組み合わせで、トラフィックスパイクへの自動対応と計画メンテナンス中の可用性確保を両立しました。
ConfigMapによる設定の外部化、Network Policyによるトラフィック制御、Kustomizeによる環境差分管理、構造化ログによる可観測性の確保が、真の本番グレード運用に必要な要素です。
サーバーレス構成と比較したKubernetesのトレードオフについては、GeminiをCloud Runにデプロイするサーバーレスアーキテクチャガイドも参考にしてください。小規模なプロジェクトではCloud Runが運用コストの観点で優れており、本記事のKubernetes構成は大規模・複雑なマルチサービス環境に適しています。
また、GitHub ActionsとGemini APIを組み合わせたCI/CDパイプライン構築方法とGemini APIのエラーハンドリングとレート制限の本番対策をあわせて読むことで、デプロイから運用までの全体像がより明確になるでしょう。
本書でK8sの内部動作を理解した上で、本記事のGemini API統合パターンを応用することで、堅牢なAIインフラを構築できるでしょう。