Docker Compose: Health CheckでDBが完全に起動してからAppを実行する方法

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

アプリの起動がデータベースより早いと何が起きるのか?

マイクロサービスを開発し始めたばかりの頃、非常に頭の痛い問題に直面しました。サーバーのメンテナンス時や docker-compose up -d を実行するたびに、Node.jsアプリが何度もクラッシュしてしまったのです。Composeファイルには depends_on: - db を慎重に設定していたにもかかわらず、ログには真っ赤な Connection Refused エラーが表示されていました。

一晩中ログを調査した結果、原因がわかりました。DockerはDBコンテナが Running 状態にあるかどうかしか見ていなかったのです。中のMySQLやPostgresが実際にリクエストを受け付けられる状態(「開店」状態)かどうかは関知していませんでした。実際、MySQL 8.0は初期化に12〜15秒ほどかかりますが、アプリはわずか2秒で起動します。その結果、DBが設定を読み込んでいる最中にアプリが接続を試み、あえなく撃沈していたのです。

この状況を根本的に解決するには、高度な設定の depends_onhealthcheck のコンビネーションが必要です。これにより、手動での介入なしにコンテナ群をスムーズに連携させることができます。

「実践的」なDocker Composeの設定

すぐに適用したい方のために、DBが単に実行中であるだけでなく、準備完了(READY) になるまでWebアプリを待機させる標準的なテンプレートを紹介します。

version: '3.8'
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: my_app
    healthcheck:
      # pg_isreadyを使用してDBの準備ができているか確認
      test: ["CMD-SHELL", "pg_isready -U user -d my_app"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s

  web-app:
    build: .
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://user:password@db:5432/my_app

この数行の healthcheck を追加するだけで、デプロイのたびに手動でサーバーを再起動する手間を少なくとも3回は省けます。非常に価値のある設定です!

なぜ depends_on だけでは不十分なのか?

Dockerに慣れていない多くの人は、depends_on がすべてを解決してくれると思いがちです。しかし、Docker Engineはプロセスレベルでのみ管理を行っています。

  • コンテナ層: メインプロセス(PID 1)が起動したため、Dockerはコンテナが実行中であると報告します。
  • アプリケーション層: Java Spring BootやOracle DBのような重いサービスは、設定の読み込みやポートの確保に時間がかかり、リクエストを処理できる状態になるまでラグがあります。

Dockerは、チェック方法を指示しない限り、アプリの状態を内部まで把握することはできません。そこで登場するのが Health Check です。

Health Checkを深掘りする – 献身的な「警備員」

Health Checkを、5秒おきにコンテナのドアを叩いて「生きてるか?」と尋ねる警備員だと想像してみてください。コンテナが正しい合言葉(終了コード 0)を返せば、healthy というラベルが貼られます。反応が長すぎる場合、そのコンテナは「救済不能」と見なされます。

調整が必要なパラメータ:

  • test: チェック用のコマンド。curl でAPIを叩いたり、mysqladmin ping のような内部コマンドを使用したりします。
  • interval: チェックの間隔。CPUリソースを無駄に消費しないよう、短すぎ(1秒など)ないように設定しましょう。
  • timeout: 1回のチェックにおける最大待機時間。
  • retries: Dockerがコンテナを正式にエラーと判断するまでの連続試行回数。
  • start_period: 「猶予期間」。この期間中は、チェックが失敗してもDockerは無視します。MagentoやJavaのように、起動が非常に遅いアプリには必須です。

サービスごとのカスタマイズの秘訣

サービスの種類によって「生存確認」の方法は異なります。以下はプロジェクトによくコピー&ペーストして使っている例です。

1. MySQL / MariaDB の場合

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
  interval: 10s
  retries: 3

2. Redis の場合

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 5s

3. Web API (Node.js, Python, Go) の場合

アプリにチェック用のツールがない場合は、Dockerfilecurl をインストールし、シンプルな /health エンドポイントを作成します。

healthcheck:
  # curlを使用してヘルスチェック用エンドポイントを確認
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3

実践的な経験:すべてをDocker任せにしない

長年システムを運用してきた中で、得られた3つの教訓を共有します。

第一に、常にコード側に「リトライロジック」を持たせること。 Docker Composeがどれほど優れていても、内部ネットワークで一時的なラグが発生することがあります。DB接続コードには、完全に停止する前に5〜10回程度リトライするループを含めるべきです。これにより、システムの自己修復能力が格段に向上します。

第二に、重すぎるHealth Checkを避けること。 数百万件のレコードを検索するSQLクエリでチェックを行うような真似はしないでください。チェックのたびにCPU使用率が跳ね上がっては本末転倒です。ping のような最も軽量なコマンドを使うだけで十分です。

第三に、Composeのバージョンを確認すること。 condition: service_healthy 機能は、Docker Composeファイル 3.x 以降を必要とします。「石器時代」の古いバージョンを使っている場合は、この利便性を享受するために今すぐアップグレードしましょう。

よくある間違いのまとめ

最後に、よく陥りがちな3つのミスを振り返ります。

  1. depends_on を単純なリスト形式で使い、アプリの準備ができるのを期待してしまう。
  2. start_period の設定を忘れ、Dockerがコンテナをエラーと見なして再起動を繰り返す(ブートループ現象)。
  3. サーバーのディスク負荷(I/Oウェイト)が高い時に timeout を短く設定しすぎて、誤検知を招く。

この2つの機能をマスターすることで、設定ファイルがプロフェッショナルになるだけでなく、深夜にサーバーの再起動で起こされる心配もなくなり、安眠できるようになります。スムーズなセットアップを祈っています!

Share: