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

Istio 実測ラボ

準備チェックは通るのに 500 を返す Pod

TT Labで続きを見る

目標

常に500を返すPodが混じったサービスで、外れ値検出によってそのPodが実際に排除されることを、レスポンスの数とEnvoyのエンドポイントの状態で確認し、ミラーリングと障害注入を加えて、それらの設定がEnvoyの何になるかを見ます。

なぜ重要なのか

Kubernetesのreadinessプローブは、Podが自分で報告する状態を見ます。リクエストごとに500を返しながらreadinessプローブは通るPodは、エンドポイントに残り続けます。外れ値検出は、実際に受け取ったレスポンスで判断するので、そのPodを外せますが、その分、故障を隠してしまうこともあります。排除を目で確認し、数字で数える方法を知って初めて、この機能を信頼して使えます。

ステップ

  1. kubectl apply -f /opt/fixtures/istlab/resilience-app.yamlで材料を載せ、Podがすべて準備できるまで待ってください。その後、clientからhttp://api/を30回呼び、/root/istlab-resilience/01-baseline.txtにok=(200の数)とerr=(500の数)を書いてください。
  2. /root/istlab-resilience/client.yamlにclientのPodを書き直し、アノテーションproxy.istio.io/configにproxyStatsMatcher.inclusionRegexps: [".*outlier_detection.*"]を入れて、既存のclientを削除した後、このファイルで作り直してください。
  3. /root/istlab-resilience/dr.yamlにDestinationRuleのapi(ホストapi.pay.svc.cluster.local)を書いて適用してください。outlierDetectionで、連続する5xxが2回(consecutive5xxErrors: 2)、検査周期2秒、基本の排除時間60秒、最大排除割合50%にします。その後、30回ずつ2回呼び、/root/istlab-resilience/03-outlier.txtにerr_first=(最初の30回の500の数)とerr_second=(2回目の30回の500の数)を書いてください。
  4. 排除がかかっている間(ステップ3の後60秒以内)に、istioctl proxy-config endpoints client.pay --cluster 'outbound|80||api.pay.svc.cluster.local' -o jsonの出力を/root/istlab-resilience/04-endpoints.jsonに保存してください。60秒が過ぎていたら、ステップ3のように30回呼んで再び排除させてから保存します。
  5. /root/istlab-resilience/vs.yamlにVirtualServiceのapi(ホストapi.pay.svc.cluster.local)を書いて適用してください。宛先は今までどおりapi.pay.svc.cluster.local、mirrorはapi-v2.pay.svc.cluster.local、mirrorPercentageは100です。適用後、x-request-idをmirror-1・mirror-2・mirror-3として3回呼び、/root/istlab-resilience/05-mirror.txtにmirrored=(api-v2のサイドカーのログに残ったその3つのIDの数)とclient_saw_v2=(clientが受け取った本文の中にv2があればyes)の2行を書いてください。
  6. /root/istlab-resilience/vs.yamlのVirtualServiceの前のほうに、ルールを2つ追加してください。ヘッダーx-chaos: abortならfault.abortで503を100%、x-chaos: delayならfault.delayで2秒を100%入れ、宛先は今までどおりです(ミラーを付けた基本ルールは最後に置きます)。適用後、/root/istlab-resilience/06-fault.txtに、abort_code=(abortのリクエストのステータスコード)、abort_flag=(そのリクエストのclientのサイドカーのアクセスログのレスポンスフラグ)、delay_seconds=(delayのリクエストにかかった時間の、小数点以下を切り捨てた秒数)の3行を書いてください。
  7. clientのサイドカーが受け取った設定から2つを取り出し、/root/istlab-resilience/07-envoy.jsonにJSONオブジェクトとして保存してください。outlierは、istioctl proxy-config cluster client.pay --fqdn api.pay.svc.cluster.local -o jsonの最初のクラスターのoutlierDetectionオブジェクトをそのまま、mirror_clusterは、istioctl proxy-config routes client.pay --name 80 -o jsonでapi.pay.svc.cluster.localの仮想ホストの最後のルートが持つrequestMirrorPolicies[0].clusterの値です。
  8. /root/istlab-resilience/08-report.mdに5行を書き、その下に学んだことを4行以上書いてください。5行は、errors_before=(ステップ1のerr)、errors_after_ejection=(ステップ3のerr_second)、ejected_ip=(ステップ4のファイルでfailedOutlierCheckがtrueのアドレス)、mirror_reached_client=(ステップ5のclient_saw_v2)、abort_flag=(ステップ6)です。

参考

故障したPodが1つ混じったサービス

kubectl apply -f /opt/fixtures/istlab/resilience-app.yamlで材料を載せ、Podがすべて準備できるまで待ってください。その後、clientからhttp://api/を30回呼び、/root/istlab-resilience/01-baseline.txtにok=(200の数)とerr=(500の数)を書いてください。

apiサービスの後ろにはPodが3つあり、そのうちapi-badは常に500を返します。readinessプローブがないので、Kubernetesは3つともReadyとみなしてエンドポイントに入れます。Kubernetesの目には正常なPodです。そのため、リクエストのおよそ3分の1が失敗します。Podの中のシェルで繰り返すには、sh -c 'for i in $(seq 1 30); do …; done'を使ってください。

見えなかった統計を有効にする

/root/istlab-resilience/client.yamlにclientのPodを書き直し、アノテーションproxy.istio.io/configにproxyStatsMatcher.inclusionRegexps: [".*outlier_detection.*"]を入れて、既存のclientを削除した後、このファイルで作り直してください。

Istioは、サイドカーが出すEnvoyの統計の大半を、デフォルトでフィルタして捨てます。Pod数千個がすべて出力したら、Prometheusが処理しきれないからです。そのため、外れ値検出を有効にしても、排除の回数のような数字が見えません。proxyStatsMatcherは、Podのアノテーションで必要な統計だけを復活させる仕組みで、プロキシが起動するときに読み込むので、Podを作り直す必要があります。うまく入ったかは、プロキシのブートストラップのstats_configで見られます。

外れ値検出で故障したPodを外す

/root/istlab-resilience/dr.yamlにDestinationRuleのapi(ホストapi.pay.svc.cluster.local)を書いて適用してください。outlierDetectionで、連続する5xxが2回(consecutive5xxErrors: 2)、検査周期2秒、基本の排除時間60秒、最大排除割合50%にします。その後、30回ずつ2回呼び、/root/istlab-resilience/03-outlier.txtにerr_first=(最初の30回の500の数)とerr_second=(2回目の30回の500の数)を書いてください。

外れ値検出は、サイドカーが実際に受け取ったレスポンスで、アップストリームを1つずつ判断します。同じPodが続けて5xxを返すと、そのPodをロードバランシングの対象からしばらく外します(排除)。最初の回では排除される前の数回の500が見え、2回目は0になるはずです。排除された回数は、ステップ2で復活させた統計…outlier_detection.ejections_enforced_totalで数えます。kubectl -n pay exec client -c istio-proxy -- pilot-agent request GET stats | grep outlierです。

Envoyはそのpodをどう記録したか

排除がかかっている間(ステップ3の後60秒以内)に、istioctl proxy-config endpoints client.pay --cluster 'outbound|80||api.pay.svc.cluster.local' -o jsonの出力を/root/istlab-resilience/04-endpoints.jsonに保存してください。60秒が過ぎていたら、ステップ3のように30回呼んで再び排除させてから保存します。

排除されたPodは、Kubernetesのエンドポイントからは外れません。このサイドカーのロードバランシングテーブルでだけ外れます。そのためkubectl get endpointsには3つがそのまま残り、Envoyのエンドポイント一覧で、そのPodのhealthStatusにfailedOutlierCheck: trueが付きます。表の形で見ると、OUTLIER CHECKの欄がFAILEDです。排除は永久ではなく、基本の排除時間の後に再び入り、また失敗すればより長く外れます。

本番トラフィックを新バージョンに映してみる: ミラーリング

/root/istlab-resilience/vs.yamlにVirtualServiceのapi(ホストapi.pay.svc.cluster.local)を書いて適用してください。宛先は今までどおりapi.pay.svc.cluster.local、mirrorはapi-v2.pay.svc.cluster.local、mirrorPercentageは100です。適用後、x-request-idをmirror-1・mirror-2・mirror-3として3回呼び、/root/istlab-resilience/05-mirror.txtにmirrored=(api-v2のサイドカーのログに残ったその3つのIDの数)とclient_saw_v2=(clientが受け取った本文の中にv2があればyes)の2行を書いてください。

ミラーリングは、リクエストを複製して片方へ送り、そのレスポンスは捨てます。そのため、新しいバージョンを本番のトラフィックで試しながら、ユーザーには影響を与えません。複製は元のリクエストと同じx-request-idを持つので、ミラー先のサイドカーのログでIDによって探せます。kubectl -n pay logs deploy/api-v2 -c istio-proxyです。複製は非同期で行くので、ログに少し遅れて出力されることがあります。

障害をわざと入れる: ヘッダーがあるときだけ

/root/istlab-resilience/vs.yamlのVirtualServiceの前のほうに、ルールを2つ追加してください。ヘッダーx-chaos: abortならfault.abortで503を100%、x-chaos: delayならfault.delayで2秒を100%入れ、宛先は今までどおりです(ミラーを付けた基本ルールは最後に置きます)。適用後、/root/istlab-resilience/06-fault.txtに、abort_code=(abortのリクエストのステータスコード)、abort_flag=(そのリクエストのclientのサイドカーのアクセスログのレスポンスフラグ)、delay_seconds=(delayのリクエストにかかった時間の、小数点以下を切り捨てた秒数)の3行を書いてください。

障害注入は、クライアント側のサイドカーがリクエストをアップストリームに送る前に行います。そのためabortはアップストリームに届かず、レスポンスフラグがFI(fault injected)になります。ヘッダー条件を付けておけば、普段のトラフィックはそのままにして、試すリクエストだけを壊せます。VirtualServiceのhttpルールは、上から最初に一致したものが使われるので、包括的なルールは最後に置きます。かかった時間はcurl -w '%{time_total}'で測ります。

DestinationRuleとVirtualServiceはEnvoyの何になったか

clientのサイドカーが受け取った設定から2つを取り出し、/root/istlab-resilience/07-envoy.jsonにJSONオブジェクトとして保存してください。outlierは、istioctl proxy-config cluster client.pay --fqdn api.pay.svc.cluster.local -o jsonの最初のクラスターのoutlierDetectionオブジェクトをそのまま、mirror_clusterは、istioctl proxy-config routes client.pay --name 80 -o jsonでapi.pay.svc.cluster.localの仮想ホストの最後のルートが持つrequestMirrorPolicies[0].clusterの値です。

istiodは、DestinationRuleのconsecutive5xxErrors・interval・baseEjectionTime・maxEjectionPercentをEnvoyクラスターのoutlierDetectionに、VirtualServiceのmirrorをルートのrequestMirrorPoliciesに移します。名前が少しずつ違い(consecutive5xx)、時間は"2s"のような文字列に変わります。設定を変えたのに動作がおかしければ、結局はこの変換結果を見ることになります。

メッシュが何を代わりにしてくれたかを書く

/root/istlab-resilience/08-report.mdに5行を書き、その下に学んだことを4行以上書いてください。5行は、errors_before=(ステップ1のerr)、errors_after_ejection=(ステップ3のerr_second)、ejected_ip=(ステップ4のファイルでfailedOutlierCheckがtrueのアドレス)、mirror_reached_client=(ステップ5のclient_saw_v2)、abort_flag=(ステップ6)です。

値は前のステップのファイルから写してください。説明の行には、「Kubernetesのreadinessプローブとメッシュの外れ値検出が、それぞれ何を見て判断するか」を書いておいてください。片方はPodが報告する状態を、もう片方は実際に受け取ったレスポンスを見ます。