アプリの起動がデータベースより早いと何が起きるのか?
マイクロサービスを開発し始めたばかりの頃、非常に頭の痛い問題に直面しました。サーバーのメンテナンス時や 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_on と healthcheck のコンビネーションが必要です。これにより、手動での介入なしにコンテナ群をスムーズに連携させることができます。
「実践的」な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) の場合
アプリにチェック用のツールがない場合は、Dockerfileで curl をインストールし、シンプルな /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つのミスを振り返ります。
depends_onを単純なリスト形式で使い、アプリの準備ができるのを期待してしまう。start_periodの設定を忘れ、Dockerがコンテナをエラーと見なして再起動を繰り返す(ブートループ現象)。- サーバーのディスク負荷(I/Oウェイト)が高い時に
timeoutを短く設定しすぎて、誤検知を招く。
この2つの機能をマスターすることで、設定ファイルがプロフェッショナルになるだけでなく、深夜にサーバーの再起動で起こされる心配もなくなり、安眠できるようになります。スムーズなセットアップを祈っています!

