VirtualService をルート表に変換し、順序と再試行を確かめる
目標
VirtualServiceがEnvoyのルート表に変換される規則に従って、手で表を立て、Hostヘッダー・最初のマッチ優先・重み・リトライを、リクエストで確認します。
なぜ重要なのか
VirtualServiceのミスは、たいてい、ルール1つではなく、ルールの間の順序と重なりから出ます。静的解析はそれを捕まえられないので、変換規則を知って、Envoyでどう動くかを描けないと、変更のレビューで弾けません。
ステップ
/root/ist2-route/dr.yaml(DestinationRulereviews、hostreviews.default.svc.cluster.local、サブセットv1・v2・broken。ラベルversionがそれぞれ同じ名前)と、/root/ist2-route/vs.yaml(VirtualServicereviews、hosts[reviews])を書いてください。vs.yamlのhttpは、この順序で4つです。jason(ヘッダーend-userがちょうどjasonならv2)、api(パスのプレフィックスが/apiならv1)、flaky(パスのプレフィックスが/flakyならbroken、retries: {attempts: 3, retryOn: 5xx}、timeout: 2s)、default(v1にweight75、v2に25)です。istioctl analyze --use-kube=false vs.yaml dr.yamlの出力と終了コードを、/root/ist2-route/01-analyze.txtに入れてください(最後の行はrc=0)。- サービス
reviewsは、ネームスペースdefault、ポート9080です。このVirtualServiceがサイドカーで作るルート表の名前を、/root/ist2-route/02-names.txtに3行で書いてください。route_config=(ルート表の名前)、virtual_host=(バーチャルホストの名前)、domains=(そのバーチャルホストのdomainsのうち、短い名前の4種類をカンマ区切りで、順序は問いません)です。 /root/ist2-route/route.yamlにEnvoyの設定を書いてください。管理ポートは9984、リスナーは127.0.0.1:10084、ステップ2のルート表の名前・バーチャルホストの名前・domains4つを使います。ルートは、名前を付けて、この順序で3つです。jason(パス/、かつヘッダーend-userがちょうどjason→outbound|9080|v2|reviews.default.svc.cluster.local)、api(プレフィックス/api→outbound|9080|v1|reviews.default.svc.cluster.local)、default(プレフィックス/→outbound|9080|v1|reviews.default.svc.cluster.local)です。クラスターは、v1(127.0.0.1:8105)とv2(127.0.0.1:8106)の2つです。アップストリーム2つをokとして起動し、Envoyを起動してから、3種類のHostヘッダーで/を呼び、/root/ist2-route/03-hosts.txtに、reviews=、reviews.default.svc.cluster.local=、ratings=の3行でHTTPコードを書いてください。route.yamlで起動しているEnvoyに、Hostreviewsで3つのリクエストを送り、応答本文のポートでサブセットを見分けて、/root/ist2-route/04-match.txtに書いてください。anonymous=(ヘッダーなしで/)、jason=(end-user: jasonで/)、api_as_jason=(end-user: jasonで/api/list)です。値はv1かv2です。vs.yamlからdefaultルールを一番前に移した/root/ist2-route/vs-bad.yamlを作り、istioctl analyze --use-kube=false vs-bad.yaml dr.yamlの終了コードを確認してください。同じミスをEnvoyで再現します。route.yamlからdefaultルートを一番前に移した/root/ist2-route/route-bad.yamlでEnvoyを起動し直し、end-user: jasonで/を呼んでみてください。/root/ist2-route/05-order.txtに、analyze_rc=(vs-badの解析の終了コード)、jason_after=(route-badでjasonが行ったサブセット)の2行を書いてください。確認した後は、route.yamlに戻して起動しておいてください。route.yamlを/root/ist2-route/route-split.yamlにコピーして、defaultルートだけをweighted_clustersに変えてください(v1が75、v2が25。VirtualServiceのdefaultルールそのまま)。--concurrency 1で起動し直してから、Hostreviewsで/をちょうど40回送り、/root/ist2-route/06-split.txtにv1=、v2=、total=の3行を書いてください。route-split.yamlを/root/ist2-route/route-retry.yamlにコピーして、2つを追加してください。クラスターoutbound|9080|broken|reviews.default.svc.cluster.local(127.0.0.1:8114、常に503を返すアップストリーム)と、defaultの前にflakyルート(プレフィックス/flaky→ そのクラスター、timeout: 2s、retry_policy: {retry_on: 5xx, num_retries: 3})です。アップストリームを8114でfailとして起動し、Envoyを起動し直してから、/flakyを1回だけ呼んでください。/root/ist2-route/07-retry.txtに、status=(HTTPコード)、upstream_rq_retry=、upstream_rq_total=(brokenクラスターの2つの統計の値)を書いてください。/root/ist2-route/08-report.mdに、route_config=、virtual_host=、first_match_wins=(ステップ5で見たとおり、最初に一致するルート1つだけが使われるならyes)、retry_attempts=(VirtualServiceのattemptsの値)の4行を書き、その下に、-で始まる説明を4行以上書いてください。
参考
- このPodには、本物のistiodも本物のサイドカーもありません。そのため、
istioctl proxy-configで実際の生成物を見られず、変換ルールを知って、手で同等のEnvoy設定を作って動作を確認します。同じルールが、本番クラスターのproxy-configの出力に、そのまま見えます。 - すべてのリクエストに
-H 'Host: reviews'を付けてください。バーチャルホストは、Hostヘッダーで選びます。 - 分配とリトライを数えるステップ6・7は、Envoyを
--concurrency 1で起動してください。統計は累積されるので、起動し直した直後に数えます。 - Envoyを起動するときは、
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null(プレースホルダーは設定ファイルとログファイルです)でシェルから完全に切り離してください。起動し直す前にはpkill -x envoyで片付けます(pkill -f 'envoy -c'は、その文字列を含むシェル自身まで終了させます)。 - アップストリームの代用サーバーがイメージにあります:
python3 /opt/lab/envoy/upstream.py <포트> ok|fail|slow(プレースホルダーはポート番号です)。応答本文は<모드>:<포트> <경로>(プレースホルダーはモード、ポート番号、パスです)です。 - 設定を直した後は、起動する前に
envoy --mode validate -c <파일>(プレースホルダーは設定ファイルです)で先に検査してください。クラスター名に|が入るので、YAMLでは必ず引用符で囲みます。
ルートが4つのVirtualServiceを書いて、解析を通す
/root/ist2-route/dr.yaml(DestinationRule reviews、host reviews.default.svc.cluster.local、サブセットv1・v2・broken。ラベルversionがそれぞれ同じ名前)と、/root/ist2-route/vs.yaml(VirtualService reviews、hosts [reviews])を書いてください。vs.yamlのhttpは、この順序で4つです。jason(ヘッダーend-userがちょうどjasonならv2)、api(パスのプレフィックスが/apiならv1)、flaky(パスのプレフィックスが/flakyならbroken、retries: {attempts: 3, retryOn: 5xx}、timeout: 2s)、default(v1にweight75、v2に25)です。istioctl analyze --use-kube=false vs.yaml dr.yamlの出力と終了コードを、/root/ist2-route/01-analyze.txtに入れてください(最後の行はrc=0)。
VirtualServiceは「どこへ送るか」、DestinationRuleは「送った後でどう扱うかと、サブセットの定義」です。analyzeは、2つのファイルを一緒に読んで、VSが指すサブセットがDRにあるかのような参照関係を見ます。httpの項目ごとにnameを付けておくと、Envoy側のルートにも同じ名前が付き、後で探しやすくなります。
ルート表・バーチャルホスト・ドメインの名前を導出する
サービスreviewsは、ネームスペースdefault、ポート9080です。このVirtualServiceがサイドカーで作るルート表の名前を、/root/ist2-route/02-names.txtに3行で書いてください。route_config=(ルート表の名前)、virtual_host=(バーチャルホストの名前)、domains=(そのバーチャルホストのdomainsのうち、短い名前の4種類をカンマ区切りで、順序は問いません)です。
出ていく側で、サイドカーはポートごとにルート表を1つ置きます。同じポート9080を使うサービスが複数あれば、1つの表の中に、バーチャルホストがサービスごとに1つずつ入り、リクエストのHostヘッダーで選びます。そのため、表の名前はポート、バーチャルホストの名前はFQDN:포트(プレースホルダーはポートです)です。同じネームスペースのアプリは、reviews、reviews.defaultのように短くも呼ぶので、domainsにはFQDNを後ろから切り落とした名前が一緒に入ります(公式ドキュメントのproxy-configの例を参照)。
その名前でルート表を立て、Hostヘッダーで選ばれるのを見る
/root/ist2-route/route.yamlにEnvoyの設定を書いてください。管理ポートは9984、リスナーは127.0.0.1:10084、ステップ2のルート表の名前・バーチャルホストの名前・domains4つを使います。ルートは、名前を付けて、この順序で3つです。jason(パス/、かつヘッダーend-userがちょうどjason → outbound|9080|v2|reviews.default.svc.cluster.local)、api(プレフィックス/api → outbound|9080|v1|reviews.default.svc.cluster.local)、default(プレフィックス/ → outbound|9080|v1|reviews.default.svc.cluster.local)です。クラスターは、v1(127.0.0.1:8105)とv2(127.0.0.1:8106)の2つです。アップストリーム2つをokとして起動し、Envoyを起動してから、3種類のHostヘッダーで/を呼び、/root/ist2-route/03-hosts.txtに、reviews=、reviews.default.svc.cluster.local=、ratings=の3行でHTTPコードを書いてください。
ルート表を選ぶのはリスナー(ポート)で、表の中でバーチャルホストを選ぶのはHostヘッダーです。どのバーチャルホストのdomainsにもないHostで来ると、ルートが見つからず404になります。サイドカーが知らないサービス名を呼んだときの姿です。curl -H 'Host: reviews' localhost:10084/のように、ヘッダーを変えながら呼んでください。ヘッダーマッチは、match.headersにstring_match: {exact: …}で書きます。
ヘッダーマッチとパスマッチが重なるとき、どちらが勝つか
route.yamlで起動しているEnvoyに、Hostreviewsで3つのリクエストを送り、応答本文のポートでサブセットを見分けて、/root/ist2-route/04-match.txtに書いてください。anonymous=(ヘッダーなしで/)、jason=(end-user: jasonで/)、api_as_jason=(end-user: jasonで/api/list)です。値はv1かv2です。
Envoyは、ルートを上から見て、最初に一致する1つだけを使います。api_as_jasonは、jasonのルールにもapiのルールにも一致するので、どちらが先に書かれているかが、答えを決めます。本文がok:8105ならv1、ok:8106ならv2です。
catch-allを前に置くと、解析は静かで、トラフィックは間違う
vs.yamlからdefaultルールを一番前に移した/root/ist2-route/vs-bad.yamlを作り、istioctl analyze --use-kube=false vs-bad.yaml dr.yamlの終了コードを確認してください。同じミスをEnvoyで再現します。route.yamlからdefaultルートを一番前に移した/root/ist2-route/route-bad.yamlでEnvoyを起動し直し、end-user: jasonで/を呼んでみてください。/root/ist2-route/05-order.txtに、analyze_rc=(vs-badの解析の終了コード)、jason_after=(route-badでjasonが行ったサブセット)の2行を書いてください。確認した後は、route.yamlに戻して起動しておいてください。
defaultには条件がないので、すべてのリクエストに一致します。それが一番前にあると、後ろのルールは決して使われません。ところが、analyzeは、各ルールが参照するものが存在するかを見るだけで、ルール同士の覆い隠しは見ないので、きれいだと言います。そのため、VirtualServiceを直すときは、「広いものは後ろへ」を、人が守る必要があります。yq '.spec.http |= [.[3], .[0], .[1], .[2]]'のように、リストの順序を変えられます。
weightはweighted_clustersになる — 40回数える
route.yamlを/root/ist2-route/route-split.yamlにコピーして、defaultルートだけをweighted_clustersに変えてください(v1が75、v2が25。VirtualServiceのdefaultルールそのまま)。--concurrency 1で起動し直してから、Hostreviewsで/をちょうど40回送り、/root/ist2-route/06-split.txtにv1=、v2=、total=の3行を書いてください。
重みは、リクエストごとにサイコロを振るものなので、40回でちょうど30対10にはなりません。合計が40で、v1のほうが多ければ、正しい結果です。Istioでカナリアの比率を1%に設定して、リクエスト数十個で確認すると、v2が一度も出ないことがあるのも、同じ理由です。
retriesとtimeoutがretry_policyになる — 何回叩かれるか数える
route-split.yamlを/root/ist2-route/route-retry.yamlにコピーして、2つを追加してください。クラスターoutbound|9080|broken|reviews.default.svc.cluster.local(127.0.0.1:8114、常に503を返すアップストリーム)と、defaultの前にflakyルート(プレフィックス/flaky → そのクラスター、timeout: 2s、retry_policy: {retry_on: 5xx, num_retries: 3})です。アップストリームを8114でfailとして起動し、Envoyを起動し直してから、/flakyを1回だけ呼んでください。/root/ist2-route/07-retry.txtに、status=(HTTPコード)、upstream_rq_retry=、upstream_rq_total=(brokenクラスターの2つの統計の値)を書いてください。
VirtualServiceのretries.attemptsは、Envoyのnum_retriesに、retryOnはretry_onに、timeoutはルートのtimeoutになります。元のリクエスト1つに、リトライが最大attempts回付くので、アップストリームは1+attempts回叩かれます。障害の起きたサービスに、リトライが負荷を何倍にも載せる理由です。統計は累積なので、起動し直した直後に1回だけ呼ぶと、数字がきれいになります。/stats?filter=で、brokenクラスターの2つの値を選んでください。
VirtualServiceの変換表にまとめる
/root/ist2-route/08-report.mdに、route_config=、virtual_host=、first_match_wins=(ステップ5で見たとおり、最初に一致するルート1つだけが使われるならyes)、retry_attempts=(VirtualServiceのattemptsの値)の4行を書き、その下に、- で始まる説明を4行以上書いてください。
値は前のステップのファイルから移してください。説明の行に、「VirtualServiceを直すとき、何を確認するか」を書いておくと、この表が、次の変更レビューのときに役立ちます。