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

Envoyの内部構造

チェーンは順序ではなく具体性で勝つ

TT Labで続きを見る

一言でいうと

リスナーは「アドレスとポートの組」であり、その中のフィルターチェーンが実際に接続を受けます。どのチェーンが受けるかは接続の性質(SNI・トランスポートプロトコル・ALPN・送信元)で決まり、その判定は8段階の固定された順序で絞り込まれます。設定に書いた順序は関係ありません。

なぜ必要なのか

イングレスゲートウェイ1台が数十のドメインを受けます。ドメインごとに証明書が違い、HTTP/2で受けなければならないドメインもあり、内部のヘルスチェックは平文で入ってきます。これをポートごとにプロセスを別々に立てて解決する方法もありますが、そうするとアドレスをその分だけ割り当てる必要があり、設定もその分だけ散らばります。

Envoyは別の道を選びました。ポートは1つのままにして、そのポートに入ってきた接続の性質を見て別のチェーンへ送ります。 そのため、4431つがドメインごとに別の証明書を提示でき、同じポートが平文のリクエストも受けられます。

この構造を知らないと、2つの症状の前で行き詰まります。1つは「証明書は確かに入れたのに、このドメインだけ接続がただ切れる」、もう1つは「チェーンを上に移したのに、まだ下のものが受ける」です。どちらも原因は同じで、チェーンの選択規則を順序と誤解したことです。

どう動くのか

接続が入ってくると、まずリスナーフィルターが動きます。これらのフィルターはまだバイトを読むだけで、何も変更しません。その中でもtls_inspectorの働きが核心です。TLSハンドシェイクの最初のメッセージ(ClientHello)は平文なので、復号しなくても読むことができ、そこにSNI(接続しようとしている名前)とALPN(使いたいプロトコルの一覧)が入っています。インスペクターはその2つを取り出して接続に付けておき、ついでに「この接続はTLSかどうか」も判定して、transport_protocolをtlsまたはraw_bufferに設定します。

このフィルターを有効にしないと、server_namesとapplication_protocolsの条件はどの接続にも一致しません。設定は通るのに動作しないだけなので、最も見つけにくい種類のミスです。

その次にマッチングが動きます。公式ドキュメントに書かれている順序は次のとおりです。

1. 목적지 포트        2. 목적지 IP
3. 서버 이름(SNI)     4. 전송 프로토콜
5. 애플리케이션 프로토콜(ALPN)
6. 직접 연결된 출발지 IP   7. 출발지 유형
8. 출발지 IP          9. 출발지 포트

各段階で最も具体的に一致するチェーンだけが次の段階へ進みます。条件を書いていないチェーンは「何でも受ける」という意味なので生き残りますが、条件を書いたのに合わないチェーンは、その場で脱落します。すべての段階を通過すると、残るチェーンは最大1つであることが保証されます。

ここから出る結論の1つが、実務でよく足を引っ張ります。SNIの条件はALPNの条件より先です。そのため、server_names: ["a.example.com"]だけを書いたチェーンと、application_protocols: ["h2"]だけを書いたチェーンがあるとき、a.example.comにHTTP/2のリクエストが来ると、SNI側が受けます。「h2はあちらのチェーンへ送るつもりだったのに」という言葉が出てくる場面が、まさにここです。

ワイルドカードも具体性で比較します。SNIがwww.example.comのとき、優先順位はwww.example.com → *.example.com → *.com → 条件なしの順です。

現場での姿

「このドメインだけ接続が切れます」という報告では、応答コードがないことが手がかりです。404や503はHTTPレイヤーまで届いたという意味ですが、一致するチェーンがないとその前で接続が閉じられるため、クライアントにはTLSハンドシェイクの失敗としてしか見えません。新しいドメインの証明書を入れるときに、server_namesに名前を追加し忘れると、まさにこの形になります。

「チェーンの順序を変えても結果が同じです」というのは当然です。順序はマッチングに使われません。変えるべきなのは条件の具体性です。

平文とTLSを1つのポートで受ける構成。Kubernetesの中では、ヘルスチェックは平文で、外から来るトラフィックはTLSで受けたいときに、transport_protocol: raw_bufferのチェーンを1つ追加して解決します。ポートをもう1つ開き、ファイアウォールルールをもう1つ作るよりも、はるかに簡単です。

公式ドキュメント: Listeners・FilterChainMatch・TLS Inspector

次のラボですること

証明書を2枚自分で作ってSNIでチェーンを振り分け、同じポートで平文とTLSを一緒に受け、SNIとALPNが同時に一致したときにどちらが勝つかを自分で確認します。最後にどのチェーンにも一致しないリクエストを送り、「応答ではなく沈黙」を目で確かめます。