コードを触らずに障害を作ってみる
目標
HTTPフィルターチェーンに、障害注入とレート制限を挟んでみて、順序と条件と割合が結果をどう変えるかを、自分で確認します。
なぜ重要なのか
フィルターは、アプリケーションに触れずに、その前で何かをする場所です。そのため、「決済が遅くなったら私たちはどうなるか」のような質問に、推論ではなく実測で答えられます。ただし、この道具は誤って使うと、それ自体が障害になります。条件なしで有効にした障害注入、プロキシの台数を考慮し忘れたレート制限の上限、順序が逆になったチェーンは、すべて実際に事故が起きた形です。一度手で作っておけば、その3つを設定を見ただけで見抜けるようになります。
ステップ
- アップストリームを
8095でokとして起動してください。/root/envd-filter/f-ok.yamlにフィルターがrouterだけの設定を置き(管理9976、リスナー127.0.0.1:10076、クラスターorigin)、/root/envd-filter/f-badorder.yamlにはrouterの後ろにfaultフィルターを置いた設定を置いてください。2つのファイルをそれぞれenvoy --mode validateで検査して、/root/envd-filter/01-order.txtにok_rc=とbad_rc=の2行を書いてください。 /root/envd-filter/f-abort.yamlを作ってください。routerの前にfaultフィルターを置き、abortで418を返します。条件はリクエストヘッダーx-fault: yesです。起動してから、ヘッダーを付けたリクエストと付けないリクエストをそれぞれ送り、/root/envd-filter/02-abort.txtにwith=とwithout=の2行(それぞれのHTTPコード)を書いてください。/root/envd-filter/f-delay.yamlを作ってください。faultフィルターのdelay.fixed_delayを2秒にし、条件はヘッダーx-slow: yesです。起動してから、ヘッダーを付けたリクエストと付けないリクエストの所要時間を測り、/root/envd-filter/03-delay.txtにslow_ms=・fast_ms=・gap_ms=の3行を書いてください(ミリ秒の整数、gap_msは2つの値の差)。/root/envd-filter/f-half.yamlを作ってください。条件なしでabortにより503を返しますが、percentageは50%にします。起動してから20回リクエストし、/root/envd-filter/04-percentage.txtにtotal=20、aborted=(503を受け取った回数)、passed=(200を受け取った回数)の3行を書いてください。/root/envd-filter/f-lrl.yamlを作ってください。faultの代わりにlocal_ratelimitフィルターをrouterの前に置き、stat_prefixはlrl、トークンはmax_tokens3・tokens_per_fill3・fill_interval300sで、有効化と強制はどちらも100%です。起動してから6回リクエストし、/root/envd-filter/05-ratelimit.txtにok=・limited=・codes=の3行を書いてください(codesは受け取ったコードを空白区切りで)。/root/envd-filter/f-perroute.yamlを作ってください。グローバルなlocal_ratelimitは、filter_enabledを0%にして無効にしておき、ルート/tightにはtyped_per_filter_configでトークン1個(stat_prefix: tight)、/looseにはトークン5個(stat_prefix: loose)を与えます。起動してから、/tightに3回、/looseに3回リクエストし、/root/envd-filter/06-perroute.txtにtight_ok=・tight_limited=・loose_ok=・loose_limited=の4行を書いてください。- いま起動しているEnvoyの
/statsから、レート制限に関する統計を取り出し、/root/envd-filter/07-stats.txtにtight_rate_limited=・loose_rate_limited=・enabled_total=の3行を書いてください(前の2つは各stat_prefixのrate_limitedの値、最後は2つのstat_prefixのenabledの値を足した数)。 /root/envd-filter/08-report.mdに、router_last=(終端フィルターが最後でなければならないならyes)、abort_status=(ステップ2で受け取ったコード)、delay_ms=(ステップ3のgap_ms)、tight_allowed=(ステップ6で/tightが通した回数)の4行を書き、その下に学んだことを4行以上書いてください。
参考
- Envoyを起動するときは
setsid --fork nohup envoy -c <파일> --log-level warn --concurrency 1 > <로그> 2>&1 </dev/null(プレースホルダーは設定ファイルとログファイルです)を使い、再起動する前にはpkill -x envoyで片付けてください。 - 起動は、固定の
sleepではなく、/readyがLIVEを返すまで回るループで待ちます。 - ステータスコードだけを受け取るには
curl -s -o /dev/null -w '%{http_code}'を、所要時間には-w '%{time_total}'を使います。時間は秒単位の小数なので、ミリ秒に変えるには掛け算が必要です。 - アップストリームは
python3 /opt/lab/envoy/upstream.py <포트> ok(プレースホルダーはポート番号です)で起動します。 - よくある間違い:
routerをチェーンの先頭や途中に置いてしまうことです。その後ろのフィルターは永遠に実行されないので、Envoyが設定をまるごと拒否します。 - よくある間違い:
fill_intervalを短くしてしまうことです。ラボの最中にトークンが再び補充され、結果がぶれます。
チェーンの最後は決まっている
アップストリームを8095でokとして起動してください。/root/envd-filter/f-ok.yamlにフィルターがrouterだけの設定を置き(管理9976、リスナー127.0.0.1:10076、クラスターorigin)、/root/envd-filter/f-badorder.yamlにはrouterの後ろにfaultフィルターを置いた設定を置いてください。2つのファイルをそれぞれenvoy --mode validateで検査して、/root/envd-filter/01-order.txtにok_rc=とbad_rc=の2行を書いてください。
フィルターチェーンの最後のフィルターは、リクエストを実際にアップストリームへ送り出す役割を担います。それがrouterで、このようなフィルターを終端フィルターと呼びます。終端フィルターの後ろに何かを置くと、そのフィルターは永遠に実行されないので、Envoyはその状態を実行中に放置せず、設定を読み込む時点で拒否します。拒否メッセージに理由がそのまま出るので、読んでおいてください。
条件に合うリクエストだけを切断する
/root/envd-filter/f-abort.yamlを作ってください。routerの前にfaultフィルターを置き、abortで418を返します。条件はリクエストヘッダーx-fault: yesです。起動してから、ヘッダーを付けたリクエストと付けないリクエストをそれぞれ送り、/root/envd-filter/02-abort.txtにwith=とwithout=の2行(それぞれのHTTPコード)を書いてください。
障害注入は、「このサービスが死んだら、自分たちのサービスはどうなるか」を運用中に確認する道具です。そのため、条件を付けることが核心です。条件なしで有効にすると、すべてのユーザーが対象になります。ヘッダー条件を使えば、テスト用ツールだけがそのヘッダーを付けて、自分のリクエストにだけ障害を経験させられます。418は実際のサービスでは使わないコードなので、実験用として目立ちます。
切断する代わりに遅くする
/root/envd-filter/f-delay.yamlを作ってください。faultフィルターのdelay.fixed_delayを2秒にし、条件はヘッダーx-slow: yesです。起動してから、ヘッダーを付けたリクエストと付けないリクエストの所要時間を測り、/root/envd-filter/03-delay.txtにslow_ms=・fast_ms=・gap_ms=の3行を書いてください(ミリ秒の整数、gap_msは2つの値の差)。
遅くなることは、切断されることより扱いが難しいです。切断されればすぐにわかりますが、遅くなると、接続がたまり、スレッドが塞がれ、そのあとでやっと障害になります。そのため、タイムアウトとサーキットブレーカーが正しく設定されているかを確認するには、切断する実験ではなく、遅くする実験が必要です。所要時間はcurl -w '%{time_total}'で測ります。秒単位の小数なので、ミリ秒に変えるには掛け算が必要です。
一部だけを選んで切断する
/root/envd-filter/f-half.yamlを作ってください。条件なしでabortにより503を返しますが、percentageは50%にします。起動してから20回リクエストし、/root/envd-filter/04-percentage.txtにtotal=20、aborted=(503を受け取った回数)、passed=(200を受け取った回数)の3行を書いてください。
割合は、リクエストごとに独立して引く確率であり、「20回中ちょうど10回」ではありません。そのため、同じ設定で数え直すと、数字が変わります。カナリアの検証で「比率が合わない」という報告が出る理由と同じです。本番で障害注入を使うときに、割合をごく低く始めるのも、この性質のためです。100分の1でも、リクエストが多ければ十分な標本になります。
プロキシの中でトークンを数える
/root/envd-filter/f-lrl.yamlを作ってください。faultの代わりにlocal_ratelimitフィルターをrouterの前に置き、stat_prefixはlrl、トークンはmax_tokens 3・tokens_per_fill 3・fill_interval 300sで、有効化と強制はどちらも100%です。起動してから6回リクエストし、/root/envd-filter/05-ratelimit.txtにok=・limited=・codes=の3行を書いてください(codesは受け取ったコードを空白区切りで)。
ローカルレート制限は、そのプロキシの中だけでトークンを数えます。プロキシが10台なら、上限が事実上10倍になるので、全体の上限を守るには、台数で割って設定するか、外部のレート制限サービスを使う必要があります。その代わり、外部に問い合わせないので遅延がなく、そのサービスが死んでも影響がありません。上限を超えたリクエストは429を受け取ります。fill_intervalを長くしておくと、ラボの最中にトークンが再び補充されないので、結果が安定します。
同じフィルターをルートごとに変えて設定する
/root/envd-filter/f-perroute.yamlを作ってください。グローバルなlocal_ratelimitは、filter_enabledを0%にして無効にしておき、ルート/tightにはtyped_per_filter_configでトークン1個(stat_prefix: tight)、/looseにはトークン5個(stat_prefix: loose)を与えます。起動してから、/tightに3回、/looseに3回リクエストし、/root/envd-filter/06-perroute.txtにtight_ok=・tight_limited=・loose_ok=・loose_limited=の4行を書いてください。
フィルターはリスナー単位で付きますが、設定はルートやバーチャルホスト単位で上書きできます。それがtyped_per_filter_configです。ログインのパスだけ厳しく、参照のパスはゆるく、といった要求が、こうして解決します。グローバルな設定を無効にしておき、ルートでだけ有効にするのも、よく見られる形です。キーはフィルターの名前で、値の中には、そのフィルターの設定をまるごと書き直します。各ルートのstat_prefixを変えておけば、統計でも分けて見られます。
フィルターが残す数字を読む
いま起動しているEnvoyの/statsから、レート制限に関する統計を取り出し、/root/envd-filter/07-stats.txtにtight_rate_limited=・loose_rate_limited=・enabled_total=の3行を書いてください(前の2つは各stat_prefixのrate_limitedの値、最後は2つのstat_prefixのenabledの値を足した数)。
フィルターは、自分の名前の統計を残します。ローカルレート制限の名前は<그 자리의 stat_prefix>.http_local_rate_limit.<항목>(プレースホルダーはその箇所のstat_prefixと項目です)の形で、項目にはenabled(対象になったリクエスト数)・rate_limited(実際にブロックされた数)・ok(通過した数)があります。運用で重要なのは、ブロックされた数そのものではなく、ブロックされた数と対象になった数の割合です。その割合が高いままなら、上限が現実と合っていません。名前を正確に知らなければ、curl -s localhost:<admin>/stats | grep rate_limitでまず探してみてください。
フィルター設計のメモを残す
/root/envd-filter/08-report.mdに、router_last=(終端フィルターが最後でなければならないならyes)、abort_status=(ステップ2で受け取ったコード)、delay_ms=(ステップ3のgap_ms)、tight_allowed=(ステップ6で/tightが通した回数)の4行を書き、その下に学んだことを4行以上書いてください。
説明の行には、各仕組みをいつ使い、いつ使ってはいけないかを書いてください。たとえば、障害注入は条件を付けて使う、ローカルレート制限はプロキシの台数だけ上限が増える、といったことです。値は前のステップのファイルから取ってください。