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

Envoyの内部構造

バーチャルホスト四つとマッチ五種を分ける

TT Labで続きを見る

目標

Hostヘッダーでバーチャルホストが先に決まることを確認し、マッチの種類と宛先の種類を1つずつ加えながら、同じリクエストがどこへ行くかを見ます。

なぜ重要なのか

ルーティング設定は、人間が読むと「ルールの一覧」に見えますが、実際には2段階の選択です。しかも2つの段階は、別々の規則で動きます。前は具体性、後ろは順序です。この違いを知らないと、ルールを上下に動かして時間を無駄にします。これに加えて、weighted_clustersが保証ではなく確率であること、direct_responseでアプリに触れずに済ませられること、ヘッダーが3つの層でそれぞれ追加されることまで手で確認しておけば、他人が書いたルート表も数分で読めます。

ステップ

  1. /root/envd-route/route.yamlにバーチャルホストを4つ置いてください。exact(shop.envd.test)、suffix(*.envd.test)、prefixw(shop.*)、anyhost(*)です。それぞれ/でvh=exact・vh=suffix・vh=prefixw・vh=anyを返します(管理9931、リスナー127.0.0.1:10031)。起動したら4種類のHostヘッダーで/zzzにリクエストし、/root/envd-route/01-vhosts.txtにexact=・suffix=・prefixw=・any=の4行で結果を書いてください(リクエストするHostは、順にshop.envd.test、www.envd.test、shop.other.test、nowhere.exampleです)。
  2. /root/envd-route/route.yamlを/root/envd-route/route-dup.yamlにコピーしてから、domains: ["*"]のバーチャルホストをもう1つ作ってください(名前は重ならないように)。envoy --mode validateで検査し、出力と終了コードを/root/envd-route/02-onestar.txtに保存してください(最後の行はrc=1)。
  3. exactバーチャルホストの/ルートの上に、2つのルートを追加してください。path: "/exact"はm=pathを、safe_regexで^/id/[0-9]+$はm=regexを返します。/root/envd-route/03-match.txtに、path=(/exactのリクエスト)、regex=(/id/42のリクエスト)、regex_miss=(/id/abcのリクエスト)の3行を書いてください。Hostはすべてshop.envd.testです。
  4. /apiへ向かうルートをさらに2つ追加してください(正規表現ルートの下、/ルートの上)。1つはheadersの条件でx-canary: yesのときにm=headerを、もう1つはquery_parametersの条件でdebug=1のときにm=queryを返します。/root/envd-route/04-cond.txtに、header=、query=、plain=(条件なしで/apiだけ)の3行を書いてください。
  5. アップストリームを2つ起動し(8082、8083)、クラスターblue・greenを作ってから、/splitルートがweighted_clustersで75対25に分けて送るようにしてください。--concurrency 1を付けて起動し、40回リクエストして、/root/envd-route/05-weighted.txtにblue=・green=・total=の3行(各アップストリームが受けた回数と合計)を書いてください。
  6. 2つのルートを追加してください。path: "/healthz"はdirect_responseで200とaliveを返し、prefix: "/old"はredirectで/newへ301を返します。/root/envd-route/06-direct.txtに、health=(応答本文)、redirect_code=(HTTPコード)、redirect_url=(Locationヘッダーの値)の3行を書いてください。
  7. レスポンスヘッダーx-levelを3つの層にそれぞれ追加してください。ルートにroute、バーチャルホストにvirtualhost、ルート表にrouteconfigです。shop.envd.testの/zzzにリクエストして返ってきたx-levelヘッダーをすべて、/root/envd-route/07-headers.txtのlevels=の1行に、カンマなしで空白でつなげて書き、count=の行に個数を書いてください。
  8. /root/envd-route/08-report.mdに、vhost_order=(バーチャルホストの具体性の順序をexact,suffix,prefix,starの形式で)、star_limit=(1つのルート表に置ける*のバーチャルホストの数)、blue_share=(ステップ5でblueが受けた割合、パーセントの整数)、redirect_code=(ステップ6のコード)の4行を書き、その下に学んだことを4行以上書いてください。

参考

ルート表より先にバーチャルホストが決まる

/root/envd-route/route.yamlにバーチャルホストを4つ置いてください。exact(shop.envd.test)、suffix(*.envd.test)、prefixw(shop.*)、anyhost(*)です。それぞれ/でvh=exact・vh=suffix・vh=prefixw・vh=anyを返します(管理9931、リスナー127.0.0.1:10031)。起動したら4種類のHostヘッダーで/zzzにリクエストし、/root/envd-route/01-vhosts.txtにexact=・suffix=・prefixw=・any=の4行で結果を書いてください(リクエストするHostは、順にshop.envd.test、www.envd.test、shop.other.test、nowhere.exampleです)。

ルート表を見る前に、バーチャルホストが先に決まります(Hostまたは:authorityヘッダーによる)。そのため、「ルートを確かに書いたのに掛からない」の半分は、バーチャルホストの選び間違いです。ワイルドカードには、接尾辞(*.foo.com)と接頭辞(foo.*)の2種類があり、どちらも空文字列には一致しません。リクエストはcurl -H "Host: 이름" http://127.0.0.1:포트/zzz(プレースホルダーは名前とポートです)で送ります。

*は、ルート表全体に1つしか置けない

/root/envd-route/route.yamlを/root/envd-route/route-dup.yamlにコピーしてから、domains: ["*"]のバーチャルホストをもう1つ作ってください(名前は重ならないように)。envoy --mode validateで検査し、出力と終了コードを/root/envd-route/02-onestar.txtに保存してください(最後の行はrc=1)。

バーチャルホストの選択は「最も具体的なものが勝つ」で動きますが、*が2つあると、どちらがより具体的かを決める方法がありません。そのためEnvoyは、この状態を実行中にあいまいなまま放置せず、設定を読み込む時点で拒否します。拒否メッセージにルート表の名前も一緒に出るので、その行をそのまま書き写しておいてください。

正確なパスと正規表現で一致させる

exactバーチャルホストの/ルートの上に、2つのルートを追加してください。path: "/exact"はm=pathを、safe_regexで^/id/[0-9]+$はm=regexを返します。/root/envd-route/03-match.txtに、path=(/exactのリクエスト)、regex=(/id/42のリクエスト)、regex_miss=(/id/abcのリクエスト)の3行を書いてください。Hostはすべてshop.envd.testです。

マッチの種類は3つです。prefix(先頭が同じなら)、path(全体が一致する必要がある)、safe_regex(正規表現)。前の2つは速いので、正規表現は本当に必要なときだけ使います。正規表現に一致しなければ、そのルートを飛ばして下へ進み続けます。404ではなく、次のルートが受けるという点が重要です。safe_regexは{ regex: "..." }の形で書きます。

同じパスをヘッダーとクエリ文字列で振り分ける

/apiへ向かうルートをさらに2つ追加してください(正規表現ルートの下、/ルートの上)。1つはheadersの条件でx-canary: yesのときにm=headerを、もう1つはquery_parametersの条件でdebug=1のときにm=queryを返します。/root/envd-route/04-cond.txtに、header=、query=、plain=(条件なしで/apiだけ)の3行を書いてください。

パスだけでは分けられない要件が、実務には多くあります。内部のテスターだけ新バージョンへ、デバッグ用のクエリが付いたリクエストだけ別のバックエンドへ、といったものです。そのため、マッチにはパス以外にもheadersとquery_parametersの条件を一緒に付けることができ、1つのマッチの中の条件はすべて満たす必要があります。2つのルートが同じprefixを使う場合は、上にあるほうが先に判定されるので、条件の付いた側を上に置きます。

1つのルートが2つのクラスターに振り分けて送る

アップストリームを2つ起動し(8082、8083)、クラスターblue・greenを作ってから、/splitルートがweighted_clustersで75対25に分けて送るようにしてください。--concurrency 1を付けて起動し、40回リクエストして、/root/envd-route/05-weighted.txtにblue=・green=・total=の3行(各アップストリームが受けた回数と合計)を書いてください。

重みは比率であって、保証ではありません。さらに、ワーカースレッドごとに状態が別々に動くため、デフォルトのconcurrency(コア数)で数えると、数字が毎回変わります。そのため、このステップはワーカー1つで起動する必要があります。アップストリームはpython3 /opt/lab/envoy/upstream.py <포트> ok(プレースホルダーはポート番号です)で起動し、応答本文にポートが入っているので、sort | uniq -cで数えれば済みます。

アップストリームなしで応答し、古いパスを移転させる

2つのルートを追加してください。path: "/healthz"はdirect_responseで200とaliveを返し、prefix: "/old"はredirectで/newへ301を返します。/root/envd-route/06-direct.txtに、health=(応答本文)、redirect_code=(HTTPコード)、redirect_url=(Locationヘッダーの値)の3行を書いてください。

direct_responseは、アップストリームへ行かずにEnvoyが直接応答します。ヘルスチェックのパスやメンテナンス案内のページを、アプリに入れずにプロキシで完結させられます。redirectはデフォルトが302なので、恒久的な移転ならresponse_code: MOVED_PERMANENTLYを書く必要があります。curl -o /dev/null -w '%{http_code} %{redirect_url}'で、2つの値を一度に受け取れます。

ヘッダーを付ける場所が3か所ある

レスポンスヘッダーx-levelを3つの層にそれぞれ追加してください。ルートにroute、バーチャルホストにvirtualhost、ルート表にrouteconfigです。shop.envd.testの/zzzにリクエストして返ってきたx-levelヘッダーをすべて、/root/envd-route/07-headers.txtのlevels=の1行に、カンマなしで空白でつなげて書き、count=の行に個数を書いてください。

ヘッダー操作は、ルート・バーチャルホスト・ルート表の3か所で使えます。上書きではなく、それぞれが追加されます。そのため、同じ名前のヘッダーが複数残ることがあり、クライアントはそれをカンマでつないだ1つとして見ることもあります。適用は、内側(ルート)から外側(ルート表)の順です。curl -sIでヘッダーだけを受け取り、grep -i x-levelしてください。

ルーティング表を読む規則としてまとめる

/root/envd-route/08-report.mdに、vhost_order=(バーチャルホストの具体性の順序をexact,suffix,prefix,starの形式で)、star_limit=(1つのルート表に置ける*のバーチャルホストの数)、blue_share=(ステップ5でblueが受けた割合、パーセントの整数)、redirect_code=(ステップ6のコード)の4行を書き、その下に学んだことを4行以上書いてください。

割合は自分で計算してください。40回のうち何回だったかから出ます。説明の行には、「重みは比率であって保証ではない」のように、次に自分を助けてくれる文を書いてください。