仕組みから学ぶ Web
応用読了目安 15#ロードバランサ#リバースプロキシ#X-Forwarded-For#ヘルスチェック#タイムアウト

ロードバランサ — 間に入った箱が、何を書き換えているか

ロードバランサを入れた翌日、レート制限が全利用者をまとめて弾いた。ログの送信元も全部同じ。間に入る箱は接続を終端し、元の情報はヘッダーに移る。

この記事の進み方

What — 間に入る箱は、接続を終端する

ロードバランサやリバースプロキシは、通信を素通しにしません。利用者との接続をそこで受け止め(終端し)、別の接続を自分で作ってバックエンドへ送ります。

Figure接続は2本に分かれている
利用者203.0.113.45ロードバランサ10.0.2.31アプリ10.0.3.12TCP 接続 + TLSGET /api/login別の TCP 接続で GET /api/loginX-Forwarded-For: 203.0.113.45200 応答200 応答
1/6
TCP 接続 + TLS利用者はロードバランサと接続する。証明書もここに置かれていることが多い(TLS の終端)。

利用者と話しているのはロードバランサ。アプリと話しているのもロードバランサ。アプリから見える相手は、最初から最後までロードバランサだけ。

終端
そこで接続を一度受け止めること。TLS を終端すると、その先は別の接続になる。
X-Forwarded-For
元の送信元 IP を伝えるヘッダー。間に入った箱が追記していく。標準化されたものに Forwarded がある。
ヘルスチェック
振り分け先が正常かを定期的に確かめる仕組み。失敗が続くと振り分けから外す。
アイドルタイムアウト
通信が無いまま接続を保持する上限時間。上流と下流でずれると切断の事故になる。

元の情報は、ヘッダーに移っている

利用者の IP、使われたプロトコル(http / https)、元のホスト名。どれも接続からは分からなくなり、ヘッダーに移ります

知りたいもの接続からヘッダー
利用者の IPロードバランサの IP になるX-Forwarded-For
http か https かバックエンドへは http のことが多いX-Forwarded-Proto
利用者が入力したホスト名書き換わることがあるX-Forwarded-Host / Host

X-Forwarded-Proto を読まないと、https で来たのに http だと思い込みhttp:// へリダイレクトして無限ループになる、という事故が起きます。

確認 — ここまで読めたか

ロードバランサの後ろにあるアプリで、IP アドレスごとのレート制限をかけています。ロードバランサを入れた直後から、全利用者がまとめて制限に引っかかるようになりました。なぜですか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜヘッダーをそのまま信じてはいけないか

X-Forwarded-For を読めば解決、とはいきません。このヘッダーは、利用者が自分で付けられます。

$ curl https://example.com/api/login -H "X-Forwarded-For: 1.2.3.4"

アプリが素直に先頭の値を使うと、攻撃者はリクエストごとに違う IP を名乗れます。レート制限は無意味になり、ログには偽の IP が残り、IP による遮断も効きません。

右から数える

X-Forwarded-For は、間に入った箱が右へ追記していく形です。

X-Forwarded-For: 1.2.3.4, 203.0.113.45, 70.132.1.9
                 ↑偽装     ↑本物の利用者   ↑CDN が見た送信元
                 (利用者が勝手に付けた)

信頼できるのは、自分が置いた箱が付けた値だけです。CDN とロードバランサの2段なら、右から2つ目が利用者の IP です。

つまり、自分の構成で間に何段あるかを数え、その数だけ右から取るのが正しい読み方です。段数を間違えると、偽装された値を掴みます。多くのフレームワークには「信頼するプロキシの数」を設定する仕組みがあり、その設定こそが判断そのものです。

ヘルスチェックは、浅すぎても深すぎても事故になる

ロードバランサは、振り分け先が生きているかを定期的に確かめます。この確認の深さが、障害の形を決めます。

Compareヘルスチェックの深さ

ポートが開いていれば正常とみなす

TCP connect 10.0.3.12:3000
見つけられる故障
プロセスの停止だけ
誤って外すこと
ほぼ無い
見逃す故障
**応答を返せない状態**
向いている場面
HTTP 以外のサービス
  • プロセスは生きているがイベントループが詰まっている(design/io-concurrency)状態を見逃す
  • デプロイの途中で起動はしたが準備が終わっていない、も通してしまう
  • 壊れた1台に流し続けると、利用者の一定割合だけがエラーになる

浅いと壊れたサーバーに流し続け、深いと依存先の障害で全台が同時に外れる。深さは「そのサーバーが仕事をできるか」に合わせる。

確認 — ここまで読めたか

アプリは X-Forwarded-For の先頭の値を、利用者の IP として使っています。どんな問題が起きますか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

利用者の IP を、正しく取れるようにするには?

与えられた条件
  • 構成は 利用者 → CDN → ロードバランサ → アプリ の2段
  • IP はレート制限、不正アクセスの調査、地域の統計に使う
  • CDN とロードバランサは自分たちが契約・設置したもの
  • アプリは複数のフレームワークで書かれた3つのサービスに分かれている
  • 過去に、偽装された IP がログに残っていたことがある
この軸で考える
  • · どこから先が自分の管理下か
  • · ヘッダーの何番目を読むべきか、それをどう決めるか
  • · 3つのサービスで、同じ判断を繰り返さない方法
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「朝からログインできない」という問い合わせを受けたサポート担当に、何が起きたかを説明してください

与えられた条件
  • 相手は IP アドレスという言葉は知っているが、ロードバランサは知らない
  • 復旧は10分で終わり、原因も分かっている
  • 同じ問い合わせが来たときに、何と案内すればよいかを知りたがっている

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。