できない.dev

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 の値を順に疑う。

よくある原因

  1. CMD が exit 0 を返していない: 期待する 200 が返らない、ホスト名解決に失敗している、認証が必要になっている、など意外と外的要因が多い。
  2. curl / wget が無い: alpine ベースのイメージは既定で curl を含まないため、HEALTHCHECK CMD curl -f ... が即 not found で落ちる。
  3. start-period 不足: アプリが起動完了する前から check が始まり、初期化中に何度も失敗扱いされる。
  4. 書式の罠: 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 件の ExitCodeOutput が入っているので、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 1

wget も無いミニマムイメージなら、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 形式に戻す必要がある。

この記事は役立ちましたか?