【実務・中級編】 APIのヘルスチェックエンドポイント設計 – Web APIアーキテクチャ・データ連携実践ガイド

「死んでるのに生きている」を防ぐ――L7ロードバランサー時代のヘルスチェック完全攻略

ネットワークエンジニアとして数多のサービスダウンを鎮火してきた経験から言わせてもらうと、「ヘルスチェックを甘く見ているエンジニアは、いつか必ず痛い目を見る」。

ロードバランサー(LB)が送る単なる GET / の200 OK。これだけで「よし、システムは健全だ」と安心していないだろうか? データベースが悲鳴を上げ、バックエンドのマイクロサービスがタイムアウトを連発していても、アプリケーションサーバーのルートパスが200を返し続ければ、LBはそのノードにトラフィックを流し続ける。結果、ユーザーには真っ白な画面かタイムアウトエラーが表示されるわけだ。

今日は、そんな「形だけのヘルスチェック」を卒業し、L7レベルで真のサービス可用性を担保するための設計論を叩き込む。

—

ヘルスチェックは「単なるPing」ではない

HTTPヘルスチェックのエンドポイント設計において、最も重要な原則は「依存関係の階層を意識すること」だ。単にプロセスが生きているかを見る Liveness と、依存サービスを含めてサービス提供が可能かを見る Readiness を明確に分ける必要がある。

1. Liveness Probe(生存確認)

プロセス自体が死んでいないかをチェックする。基本的には超軽量で、依存関係のチェックは一切行わない。

  • URL: /health/live
  • 役割: コンテナの再起動や、プロセス再起動のトリガー。
  • 実装: 単に 200 OK を返すだけ。

2. Readiness Probe(準備完了確認)

そのノードがトラフィックを捌ける状態かをチェックする。ここで初めて、データベースへの接続確認や、外部APIの疎通確認を行う。

  • URL: /health/ready
  • 役割: LBからルーティング対象外にするかどうかの判断。
  • 実装: DBの SELECT 1 や、依存するキャッシュサーバーへの疎通を確認し、失敗すれば 503 Service Unavailable を返す。

—

現場で刺さる「ヘルスチェック」設計の勘所

設計時に陥りやすいワナが、「ヘルスチェックに重い処理を詰め込みすぎる」ことだ。ヘルスチェックの頻度が秒単位であれば、毎秒DBにクエリが飛ぶことになる。これは立派なDDoS攻撃だ。

推奨される実装例(Python / FastAPI)

from fastapi import FastAPI, Response, status
import databases

app = FastAPI()
db = databases.Database("postgresql://user:pass@localhost/dbname")

@app.get("/health/live")
async def live():
    # プロセスが生きていればOK
    return {"status": "ok"}

@app.get("/health/ready")
async def ready(response: Response):
    try:
        # DBへの接続確認(軽量なクエリを実行)
        await db.execute("SELECT 1")
        return {"status": "ready"}
    except Exception:
        # DBが死んでいれば503を返し、LBから切り離す
        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
        return {"status": "db_connection_failed"}

このように、エンドポイントを分けることで、LBは「サービス自体は動いているが、今はDB待ちで処理できない」というシグナルを正しく受け取ることができるようになる。

—

インフラ運用者が直面する「深淵」と設定のコツ

LB(AWS ALBやNginx、HAProxyなど)側でヘルスチェックを設定する際、以下のパラメータを「なぜその値にするか」説明できるだろうか?

  • Interval: チェックの間隔(例: 5s)。短すぎれば負荷に、長すぎれば切り離しが遅れる。
  • Timeout: 応答待ち時間(例: 2s)。これが Interval より短いことは絶対条件だ。
  • HealthyThreshold / UnhealthyThreshold: 連続成功/失敗回数。ネットワークの瞬断でサービスを落とさないためのバッファ。

Nginxの設定例(アップストリーム監視)

upstream backend_servers {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    
    # 5回失敗したらダウン、その後10秒待機
    # 依存関係チェックのエンドポイントを指定する
    keepalive 32;
}

server {
    location / {
        proxy_pass http://backend_servers;
    }
    
    # ヘルスチェック用エンドポイントの保護(任意だが推奨)
    location /health/ready {
        proxy_pass http://backend_servers/health/ready;
        # タイムアウトを厳しめに設定
        proxy_connect_timeout 1s;
        proxy_read_timeout 1s;
    }
}

—

最後に:トラブルシューティングの作法

「LBからノードが外れた」というアラートが飛んだ時、真っ先にやるべきことは何だろうか? ログファイルを探す前に、まずは curl で直接叩くことだ。

# ヘルスチェックパスを直接叩いて、レスポンスコードと時間を計測する
curl -Iv -o /dev/null -w "%{http_code} %{time_total}\n" http://<ノードのIP>:8080/health/ready

ここで 503 が返ってきていれば、アプリ側の依存関係の問題だ。Connection Refused ならプロセスが落ちている。Timeout ならネットワークか、高負荷でキューが溢れている。

ヘルスチェックは、システムの「心拍計」だ。この心拍計が正確でなければ、どんなに高度なオーケストレーションも無意味になる。RFC 7231に謳われるHTTPのステータスコードの意味を今一度噛み締め、単なる 200 OK の呪縛から抜け出してほしい。

いいか、「正常ではない状態を、正常ではないと正しく伝える」。これこそが、信頼性の高いシステムを構築するエンジニアの矜持だ。

コメント

タイトルとURLをコピーしました