T-CREATOR

gpt-oss を Docker Compose で本番準備:ヘルスチェック・リソース制限・再起動方針

gpt-oss を Docker Compose で本番準備:ヘルスチェック・リソース制限・再起動方針

gpt-oss は、ChatGPT のようなチャットインターフェースを提供するオープンソースプロジェクトとして注目を集めています。開発環境では docker-compose up だけで簡単に動作しますが、本番環境で安定稼働させるには、ヘルスチェック・リソース制限・再起動方針といった運用設計が欠かせません。

この記事では、gpt-oss を Docker Compose で本番運用する際に必須となる 3 つの設定要素について、具体的な実装方法を交えながら解説します。これらの設定を適切に行うことで、サービスの可用性を高め、障害時の自動復旧やリソース枯渇の防止が実現できるでしょう。

背景

Docker Compose が本番環境で選ばれる理由

Docker Compose は、複数コンテナのオーケストレーションツールとして、開発環境だけでなく本番環境でも広く利用されています。特に小規模から中規模のアプリケーションでは、Kubernetes ほどの複雑さを必要とせず、YAML ファイル 1 つでインフラを管理できる手軽さが魅力です。

gpt-oss のような Web アプリケーションでは、フロントエンド・バックエンド・データベース・Redis など複数のサービスが連携して動作します。Docker Compose を使えば、これらのサービス間の依存関係やネットワーク設定を一元管理でき、デプロイの再現性も確保できますね。

本番運用で求められる信頼性

開発環境では、コンテナが停止しても手動で再起動すれば問題ありません。しかし本番環境では、夜間や休日でもサービスが自動復旧し、ユーザーに影響を与えないことが求められます。

以下の図は、Docker Compose による本番環境の基本構成を示しています。

mermaidflowchart TB
    user["ユーザー"] -->|HTTPS| nginx["Nginx<br/>(リバースプロキシ)"]
    nginx -->|HTTP| frontend["gpt-oss Frontend<br/>(Next.js)"]
    frontend -->|API 呼び出し| backend["Backend API<br/>(Node.js)"]
    backend -->|クエリ| db[("PostgreSQL")]
    backend -->|キャッシュ| redis[("Redis")]

    monitor["Docker Engine"] -.->|ヘルスチェック| frontend
    monitor -.->|ヘルスチェック| backend
    monitor -.->|リソース監視| frontend
    monitor -.->|リソース監視| backend

この図から分かるように、Docker Engine がヘルスチェックとリソース監視を担当し、各コンテナの健全性を保ちます。

gpt-oss の構成要素

gpt-oss は一般的に以下のコンポーネントで構成されています。

#コンポーネント役割重要度
1Frontend (Next.js)ユーザーインターフェース★★★
2Backend API (Node.js)ビジネスロジック・AI 連携★★★
3PostgreSQLユーザーデータ・会話履歴★★★
4Redisセッション・キャッシュ★★☆

これらすべてが正常に動作し続けることで、初めてサービスとして成立します。

課題

開発環境と本番環境のギャップ

開発環境では問題なく動いていた Docker Compose が、本番環境で以下のような問題を引き起こすケースがあります。

リソース枯渇による障害

メモリ制限を設定していないと、バックエンド API が無制限にメモリを消費し、ホストサーバー全体がダウンする恐れがあります。特に AI モデルとの通信で大量のデータを扱う gpt-oss では、メモリリークが発生しやすい傾向にありますね。

異常終了後の放置

コンテナがクラッシュしても再起動設定がないと、サービスが停止したままになってしまいます。ユーザーからの問い合わせで初めて障害に気づくという事態は避けたいものです。

不健全な状態での稼働継続

プロセスは起動しているものの、データベース接続が切れている、API が応答しないといった「半死状態」のコンテナが稼働し続けると、ユーザーはエラー画面を見続けることになります。

以下の図は、これらの課題が発生するフローを示しています。

mermaidstateDiagram-v2
    [*] --> Running: コンテナ起動
    Running --> Unhealthy: メモリリーク発生
    Running --> Crashed: 予期せぬエラー

    Unhealthy --> Running: 設定なし→放置
    Unhealthy --> Restart: ヘルスチェック検知

    Crashed --> Stopped: 再起動設定なし
    Crashed --> Restart: restart=always

    Restart --> Running: 自動復旧
    Stopped --> [*]: サービス停止

    note right of Unhealthy
        応答遅延・エラー頻発
        ユーザー影響大
    end note

    note right of Stopped
        完全停止
        手動復旧が必要
    end note

図で理解できる要点:

  • ヘルスチェックがないと不健全な状態が検知されない
  • 再起動設定がないとクラッシュ後に停止したまま
  • 適切な設定で自動復旧のサイクルが確立される

運用負荷の増大

これらの問題に対処するため、運用担当者が 24 時間監視し、手動で再起動やリソース調整を行うのは現実的ではありません。自動化された仕組みが必要です。

解決策

ヘルスチェックで異常を自動検知

Docker Compose の healthcheck 機能を使うと、コンテナが本当に健全かどうかを定期的に確認できます。単にプロセスが動いているだけでなく、HTTP エンドポイントが正常に応答するかをチェックしましょう。

ヘルスチェックの仕組み

ヘルスチェックは以下のパラメータで制御されます。

#パラメータ説明推奨値
1test実行するコマンドcurl -f http:​/​​/​localhost:3000​/​health
2intervalチェック間隔30s
3timeoutタイムアウト時間10s
4retries失敗許容回数3
5start_period起動猶予期間40s

ヘルスチェックが連続して失敗すると、コンテナは unhealthy 状態になります。この状態を検知して自動的に再起動させることで、サービスの自己修復が可能になりますね。

リソース制限でホストを保護

deploy.resources セクションで CPU とメモリの上限を設定すると、1 つのコンテナが暴走してもホスト全体への影響を抑えられます。

制限設定の考え方

リソース制限には 2 つのレベルがあります。

  • limits:絶対に超えてはいけない上限値
  • reservations:最低限確保する予約値

gpt-oss のバックエンドは AI API との通信で一時的に負荷が高くなるため、通常時は控えめに、ピーク時は上限まで使えるよう設定するのが効果的です。

再起動方針で自動復旧

restart ポリシーを設定すると、コンテナが停止した際の動作を制御できます。

Docker Compose で指定できる再起動ポリシーは以下の通りです。

#ポリシー動作用途
1no再起動しないテスト・開発
2always常に再起動本番サービス
3on-failure異常終了時のみ再起動バッチ処理
4unless-stopped手動停止以外は再起動本番サービス(推奨)

本番環境では unless-stopped を使うことで、手動でのメンテナンス停止は尊重しつつ、予期せぬクラッシュからは自動復旧できます。

以下の図は、再起動ポリシーの動作フローを示しています。

mermaidflowchart TD
    start["コンテナ起動"] --> running["Running 状態"]
    running --> check{"終了原因は?"}

    check -->|正常終了<br/>exit 0| policy1{"restart ポリシー"}
    check -->|異常終了<br/>exit 1-255| policy2{"restart ポリシー"}
    check -->|手動停止<br/>docker stop| policy3{"restart ポリシー"}

    policy1 -->|always| restart["再起動"]
    policy1 -->|unless-stopped| restart
    policy1 -->|on-failure| stopped["停止"]
    policy1 -->|no| stopped

    policy2 -->|always| restart
    policy2 -->|unless-stopped| restart
    policy2 -->|on-failure| restart
    policy2 -->|no| stopped

    policy3 -->|always| restart
    policy3 -->|unless-stopped| stopped
    policy3 -->|on-failure| stopped
    policy3 -->|no| stopped

    restart --> running
    stopped --> finish["終了"]

図で理解できる要点:

  • always はすべてのケースで再起動
  • unless-stopped は手動停止を尊重
  • on-failure は異常終了のみ対応

具体例

docker-compose.yml の基本構成

それでは、gpt-oss の本番環境向け docker-compose.yml を段階的に構築していきましょう。まずは全体の骨格を定義します。

yamlversion: '3.8'

services:
  frontend:
    # Frontend サービス設定(後述)

  backend:
    # Backend サービス設定(後述)

  postgres:
    # PostgreSQL 設定(後述)

  redis:
    # Redis 設定(後述)

networks:
  gpt-oss-network:
    driver: bridge

volumes:
  postgres-data:
  redis-data:

この構成では、4 つのサービスをプライベートネットワークで接続し、データは永続化ボリュームに保存します。

Frontend サービスの設定

Next.js で構築された Frontend には、ヘルスチェック・リソース制限・再起動方針のすべてを適用します。

イメージとネットワークの定義

yamlfrontend:
  image: gpt-oss/frontend:latest
  container_name: gpt-oss-frontend
  networks:
    - gpt-oss-network
  ports:
    - '3000:3000'

このコードでは、Frontend を 3000 番ポートで公開し、内部ネットワークに接続しています。

環境変数の設定

yamlenvironment:
  - NODE_ENV=production
  - NEXT_PUBLIC_API_URL=http://backend:4000
  - NEXT_TELEMETRY_DISABLED=1

本番環境では NODE_ENV=production を必ず設定し、最適化されたビルドを使用しましょう。

ヘルスチェックの実装

yamlhealthcheck:
  test:
    [
      'CMD',
      'curl',
      '-f',
      'http://localhost:3000/api/health',
    ]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

このヘルスチェックは、30 秒ごとに ​/​api​/​health エンドポイントにアクセスし、10 秒以内に応答がなければ失敗と判定します。起動直後の 40 秒間は猶予期間として、失敗してもカウントされません。

リソース制限の設定

yamldeploy:
  resources:
    limits:
      cpus: '1.0'
      memory: 1G
    reservations:
      cpus: '0.5'
      memory: 512M

Frontend は CPU 1 コア、メモリ 1GB を上限とし、通常時は 0.5 コア・512MB を確保します。Next.js のビルド時にメモリを多く使うため、1GB の上限は妥当な設定です。

再起動方針の適用

yamlrestart: unless-stopped
depends_on:
  backend:
    condition: service_healthy

unless-stopped により、クラッシュ時は自動再起動し、手動停止時は停止したままになります。また、Backend のヘルスチェックが通るまで起動を待機しますね。

Backend サービスの設定

Backend API は gpt-oss の心臓部であり、最も慎重なリソース管理が必要です。

基本設定とネットワーク

yamlbackend:
  image: gpt-oss/backend:latest
  container_name: gpt-oss-backend
  networks:
    - gpt-oss-network
  ports:
    - '4000:4000'

Backend は 4000 番ポートで API を提供します。

環境変数とシークレット

yamlenvironment:
  - NODE_ENV=production
  - DATABASE_URL=postgresql://gptoss:password@postgres:5432/gptoss
  - REDIS_URL=redis://redis:6379
  - OPENAI_API_KEY=${OPENAI_API_KEY}
  - JWT_SECRET=${JWT_SECRET}

API キーやシークレットは、.env ファイルや環境変数から注入し、docker-compose.yml にハードコードしないよう注意しましょう。

ヘルスチェックの実装

yamlhealthcheck:
  test:
    ['CMD', 'curl', '-f', 'http://localhost:4000/health']
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 60s

Backend はデータベース接続の初期化に時間がかかるため、start_period を 60 秒に設定しています。

リソース制限の詳細設定

yamldeploy:
  resources:
    limits:
      cpus: '2.0'
      memory: 2G
    reservations:
      cpus: '1.0'
      memory: 1G

Backend は AI API との通信やデータ処理で負荷が高いため、CPU 2 コア・メモリ 2GB を上限とします。これにより、複数のユーザーが同時にチャットしても安定稼働できるでしょう。

再起動方針と依存関係

yamlrestart: unless-stopped
depends_on:
  postgres:
    condition: service_healthy
  redis:
    condition: service_started

PostgreSQL のヘルスチェックが通過してから起動することで、データベース接続エラーを防ぎます。

PostgreSQL の設定

データベースは永続化とパフォーマンスのバランスが重要です。

基本設定

yamlpostgres:
  image: postgres:15-alpine
  container_name: gpt-oss-postgres
  networks:
    - gpt-oss-network

Alpine ベースのイメージを使うことで、軽量かつセキュアな環境を構築します。

環境変数とボリューム

yamlenvironment:
  - POSTGRES_USER=gptoss
  - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  - POSTGRES_DB=gptoss
  - PGDATA=/var/lib/postgresql/data/pgdata
volumes:
  - postgres-data:/var/lib/postgresql/data

データは名前付きボリュームに永続化し、コンテナを削除してもデータが失われないようにします。

ヘルスチェックの実装

yamlhealthcheck:
  test: ['CMD-SHELL', 'pg_isready -U gptoss -d gptoss']
  interval: 10s
  timeout: 5s
  retries: 5
  start_period: 30s

pg_isready コマンドでデータベースが接続可能かを確認します。データベースは他のサービスの前提条件なので、チェック間隔を 10 秒と短く設定していますね。

リソース制限

yamldeploy:
  resources:
    limits:
      cpus: '1.0'
      memory: 1G
    reservations:
      cpus: '0.5'
      memory: 512M
restart: unless-stopped

PostgreSQL は効率的なメモリ管理を行うため、1GB の上限で十分なパフォーマンスを発揮します。

Redis の設定

Redis はセッション管理とキャッシュに使用します。

基本設定

yamlredis:
  image: redis:7-alpine
  container_name: gpt-oss-redis
  networks:
    - gpt-oss-network
  command: redis-server --appendonly yes

--appendonly yes で AOF 永続化を有効にし、データの耐久性を確保します。

ボリュームとヘルスチェック

yamlvolumes:
  - redis-data:/data
healthcheck:
  test: ['CMD', 'redis-cli', 'ping']
  interval: 10s
  timeout: 3s
  retries: 3
  start_period: 10s

redis-cli ping で Redis が応答するかを確認します。Redis は起動が速いため、start_period は 10 秒で十分でしょう。

リソース制限

yamldeploy:
  resources:
    limits:
      cpus: '0.5'
      memory: 512M
    reservations:
      cpus: '0.25'
      memory: 256M
restart: unless-stopped

Redis は軽量なため、控えめなリソース制限で動作します。

ヘルスチェックエンドポイントの実装

Docker Compose のヘルスチェックを機能させるには、アプリケーション側にヘルスチェックエンドポイントを用意する必要があります。

Backend のヘルスチェックエンドポイント

以下は Express.js での実装例です。

typescriptimport express from 'express';
import { Router } from 'express';

const healthRouter = Router();

healthRouter.get('/health', async (req, res) => {
  try {
    // データベース接続確認
    await checkDatabaseConnection();

このエンドポイントでは、データベースへの接続を確認します。

typescript// Redis 接続確認
await checkRedisConnection();

// レスポンス返却
res.status(200).json({
  status: 'healthy',
  timestamp: new Date().toISOString(),
  uptime: process.uptime(),
});

すべてのチェックが成功したら、ステータス 200 で健全性を報告します。

typescript  } catch (error) {
    // エラー時は 503 Service Unavailable を返す
    res.status(503).json({
      status: 'unhealthy',
      error: error.message,
      timestamp: new Date().toISOString()
    });
  }
});

export default healthRouter;

エラーが発生した場合は 503 Service Unavailable を返し、Docker にコンテナが不健全であることを通知します。

データベース接続確認の実装

typescriptimport { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function checkDatabaseConnection(): Promise<void> {
  // シンプルなクエリで接続確認
  await prisma.$queryRaw`SELECT 1`;
}

Prisma を使っている場合、$queryRaw で簡単なクエリを実行して接続を確認できますね。

Redis 接続確認の実装

typescriptimport Redis from 'ioredis';

const redis = new Redis({
  host: process.env.REDIS_HOST || 'redis',
  port: parseInt(process.env.REDIS_PORT || '6379'),
});

async function checkRedisConnection(): Promise<void> {
  // PING コマンドで接続確認
  const result = await redis.ping();
  if (result !== 'PONG') {
    throw new Error('Redis connection failed');
  }
}

Redis の PING コマンドで接続を確認し、PONG が返ってこなければエラーをスローします。

本番環境へのデプロイ手順

設定が完成したら、以下の手順で本番環境にデプロイします。

環境変数ファイルの準備

bash# .env.production ファイルを作成
cat > .env.production << 'EOF'
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
JWT_SECRET=your-secure-random-secret
POSTGRES_PASSWORD=your-database-password
EOF

機密情報は .env.production ファイルに記載し、Git にはコミットしません。

イメージのビルドとプッシュ

bash# Frontend イメージをビルド
docker build -t gpt-oss/frontend:latest ./frontend

# Backend イメージをビルド
docker build -t gpt-oss/backend:latest ./backend

本番環境で使用するイメージを事前にビルドしておきます。

Docker Compose の起動

bash# 環境変数を読み込んで起動
docker-compose --env-file .env.production up -d

# ログを確認
docker-compose logs -f

-d オプションでバックグラウンド起動し、ログで起動状態を確認しましょう。

ヘルスチェック状態の確認

bash# すべてのコンテナの状態を確認
docker-compose ps

# 特定のコンテナのヘルスチェック詳細を確認
docker inspect gpt-oss-backend | grep -A 10 Health

docker-compose psSTATUS 列に (healthy) と表示されれば、ヘルスチェックが正常に機能しています。

トラブルシューティング

本番運用で発生しやすい問題と対処法を紹介します。

Error: Container is unhealthy

ヘルスチェックが連続して失敗し、コンテナが unhealthy 状態になった場合の対処法です。

bash# ヘルスチェックログを確認
docker inspect --format='{{json .State.Health}}' gpt-oss-backend | jq

# コンテナ内でヘルスチェックコマンドを手動実行
docker exec gpt-oss-backend curl -f http://localhost:4000/health

ヘルスチェックコマンドを手動で実行し、エラー内容を特定します。データベース接続エラーやポート設定ミスが原因のケースが多いですね。

Error: OOMKilled (Out of Memory)

メモリ制限を超えてコンテナが強制終了された場合です。

bash# コンテナの終了理由を確認
docker inspect gpt-oss-backend | grep OOMKilled

# メモリ使用状況をモニタリング
docker stats gpt-oss-backend

OOMKilled: true と表示された場合は、deploy.resources.limits.memory の値を増やす必要があります。

yamldeploy:
  resources:
    limits:
      memory: 3G # 2G から 3G に増量

メモリ使用量の傾向を観察し、ピーク時に余裕を持った値に設定しましょう。

Error: Container restart loop

コンテナが起動と停止を繰り返す場合の対処法です。

bash# 直近のログを確認
docker-compose logs --tail=100 backend

# 再起動回数を確認
docker inspect gpt-oss-backend | grep RestartCount

起動時のエラーログから原因を特定します。環境変数の設定ミス、データベース接続失敗、ポート競合などが考えられます。

監視とアラート設定

本番環境では、ヘルスチェックの結果を監視システムに連携することが重要です。

Prometheus との連携

以下は、Docker のヘルスチェック状態を Prometheus で収集する設定例です。

yaml# prometheus.yml に追加
scrape_configs:
  - job_name: 'docker'
    static_configs:
      - targets: ['localhost:9323']

Docker Engine のメトリクスエンドポイントから、コンテナのヘルスチェック状態を収集できます。

アラート条件の定義

yaml# alert.rules.yml
groups:
  - name: docker_health
    interval: 30s
    rules:
      - alert: ContainerUnhealthy
        expr: docker_container_health_status != 1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: 'コンテナ {{ $labels.container }} が unhealthy 状態'
          description: '2分以上 unhealthy 状態が続いています'

この設定により、コンテナが 2 分以上 unhealthy 状態になるとアラートが発火します。

まとめ

gpt-oss を Docker Compose で本番運用する際には、ヘルスチェック・リソース制限・再起動方針の 3 つの設定が欠かせません。これらを適切に実装することで、以下のメリットが得られます。

可用性の向上

ヘルスチェックと再起動方針により、障害発生時の自動復旧が実現し、サービスの稼働率が向上します。夜間や休日でも、人手を介さずにサービスが復旧するのは大きな安心材料ですね。

リソース枯渇の防止

メモリや CPU の制限により、1 つのコンテナが暴走してもホスト全体への影響を抑えられます。複数のサービスを同じサーバーで運用する場合、この設定は必須と言えるでしょう。

運用負荷の軽減

自動化された監視と復旧により、運用担当者の負担が大幅に軽減されます。手動での再起動やリソース調整の頻度が減り、より戦略的な業務に時間を使えますね。

本番環境では、これらの設定を組み合わせて使用することで、安定したサービス提供が可能になります。最初は控えめなリソース制限から始め、実際の負荷を観察しながら調整していくアプローチをお勧めします。

また、ヘルスチェックエンドポイントは単純な「生存確認」だけでなく、外部サービスとの接続状態まで含めた「健全性確認」を実装することで、より信頼性の高いシステムが構築できるでしょう。

関連リンク