Docker の HEALTHCHECK がいつも unhealthy になる
HEALTHCHECK は CMD が exit 0 で healthy、それ以外で unhealthy。
コンテナに curl 等が無い、起動待ち時間不足、shell / exec 形式の書き分けが主因。
docker inspect の Health.Log で実エラーを確認する。
#docker#healthcheck#dockerfile#container#status
公開:
要約
Docker の HEALTHCHECK は指定した CMD が exit 0 を返したときだけ healthy、それ以外は unhealthy として扱われる。
常に unhealthy になるときは、まず docker inspect でログを確認し、CMD の中身(コマンドの存在、ポート、exit code)と --start-period の値を順に疑う。
よくある原因
- CMD が exit 0 を返していない: 期待する 200 が返らない、ホスト名解決に失敗している、認証が必要になっている、など意外と外的要因が多い。
- curl / wget が無い:
alpineベースのイメージは既定でcurlを含まないため、HEALTHCHECK CMD curl -f ...が即not foundで落ちる。 - start-period 不足: アプリが起動完了する前から check が始まり、初期化中に何度も失敗扱いされる。
- 書式の罠: shell 形式
HEALTHCHECK CMD curl -f http://localhost/と exec 形式HEALTHCHECK CMD ["curl", "-f", "http://localhost/"]で挙動が変わる。
リダイレクトやパイプを使うなら shell 形式が必要。
解決策
1. まず Health.Log を見る
docker inspect --format '{{json .State.Health}}' <container>Log 配列に直近 5 件の ExitCode と Output が入っているので、CMD が何を出力したかを直接確認する。
詳細は docker inspect 公式 を参照。
2. curl が無いイメージへの対応
# alpine の場合
RUN apk add --no-cache curl
# あるいは wget で代替
HEALTHCHECK CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1wget も無いミニマムイメージなら、Node や Python など同梱済みの言語で軽い HTTP リクエストを 1 行書く方法もある。
3. start-period を伸ばす
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1--start-period の間は失敗してもカウントされず、起動に時間のかかるアプリで unhealthy 確定を防げる。
各オプションは HEALTHCHECK 公式リファレンス のとおり。
4. exec 形式で曖昧さを除く
HEALTHCHECK CMD ["curl", "-f", "http://localhost:8080/health"]shell の解釈が挟まらないため、変数展開・パイプを使わないなら exec 形式が安全。|| exit 1 を入れるなら shell 形式に戻す必要がある。