コンテナが「仮死状態」なのにDockerが気付けない理由
こんな苦い経験はないでしょうか。顧客から「システムが落ちている」と連絡が入り、慌ててターミナルを開いて docker ps を実行すると、コンテナのステータスには平然と Up 3 days と表示されている……。
外見上はプロセスが動いているように見えます。しかし内部では、アプリケーションがデッドロックに陥っていたり、メモリリークを起こしていたり、データベース接続が枯渇していたりして、送られたリクエストがことごとくタイムアウトしているのです。
これは、レジ打ちの店員がレジカウンターで気を失って倒れているような状態です。遠目には席に人が座っているように見えても、客の行列は伸びるばかりで会計は一向に進みません。Dockerのデフォルトの仕組みは、メインプロセス(PID 1)しか監視していません。このプロセスがクラッシュしてエラーコードで終了しない限り、Dockerはすべて正常に動いていると思い込んでしまいます。
筆者も以前、Docker Composeを使い始めたばかりの頃にこの罠にハマりました。設定ファイルに restart: always を追加して「これで安心」と眠りについたところ、深夜2時にNode.jsアプリが2GBのメモリを食い潰して完全にハングアップ。プロセス自体は落ちていなかったため、Dockerは頑なに再起動してくれませんでした。朝起きると、上長からの着信やチャット通知の嵐。そのときの応急処置といえば、サーバーにSSHログインして、ただひとつのコマンド docker restart を叩くことだけでした。
必要なのは、より能動的な仕組みです。アプリケーションが実際にリクエストに応答できているかを継続的にチェックしなければなりません。そこで活躍するのが、Docker HealthcheckとDocker Autohealの組み合わせです。
救世主となる組み合わせ:HealthcheckとDocker Autoheal
コンテナを自動復旧させるには、「状態を診断する仕組み」と「復旧アクションを実行する仕組み」の2つが必要です。
1. Docker Healthcheck
Healthcheckは、Dockerfileや docker-compose.yml 内で定義するヘルスチェックディレクティブです。指定した間隔(例えば10秒ごと)で、Dockerがコンテナ内部でテストコマンドを定期実行します。
- Healthy: コマンドが終了コード
0(HTTP 200など)を返した場合。アプリは正常に稼働しています。 - Unhealthy: リトライ上限回数を超えてエラー(終了コード
1)が返された場合。Dockerはそのコンテナに unhealthy のラベルを付与します。
しかし、決定的な弱点があります。標準のDockerは「警告ラベルを貼る」だけで処理を終えてしまいます。コンテナが unhealthy になっても、Docker Composeが勝手に再起動してくれるわけではありません。
2. Docker Autoheal
Docker Autoheal(Will Farrell氏作成)は、メモリ消費量が20MB未満という極めて軽量な監視用コンテナです。ホスト上のDockerソケットに直接接続して動作します。
Autohealはコンテナの状態を定期的に巡回監視し、unhealthy フラグの立ったコンテナを見つけると、即座にDockerデーモンへ再起動コマンドを発行します。夜中に起きてコマンドを手打ちすることなく、数秒のうちに自動で復旧処理が完了します。
Docker ComposeでのDocker Autoheal設定
ここでは、Autohealで自動監視するNginx Webアプリケーションの構築手順を紹介します。
ステップ1:docker-compose.ymlの作成
新規ディレクトリを作成し、docker-compose.yml を記述します:
services:
# 1. 監視対象のWebアプリケーション
my-web-app:
image: nginx:alpine
container_name: my-web-app
ports:
- "8080:80"
restart: always
labels:
# Autohealにこのコンテナの監視を指示
- "autoheal=true"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
interval: 10s # 10秒ごとにチェック
timeout: 3s # タイムアウトは最大3秒
retries: 3 # 3回連続失敗でunhealthyと判定
start_period: 5s # 起動後5秒待機してからチェック開始
# 2. 障害時に自動再起動を行うコンテナ
autoheal:
image: willfarrell/autoheal:latest
container_name: autoheal
restart: always
environment:
- AUTOHEAL_INTERVAL=5 # 5秒ごとにステータスをスキャン
- AUTOHEAL_START_PERIOD=10 # 起動後10秒待機してからスキャン開始
- AUTOHEAL_DEFAULT_STOP_TIMEOUT=10 # 安全に停止するまでの猶予時間
volumes:
# Autohealがコンテナを再起動できるようDockerソケットをマウント
- /var/run/docker.sock:/var/run/docker.sock
主要パラメータのポイント
autoheal=true:Autohealが監視対象を識別するための必須ラベルです。これにより、バッチ処理中のワーカーコンテナなどを誤って再起動してしまう事故を防げます。/var/run/docker.sock:Dockerデーモンと直接通信するためのソケットです。セキュリティ上の注意点として、このソケットをマウントしたコンテナは、実質的にホスト上のrootユーザーと同等の権限を持つことになります。curl -f http://localhost/:-f(fail silently)フラグを付けることで、HTTP 4xxや5xxエラーの際にcurlがエラーコードを返し、exit 1の条件が成立します。
ステップ2:サービスの起動
以下のコマンドで2つのサービスをバックグラウンド起動します:
docker compose up -d
15秒ほど待ってから、コンテナ一覧を確認してみましょう:
docker ps
my-web-app の STATUS カラムを確認すると、Up ... (healthy) と表示されているはずです。ヘルスチェックのエンドポイントが正常に動作しています。
ステップ3:自動復旧の動作テスト
障害発生時(404エラーの返却)をシミュレートするため、コンテナ内の index.html のファイル名を一時的に変更してみます:
docker exec -it my-web-app mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index.html.bak
別のターミナルタブを開き、Autohealのログをリアルタイムで監視します:
docker logs -f autoheal
curlの失敗が3回続くと(約30秒後)、Dockerがコンテナを unhealthy と判定します。Autohealがそのイベントを検知し、直ちに自動再起動を実行します:
Container my-web-app (9a1f2b3c4d5e) found to be 'unhealthy'
Restarting container my-web-app (9a1f2b3c4d5e)...
Container my-web-app (9a1f2b3c4d5e) restarted
再起動コマンドが完全に自動でトリガーされました。再起動プロセスを確認できたら、ファイル名を元に戻してアプリケーションを正常な状態に復元しておきましょう:
docker exec -it my-web-app mv /usr/share/nginx/html/index.html.bak /usr/share/nginx/html/index.html
コネクションプールの枯渇やヒープメモリの圧迫といった実際の障害時にも、この再起動によってリソースが解放され、迅速にサービスを復旧させることができます。
まとめ
自己修復機能を実現するためだけに、大規模で複雑なKubernetesクラスタを導入する必要はありません。単一VPSで運用する中小規模システムであれば、Docker Healthcheck と Docker Autoheal の組み合わせが最も軽量で確実な選択肢となります。
ただし、Autohealはあくまで一時的な応急処置に過ぎません。ログを確認して毎時間のように再起動が発生している場合は、根本原因を解決するためにソースコードのデバッグを行いましょう。
