- 設計
ヘルスチェック設計|livenessとreadinessの違いと実装例
ヘルスチェックとは、アプリケーションが「動いているか」「リクエストを受けられるか」を外から確かめるための専用エンドポイントです。 設計の中心は、liveness(生存確認)と readiness(受け入れ準備の確認)を別のパスに分け、それぞれが何を確認するかを決めることにあります。
この記事では、エンドポイントの設計に絞って、比較表、設計書のテンプレートと記入例、応答 JSON の仕様例、FastAPI・Express の動く最小実装と curl の結果、Spring Boot Actuator の設定、切り離しまでの秒数の計算をまとめます。監視項目全体やしきい値の決め方は扱いません。
確認環境(確認日 2026-10-06):Ubuntu 24.04、Python 3.11.15+FastAPI 0.142.2+uvicorn 0.46.0、Node.js 22.22.2+Express 5.2.1、curl 8.5.0。Kubernetes と Spring Boot Actuator の内容は、今回開いた公式ドキュメント(Kubernetes API リファレンス v1.34、Spring Boot 4.1.1 リファレンス)の記載にもとづきます。
この記事の結論
- liveness は依存先を見ない。プロセス自身が応答できるかだけを返す(異常なら再起動される)。
- readiness は必須の依存先だけ見る。受けられないときは 503 を返し、振り分け先から一時的に外してもらう(再起動はされない)。
- startup は起動完了だけを見る。起動に時間がかかるアプリで、liveness による早すぎる再起動を防ぐ。
- 依存先を含めるかは 「落ちたら再起動で直るか」 で決める。再起動で直らないものを liveness に入れない。
- 切り離しまでの時間は 間隔 × しきい値 + タイムアウト が上限の目安。既定値(10秒・3回・1秒)なら最長31秒。
ヘルスチェックの種類の早見表
| 種類 | 問い | 確認するもの | 異常時に起きること | 確認する主体 | パスの例 |
|---|---|---|---|---|---|
| liveness | プロセスは応答できるか | アプリ自身のみ(依存先は見ない) | コンテナを再起動 | オーケストレーター(kubelet など) | /livez |
| readiness | いまリクエストを受けられるか | 起動完了+必須の依存先 | 振り分け先から外す(再起動なし) | オーケストレーター | /readyz |
| startup | 起動は終わったか | 初期化の完了 | しきい値を超えたら再起動。成功するまで他の2つは始まらない | オーケストレーター | /startupz |
| LB ヘルスチェック | このサーバーへ振り分けてよいか | readiness と同じ内容 | 振り分け対象から外す | ロードバランサー | /readyz |
| 外形監視 | 利用者から見て使えるか | 公開URLからの主要な操作 | 担当者へ通知(自動の切り離しはしない) | 外部の監視サービス | / や主要API |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ヘルスチェック設計|livenessとreadinessの違いと実装例」
livenessとreadinessの違い
違いは 「異常時に何をしてほしいか」 です。liveness は「再起動してほしい」、readiness は「少し待ってほしい」という合図になります。
| 観点 | liveness | readiness |
|---|---|---|
| 伝えたいこと | 自力で回復できない。再起動してほしい | いまは受けられない。振り分けを止めてほしい |
| 異常時の動作 | コンテナの再起動 | 振り分け先から外す。回復すれば自動で戻る |
| データベースの確認 | 入れない | 必須なら入れる |
| 外部APIの確認 | 入れない | 無くても動ける機能なら入れない |
| 処理の重さ | 定数を返す程度 | 短いタイムアウトつきの軽い確認 |
| 向いている異常 | デッドロック、応答不能 | 起動直後、依存先の一時的な不調、過負荷 |
Kubernetes の公式ドキュメントでも、liveness は「コンテナをいつ再起動するか」、readiness は「コンテナがいつトラフィックを受けられるか」を決めるものと説明されています。startup は起動時だけ実行され、成功するまで liveness と readiness は実行されません。
依存先をlivenessに入れるとどうなるか
データベースが一時的に応答しなくなった場面を考えます。liveness にデータベースの確認が入っていると、全インスタンスが同時に「異常」と判定されて一斉に再起動 されます。アプリを再起動してもデータベースは直らないため、再起動が繰り返され、データベースが戻ったあとの復帰も遅れます。
readiness だけに入れておけば、アプリは起動したまま振り分けから外れ、データベースが戻った時点で自動的に復帰します。Spring Boot のリファレンスにも、liveness は外部システムのヘルスチェックに依存させない、という方針が書かれています。
依存先を含めるかどうかの判断基準
| 条件 | liveness | readiness | 理由 |
|---|---|---|---|
| アプリを再起動すれば直る(デッドロック、内部状態の破損) | 含める | — | 再起動が回復手段になる |
| 再起動しても直らない、必須の依存先(主データベース) | 含めない | 含める | 待てば戻る。振り分けだけ止める |
| 無くても縮退して動ける依存先(キャッシュ、検索、通知) | 含めない | 含めない | 外すと全体が止まる。応答の checks に warn で載せるか、メトリクスで見る |
| 全インスタンスが共有する外部API | 含めない | 原則含めない | 全台が同時に外れ、受け口が無くなる |
| 起動時だけ必要な準備(設定の読み込み、マイグレーションの完了) | 含めない | 含める | startup でも確認する |
迷ったら 「これが落ちたとき、このインスタンスだけを外せば利用者は助かるか」 と問います。全インスタンスが同じ結果になる確認は、readiness に入れても振り分け先が0台になるだけなので、アラートで人に知らせる側に回します。
ヘルスチェック設計書のテンプレート
そのまま設計書に貼れる形です。1エンドポイントを1行で書きます。
## ヘルスチェック設計
### 1. エンドポイント一覧
| No | 種類 | パス | 確認対象 | 確認方法 | タイムアウト | 間隔 | 失敗しきい値 | 成功しきい値 | 正常時 | 異常時 | 認証 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | liveness | | | | | | | | | | |
| 2 | readiness | | | | | | | | | | |
| 3 | startup | | | | | | | | | | |
### 2. 依存先ごとの扱い
| 依存先 | 必須か | liveness | readiness | 確認方法 | 個別タイムアウト | 落ちたときの動作 |
|---|---|---|---|---|---|---|
| | | | | | | |
### 3. 応答仕様
- 正常時のステータスコード:
- 異常時のステータスコード:
- 本文の形式:
- キャッシュ:
### 4. 切り離し・再起動までの時間
- readiness:間隔 × しきい値 + タイムアウト =
- liveness:間隔 × しきい値 + タイムアウト =
- startup:間隔 × しきい値 = (起動にかかる最長時間より長いこと)
記入例(架空の備品予約システムのAPI)
| No | 種類 | パス | 確認対象 | 確認方法 | タイムアウト | 間隔 | 失敗しきい値 | 成功しきい値 | 正常時 | 異常時 | 認証 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | liveness | /livez |
アプリのプロセス | 固定の応答を返す | 2秒 | 10秒 | 6回 | 1回 | 200 | 応答なしまたは 5xx | なし |
| 2 | readiness | /readyz |
起動完了、予約データベース | 接続して SELECT 1 |
3秒 | 5秒 | 2回 | 1回 | 200 | 503 | なし |
| 3 | startup | /startupz |
設定の読み込み、接続プールの準備 | 起動完了フラグ | 2秒 | 10秒 | 30回 | 1回 | 200 | 503 | なし |
| 依存先 | 必須か | liveness | readiness | 確認方法 | 個別タイムアウト | 落ちたときの動作 |
|---|---|---|---|---|---|---|
| 予約データベース | 必須 | 見ない | 見る | SELECT 1 |
1秒 | 503 を返し振り分けから外れる |
| キャッシュ | 任意 | 見ない | 見ない | — | — | データベースから直接読む |
| メール通知 | 任意 | 見ない | 見ない | — | — | 送信を後回しにして再試行 |
表計算ソフトに貼るときは、次のタブ区切りを使います。
No 種類 パス 確認対象 確認方法 タイムアウト 間隔 失敗しきい値 成功しきい値 正常時 異常時 認証
1 liveness /livez アプリのプロセス 固定の応答を返す 2秒 10秒 6回 1回 200 応答なしまたは5xx なし
2 readiness /readyz 起動完了、予約データベース 接続してSELECT 1 3秒 5秒 2回 1回 200 503 なし
3 startup /startupz 設定の読み込み、接続プールの準備 起動完了フラグ 2秒 10秒 30回 1回 200 503 なし
数値はすべて例です。liveness のしきい値を readiness より大きく しておくと、まず振り分けから外れ、それでも戻らないときに再起動される、という順になります。
応答JSONの仕様例
ステータスコードだけで判定できるようにし、本文は人が原因を読むための補助にします。
| 項目 | 仕様例 |
|---|---|
| 正常時 | 200 |
| 異常時 | 503(Service Unavailable) |
| 本文 | JSON。全体の status と、依存先ごとの checks |
status の値 |
pass(正常)/fail(異常)/warn(動くが気になる点あり。ステータスコードは 200) |
checks の各要素 |
status、time_ms(確認にかかった時間) |
| キャッシュ | Cache-Control: no-store |
| 載せないもの | 接続文字列、ホスト名、バージョンの詳細、エラーのスタックトレース |
正常時:
{"status": "pass", "checks": {"database": {"status": "pass", "time_ms": 3}}}
異常時(503):
{"status": "fail", "checks": {"database": {"status": "fail", "time_ms": 1001}}}
pass/fail/warn という値は、IETF に提出された Internet-Draft「Health Check Response Format for HTTP APIs」(draft-inadarei-api-health-check-06、メディアタイプ application/health+json)の語彙に合わせています。この文書は期限切れの草案で正式な標準ではないため、語彙をそろえる参考 として使い、項目は必要な分だけにしています。
Kubernetes の HTTP による確認は、公式ドキュメントによると 200 以上 400 未満を成功 とみなします。本文は判定に使われないので、異常時に 200 を返して本文だけ fail にする形は避けます。
FastAPIの最小実装とcurlの実行結果
依存先(ここではデータベースの待ち受けポート)への接続確認を、1秒の上限つきで行う例です。起動時の準備は3秒の待ちで模しています。
# health_app.py (FastAPI 0.142 / Python 3.11)
import asyncio
import os
import time
from contextlib import asynccontextmanager
from fastapi import FastAPI, Response
DEP_HOST = os.environ.get("DEP_HOST", "127.0.0.1") # 必須の依存先(例:データベース)
DEP_PORT = int(os.environ.get("DEP_PORT", "5432"))
CHECK_TIMEOUT = 1.0 # 依存先1件あたりの確認時間の上限(秒)
WARMUP_SECONDS = 3 # 起動時の準備にかかる時間を模したもの
state = {"started": False, "started_at": None}
async def warmup():
await asyncio.sleep(WARMUP_SECONDS) # 設定の読み込みやキャッシュの準備を想定
state["started"] = True
state["started_at"] = time.time()
@asynccontextmanager
async def lifespan(app: FastAPI):
task = asyncio.create_task(warmup())
yield
task.cancel()
app = FastAPI(lifespan=lifespan)
async def check_tcp(host: str, port: int) -> dict:
"""依存先に接続できるかを、時間の上限つきで確かめる"""
t0 = time.perf_counter()
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection(host, port), timeout=CHECK_TIMEOUT
)
writer.close()
await writer.wait_closed()
status = "pass"
except (OSError, asyncio.TimeoutError):
status = "fail"
return {"status": status, "time_ms": round((time.perf_counter() - t0) * 1000)}
@app.get("/livez")
async def livez():
# liveness:プロセスが応答できるかだけを返す。依存先は見ない
return {"status": "pass"}
@app.get("/startupz")
async def startupz(response: Response):
# startup:起動時の準備が終わったか
if not state["started"]:
response.status_code = 503
return {"status": "fail", "reason": "starting"}
return {"status": "pass"}
@app.get("/readyz")
async def readyz(response: Response):
# readiness:準備が終わり、必須の依存先に届くか
response.headers["Cache-Control"] = "no-store"
if not state["started"]:
response.status_code = 503
return {"status": "fail", "reason": "starting"}
checks = {"database": await check_tcp(DEP_HOST, DEP_PORT)}
ok = all(c["status"] == "pass" for c in checks.values())
if not ok:
response.status_code = 503
return {"status": "pass" if ok else "fail", "checks": checks}
接続確認の部分は、使っているドライバで SELECT 1 を実行する形に置き換えると、接続できるだけでなく問い合わせに答えられることまで確かめられます。
pip install fastapi uvicorn
DEP_PORT=5432 uvicorn health_app:app --port 8000
起動直後(準備が終わる前):
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/livez
{"status":"pass"}
200
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/startupz
{"status":"fail","reason":"starting"}
503
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/readyz
{"status":"fail","reason":"starting"}
503
準備が終わったあと:
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/startupz
{"status":"pass"}
200
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/readyz
{"status":"pass","checks":{"database":{"status":"pass","time_ms":0}}}
200
依存先を止めたとき(liveness は 200 のまま、readiness だけ 503):
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/livez
{"status":"pass"}
200
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:8000/readyz
{"status":"fail","checks":{"database":{"status":"fail","time_ms":0}}}
503
依存先を起動し直すと、アプリには何もしなくても /readyz は 200 に戻りました。
応答が返らない依存先でタイムアウトを確かめる
接続を拒否されるのではなく、応答そのものが返らない宛先(DEP_HOST=10.255.255.1)を指定して起動した結果です。末尾の数値は curl が測った所要時間(秒)です。
$ curl -s -w '\n%{http_code} %{time_total}\n' http://127.0.0.1:8000/readyz
{"status":"fail","checks":{"database":{"status":"fail","time_ms":1001}}}
503 1.022444
$ curl -s -w '\n%{http_code} %{time_total}\n' http://127.0.0.1:8000/livez
{"status":"pass"}
200 0.002112
CHECK_TIMEOUT の1秒で打ち切られ、503 が約1.02秒で返っています。確認する側のタイムアウトは、エンドポイント内部の上限より長く します。この例なら、内部が1秒なので確認側は2〜3秒にします。Kubernetes の timeoutSeconds の既定値は1秒なので、既定値のままだと 503 が届く前に確認側が先に打ち切ります。
Expressの最小実装とcurlの実行結果
同じ3つのパスを Express 5 で書いた例です。
// health.js (Express 5.2 / Node.js 22)
const express = require("express");
const net = require("node:net");
const DEP_HOST = process.env.DEP_HOST ?? "127.0.0.1";
const DEP_PORT = Number(process.env.DEP_PORT ?? 5432);
const CHECK_TIMEOUT_MS = 1000;
let started = false;
setTimeout(() => { started = true; }, 3000); // 起動時の準備を模したもの
function checkTcp(host, port) {
return new Promise((resolve) => {
const t0 = Date.now();
const done = (status) => {
socket.destroy();
resolve({ status, time_ms: Date.now() - t0 });
};
const socket = net.connect({ host, port });
socket.setTimeout(CHECK_TIMEOUT_MS);
socket.once("connect", () => done("pass"));
socket.once("timeout", () => done("fail"));
socket.once("error", () => done("fail"));
});
}
const app = express();
app.get("/livez", (req, res) => {
res.json({ status: "pass" }); // 依存先は見ない
});
app.get("/startupz", (req, res) => {
if (!started) return res.status(503).json({ status: "fail", reason: "starting" });
res.json({ status: "pass" });
});
app.get("/readyz", async (req, res) => {
res.set("Cache-Control", "no-store");
if (!started) return res.status(503).json({ status: "fail", reason: "starting" });
const checks = { database: await checkTcp(DEP_HOST, DEP_PORT) };
const ok = Object.values(checks).every((c) => c.status === "pass");
res.status(ok ? 200 : 503).json({ status: ok ? "pass" : "fail", checks });
});
app.listen(Number(process.env.PORT ?? 3000), "127.0.0.1");
npm install express
PORT=3000 DEP_PORT=5432 node health.js
起動直後、準備完了後、依存先を止めたあとの順に実行した結果です。
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:3000/readyz
{"status":"fail","reason":"starting"}
503
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:3000/readyz
{"status":"pass","checks":{"database":{"status":"pass","time_ms":1}}}
200
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:3000/livez
{"status":"pass"}
200
$ curl -s -w '\n%{http_code}\n' http://127.0.0.1:3000/readyz
{"status":"fail","checks":{"database":{"status":"fail","time_ms":4}}}
503
ヘルスチェックのパスは、認証やアクセスログ、レート制限のミドルウェアより前に登録しておくと、確認のたびにログが増えたり 401 が返ったりすることを避けられます。
Spring Boot Actuatorの設定
Spring Boot では、Actuator が liveness と readiness を用意しています。以下は Spring Boot 4.1.1 の公式リファレンス(Actuator の Endpoints の章)に記載された内容です。
| 項目 | 内容 |
|---|---|
| liveness のパス | /actuator/health/liveness |
| readiness のパス | /actuator/health/readiness |
| 有効・無効の切り替え | management.endpoint.health.probes.enabled |
| メインのポートにも公開 | management.endpoint.health.probes.add-additional-paths=true で /livez と /readyz |
| ステータスコード | UP と UNKNOWN は 200、DOWN と OUT_OF_SERVICE は 503 |
| readiness に確認を追加 | ヘルスグループの include に指標名を足す |
readiness にデータベースの確認を足す設定は、次のように書きます。
management:
endpoint:
health:
probes:
enabled: true
add-additional-paths: true
group:
readiness:
include: "readinessState,db"
独自の確認は HealthIndicator を実装して追加します。リファレンスに載っている形は次のとおりです。
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class MyHealthIndicator implements HealthIndicator {
@Override
public Health health() {
int errorCode = check();
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
private int check() {
// 確認したい処理を書き、正常なら 0 を返す
return 0;
}
}
management.endpoint.health.status.http-mapping で独自の対応を1つでも設定すると、DOWN と OUT_OF_SERVICE の既定の対応(503)は無効になるとリファレンスに書かれています。独自の状態を足すときは、down: 503 と out-of-service: 503 も一緒に書きます。
Kubernetesのprobe設定と既定値
アプリ側のエンドポイントに対応する、Kubernetes 側の設定例です。
startupProbe:
httpGet: { path: /startupz, port: 8000 }
periodSeconds: 10
failureThreshold: 30 # 10秒 × 30回 = 300秒まで起動を待つ
livenessProbe:
httpGet: { path: /livez, port: 8000 }
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
readinessProbe:
httpGet: { path: /readyz, port: 8000 }
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
各項目の既定値は、Kubernetes API リファレンス(v1.34、Probe)の記載では次のとおりです。
| 項目 | 意味 | 既定値 |
|---|---|---|
initialDelaySeconds |
コンテナ起動から最初の確認までの待ち時間 | 0秒 |
periodSeconds |
確認の間隔 | 10秒(最小1) |
timeoutSeconds |
1回の確認の時間の上限 | 1秒(最小1) |
failureThreshold |
異常とみなす連続回数 | 3(最小1) |
successThreshold |
正常に戻ったとみなす連続回数 | 1(liveness と startup は 1 のみ) |
間隔×しきい値から切り離しまでの秒数を計算する
異常が起きてから振り分けを外れるまでの時間は、次の範囲に入ります。
- 最短:間隔 ×(しきい値 − 1)……異常の直後に確認が走り、すぐエラーが返る場合
- 最長:間隔 × しきい値 + タイムアウト……確認の直後に異常が起き、毎回タイムアウトまで待つ場合
# probe_calc.py 間隔としきい値から、切り離しまでの秒数を求める
def window(period, failure_threshold, timeout):
shortest = period * (failure_threshold - 1) # 異常の直後に確認が走り、すぐ失敗が返る場合
longest = period * failure_threshold + timeout # 確認の直後に異常が起き、毎回タイムアウトまで待つ場合
return shortest, longest
def startup_budget(period, failure_threshold):
return period * failure_threshold # 起動に使える時間の上限
rows = [
("readiness(既定値)", 10, 3, 1),
("readiness(短め)", 5, 2, 2),
("liveness(慎重)", 10, 6, 2),
("LBの確認の例", 30, 2, 5),
]
print(f"{'設定':<20}{'間隔':>4}{'しきい値':>6}{'タイムアウト':>8}{'最短':>6}{'最長':>6}")
for name, p, f, t in rows:
s, l = window(p, f, t)
print(f"{name:<20}{p:>5}{f:>8}{t:>10}{s:>8}{l:>7}")
print("startup: 10秒 × 30回 =", startup_budget(10, 30), "秒まで起動を待てる")
$ python3 probe_calc.py
設定 間隔 しきい値 タイムアウト 最短 最長
readiness(既定値) 10 3 1 20 31
readiness(短め) 5 2 2 5 12
liveness(慎重) 10 6 2 50 62
LBの確認の例 30 2 5 30 65
startup: 10秒 × 30回 = 300 秒まで起動を待てる
式が合っているかを、異常が起きる時刻を0.1秒ずつずらして数える方法でも確かめました(間隔10秒、しきい値3、タイムアウト1秒)。
# probe_sim.py 異常が起きる時刻を0.1秒きざみで動かし、切り離しまでの秒数を数える
def detect(fault_at, period, threshold, timeout, hang):
t, fails = 0.0, 0
while True:
if t >= fault_at: # この回の確認は異常に当たる
fails += 1
done = t + (timeout if hang else 0) # 応答が返らない異常はタイムアウトまで待つ
if fails == threshold:
return done - fault_at
t += period
for hang in (False, True):
results = [detect(f / 10, 10, 3, 1, hang) for f in range(1, 101)]
kind = "応答なし(毎回タイムアウト)" if hang else "すぐエラーが返る"
print(f"{kind}: 最短 {min(results):.1f} 秒 / 最長 {max(results):.1f} 秒")
$ python3 probe_sim.py
すぐエラーが返る: 最短 20.0 秒 / 最長 29.9 秒
応答なし(毎回タイムアウト): 最短 21.0 秒 / 最長 30.9 秒
どちらも式の範囲(20〜31秒)に収まりました。既定値のままだと、異常なインスタンスへ最長で約30秒リクエストが届く ことになります。短くしたいときは間隔としきい値を小さくしますが、小さくしすぎると一時的な遅れでも外れやすくなるため、しきい値は2以上を保つと安定します。
確かめて分かったこと
- 起動直後は liveness が 200、readiness と startup が 503 になる。3つを同じパスにまとめると、この区別を返せない。
- 依存先を止めても liveness は 200 のまま で、依存先を戻すと readiness は再起動なしで 200 に戻った。
- 応答が返らない依存先では、内部のタイムアウト(1秒)で打ち切られて 503 が約1.02秒で返った。Kubernetes の
timeoutSecondsの既定値も1秒 なので、確認側のタイムアウトを明示的に長くしておく必要がある。 - 接続を拒否される異常は 0〜4ミリ秒で
failが返り、応答が無い異常は上限まで待つ。同じ「落ちている」でも判定にかかる時間が1秒ほど違う。 - 既定値(10秒・3回・1秒)で切り離しまで 20.0〜30.9秒。計算式の上限31秒と合う。
- Kubernetes の HTTP 確認は 200〜399 を成功とみなすため、リダイレクト(3xx)を返すパスも成功扱い になる。ログイン画面へ転送されるパスを指定しないよう、専用のパスを用意する。
つまずきやすい点と直し方
| 状況 | 起きること | 直し方 |
|---|---|---|
| liveness にデータベースの確認を入れている | データベースの不調で全台が再起動を繰り返す | liveness は固定の応答にし、確認は readiness へ移す |
| ヘルスチェックに認証がかかっている | 401 が返り、常に異常と判定される | 専用パスを認証の対象外にし、本文に内部情報を載せない |
| 依存先の確認にタイムアウトが無い | 確認の要求がたまり、本来のリクエストも遅くなる | 依存先ごとに短い上限を付ける |
| 起動に時間がかかるのに startup が無い | 起動の途中で liveness に引っかかり、再起動が続く | startup を足し、間隔 × しきい値を最長の起動時間より長くする |
| 確認のたびに重い問い合わせを実行 | 確認そのものが負荷になる | SELECT 1 程度にする。結果を数秒だけ保持する方法もある |
| 終了処理中も 200 を返す | 停止直前のインスタンスへリクエストが届く | 終了の合図を受けたら readiness を 503 に切り替えてから、処理中のリクエストを終える |
よくある質問
Q. livenessとreadinessの違いは何ですか? A. 異常時の動作が違います。liveness は再起動、readiness は振り分け先からの一時的な除外です。liveness はアプリ自身だけ、readiness は必須の依存先まで確認します。
Q. ヘルスチェックのパスは /health の1つだけでもよいですか? A. 再起動と切り離しを区別したいなら分けます。1つにまとめると、依存先の不調でも再起動されるか、内部の異常でも再起動されないかのどちらかになります。単一サーバーで再起動の仕組みが無い構成なら、1つでも用は足ります。
Q. ヘルスチェックと外形監視の違いは何ですか? A. ヘルスチェックは基盤が自動で再起動や切り離しをするための合図で、外形監視は利用者と同じ経路から使えるかを確かめて人に知らせるものです。経路上の DNS や証明書の問題は外形監視でしか見つからないため、両方を置きます。
Q. readinessが503のままになるときはどこを見ればよいですか?
A. 本文の checks で fail になっている依存先を確認します。次に、確認側のタイムアウトが内部の上限より短くないか、起動完了のフラグが立っているかを順に見ます。
まとめ
- liveness は依存先を見ない、readiness は必須の依存先だけ見る。startup は起動完了だけを見る。
- 依存先を入れるかは 「落ちたら再起動で直るか」 で決める。
- 設計書には パス・確認対象・タイムアウト・間隔・しきい値・返すステータス を1行ずつ書く。
- 応答は 200 か 503 で判定できるようにし、本文は原因を読むための補助にする。
- 依存先の確認には 短いタイムアウト を付け、確認側のタイムアウトはそれより長くする。
- 切り離しまでの時間は 間隔 × しきい値 + タイムアウト が上限。既定値なら最長31秒。
参考・出典
- Kubernetes ドキュメント「Configure Liveness, Readiness and Startup Probes」「Pod Lifecycle」(kubernetes.io、2026-10-06 確認)
- Kubernetes API リファレンス v1.34「Probe v1 core」(kubernetes.io)
- Spring Boot 4.1.1 リファレンス「Actuator ― Endpoints」(docs.spring.io)
- IETF Internet-Draft「Health Check Response Format for HTTP APIs」draft-inadarei-api-health-check-06(datatracker.ietf.org)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ヘルスチェック設計|livenessとreadinessの違いと実装例」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。