TT Lab
はじめる
学ぶ 学習パス コース

クラウドネットワーク設計

L4とL7、そしてヘルスチェックがやっていること

TT Labで続きを見る

一言でいうと

ロードバランサーは、トラフィックを振り分ける装置ではなく、故障した対象を外す装置です。振り分けは副次的な効果で、値打ちはヘルスチェックから出てきます。

なぜ必要なのか

サーバーを2台に増やすと、すぐに2つの疑問が生まれます。ユーザーをどちらへ送るのか、そして、1台が死んだことを何が察知するのか。

DNSにアドレスを2つ書いておく方法が最も安く見えますが、2つ目の疑問で崩れます。DNSは対象が生きているかを知らないので、死んだサーバーのアドレスも配り続け、レコードを直しても、TTLが残っているあいだは反映されません。

そのため、トラフィックの前に、対象の状態を継続的に確認する装置を置きます。負荷を分けるのは、その装置がついでにやっていることで、値打ちを発揮する部分は、故障した対象をリストから外すほうです。

L4とL7

L4(ネットワーク) L7(アプリケーション)
見るもの IP、ポート HTTPヘッダー、パス、ホスト
できること 転送 パスベースのルーティング、ホスト分岐、リダイレクト
TLS 通過させるか終端する たいてい終端する
レイテンシ 非常に低い わずかに大きい
用途 TCP・gRPC・ゲーム・DB Web API、マイクロサービス

/api/*はAサービスへ、/img/*はBサービスへ送るのは、L7にしかできません。逆に、極端なスループットが必要な場合や、HTTPではない場合は、L4です。

ヘルスチェックが本当の仕事

ロードバランサーは、定期的に対象へリクエストを送り、失敗がしきい値を超えたら、その対象をリストから外します。この動作のおかげで、インスタンス1つが死んでも、ユーザーは気づきません。

設定で重要なこと。

前のコースで扱ったlivenessとreadinessの区別が、ここでもまったく同じように適用されます。ロードバランサーのヘルスチェックは、readinessに近いものです。「今、トラフィックを受けてもよいか」を尋ねるものであり、「再起動すべきか」を尋ねるものではありません。

接続ドレイニング

インスタンスを外すときに、処理中のリクエストを切ると、ユーザーにエラーが行きます。ドレイニング(deregistration delay)は、新しいリクエストは送らず、処理中のものは終わらせるために待つ時間です。

デプロイ時の順序は次のとおりです。

1. 대상을 목록에서 빼기 시작 (새 요청 중단)
2. 드레이닝 대기 — 진행 중 요청 완료
3. 애플리케이션에 SIGTERM
4. 정상 종료

3つ目が、コンテナのコースで扱ったあのSIGTERMです。ドレイニング時間が、アプリケーションの正常終了時間より短いと、リクエストが切られます。

DNSとTTL

ロードバランサーの前段は、たいていDNSです。ここでTTLが障害対応の速度を決めます。

そのため、DNSの切り替えを障害対応の主な手段にしないのが原則です。ロードバランサーの中で対象を外すほうが、はるかに速く確実です。

スティッキーセッション(sticky session)

同じユーザーを同じサーバーへ送る機能です。便利に見えますが、代償があります。

セッションを外部ストレージ(Redisなど)に置けば、固定が不要になります。可能なら、そちらのほうがよいです。

ロードバランサーの裏で崩れる瞬間

ロードバランサーは、普段は静かで、負荷がかかるときとデプロイするときに問題を表に出します。その2つの瞬間に何が起きるかを知っていれば、大半は避けられます。

ヘルスチェックがアプリケーションと同じリソースを使うと、一緒に崩れます。/healthがデータベースを照会するように作っておくと、DBが遅くなる瞬間に、すべてのインスタンスが同時にunhealthyに落ちます。1つではなくすべてが外れるので、サービスがまるごと止まります。生きているかを見る検査(liveness)と、トラフィックを受ける準備ができたかを見る検査(readiness)を分け、前者は依存先に触れないようにします。

外れる判定は緩く、戻る判定は厳しく。一時的な遅延でインスタンスが外れると、残ったインスタンスに負荷が集中して、それらも外れます。連鎖が始まる場所です。失敗のしきい値は余裕をもって(3–5回)、復旧のしきい値は短く設定するほうが安全です。

デプロイ時に502が出る理由は、たいてい順序です。ロードバランサーがそのインスタンスをリストから外す前に、アプリケーションが先に死ぬと、その間に入ってきたリクエストが行き場を失います。順序を逆にします。SIGTERMを受けたら、まずヘルスチェックを失敗に変え、ロードバランサーが気づく時間(チェック間隔 × しきい値)だけ待ったあと、そのとき処理中のリクエストを仕上げて終了します。Kubernetesでは、preStopフックとterminationGracePeriodSecondsが、この時間を作ります。

接続ドレイニングの時間は、最も長いリクエストより長くなければなりません。30秒にしたのに、60秒かかるレポート生成リクエストがあれば、そのリクエストはデプロイのたびに切れます。

タイムアウトは層ごとに違い、内側のほうが短くなければなりません。ロードバランサー60秒、アプリケーション55秒、データベース50秒のように、内側に行くほど短く設定します。逆になっていると、ロードバランサーが先に切り、アプリケーションは誰も待っていないレスポンスを作り続けながら、接続を握り続けます。

アイドルタイムアウトが、コネクションプールを壊します。ロードバランサーが静かな接続を60秒で切るのに、アプリケーションのプールがその接続を生きていると信じていると、次のリクエストでconnection resetが起きます。プールのアイドル時間を、ロードバランサーより短く設定することが、答えです。

現場での姿

次のコース

コスト。ここまでのすべての決定に値段が付いていて、その値段の読み方を扱います。