復元力の設定値を見積もる
目標
リトライ・タイムアウト・サーキットブレーカー・障害注入・ミラーリングを自分で書きながら、各値がお互いにどんな制約をかけるのかを理解します。
なぜ重要なのか
レジリエンスの設定は、コピーしてきてもたいていは動作しないか、さらに悪いことに、障害を増幅させます。リトライを呼び出しチェーンのすべての階層にかけると、3段のチェーンでは最悪の場合に9倍、4段なら27倍のリクエストが、最も内側のサービスに集中します。すでに死にかけているサービスに対して、リトライがとどめの一撃になる構造です。
タイムアウトも同じです。timeoutはリトライとbackoffを含む全体の予算で、perTryTimeoutは各試行の上限です。速い失敗であれば、全体の予算が試行ごとの上限の合計より小さくても、リトライできます。逆に、1つの試行が全体の予算を使い切ると、次の試行が始まらないことがあります。このラボの3秒/1秒/追加2回は、設定を書くための値であり、3つの試行がそれぞれ1秒ずつ必ず実行されるという保証ではありません。実際の回数は、失敗の条件と応答時間、backoffを一緒に観測する必要があります。外側の呼び出し元が待つのをやめても、内側の業務処理が取り消されるという保証はないので、すでに処理された書き込みを再実行しないように、重複防止とキャンセルの伝播を別に設計します。
サーキットブレーカーは、値が大きすぎると事実上切れているのと同じで、小さすぎると平常時にも503が出ます。そのため、実測の同時実行数を基準に算定し、maxEjectionPercentで安全弁を置きます。このラボは、その感覚をマニフェストに移す訓練です。
Kubernetesの基本リソースは実際にapplyし、Istio CRDは/root/ica-resilience/の下にファイルとして作成します。kwokベースの設計ラボなので、実際のEnvoyのHTTP応答・リトライの回数・業務の重複処理を、ここで検証したものとして扱わないでください。
ステップ
- ネームスペース
ica-resilienceを作成し、ラベルistio-injection=enabledを付けてください。 - ネームスペース
ica-resilienceに、Deploymentinventoryをレプリカ3つでデプロイしてください。Podのラベルはapp=inventory、イメージはnginx:1.27-alpineです。 - 同じネームスペースにServiceを2つ作成してください。
inventoryとinventory-canaryで、どちらもポート8080、ポート名httpです。inventoryのselectorはapp=inventoryです。 /root/ica-resilience/vs-inventory-retry.yamlにVirtualServiceを作成してください。spec.hosts[0]はinventory.ica-resilience.svc.cluster.localで、httpルールにtimeout: 3s、retries.attempts: 2、retries.perTryTimeout: 1sを指定し、retries.retryOnには5xxとconnect-failureを含めてください。/root/ica-resilience/vs-inventory-fault.yamlにVirtualServiceを作成してください。ルールは2つです。最初のルールは、ヘッダーx-chaos-testがtrueのリクエストにだけ適用され、fault.delay.fixedDelay: 3s/fault.delay.percentage.value: 50/fault.abort.httpStatus: 503/fault.abort.percentage.value: 10を持ちます。2番目のルールは、matchもfaultもなく、ただルーティングするだけです。/root/ica-resilience/dr-inventory.yamlにDestinationRuleを作成してください。spec.hostはinventory.ica-resilience.svc.cluster.localで、trafficPolicyの下に、outlierDetection(consecutive5xxErrors 5、interval 10s、baseEjectionTime 30s、maxEjectionPercent 50)と、connectionPool(tcp.maxConnections 100、http.http1MaxPendingRequests 50)を置きます。/root/ica-resilience/vs-inventory-mirror.yamlにVirtualServiceを作成してください。inventory.ica-resilience.svc.cluster.localへ100%ルーティングしながら、inventory-canary.ica-resilience.svc.cluster.localへ20%ミラーリングし、全体のtimeoutは5sにします。
参考
retryOnは、カンマでつないだ文字列です。5xx,connect-failure,resetのように書きます。percentageは、valueフィールドを持つオブジェクトです。数値だけをぽんと書いてはいけません。- よくある間違い1: 障害注入のルールにcatch-allを作らず、ヘッダーのない通常のトラフィックが行き場を失うことです。
- よくある間違い2: ミラーリングにweightを2つ指定することです。ミラーは応答を捨てるので、実際のrouteは1つです。
- ミラーのリクエストも、DBへの書き込みのような副作用を起こします。書き込みの経路をミラーリングするには、対象のアプリケーションがシャドウモードで動作する必要があります。
ラボ用のネームスペースの準備
ネームスペースica-resilienceを作成し、ラベルistio-injection=enabledを付けてください。
前のラボと同じ方法です。自動注入のラベルを忘れないでください。
インスタンス3つのDeploymentをデプロイする
ネームスペースica-resilienceに、Deployment inventoryをレプリカ3つでデプロイしてください。Podのラベルはapp=inventory、イメージはnginx:1.27-alpineです。
outlierDetectionのmaxEjectionPercentを体感するには、インスタンスが複数ある必要があります。PodがReadyになるまで待ってから、採点してください。
本番用のServiceとカナリア用のServiceを作成する
同じネームスペースにServiceを2つ作成してください。inventoryとinventory-canaryで、どちらもポート8080、ポート名httpです。inventoryのselectorはapp=inventoryです。
ミラーリングの対象は、別のServiceにしておくほうが安全です。2つのServiceとも、ポート名をプロトコルが分かるように付けてください。
タイムアウトとリトライの関係を合わせる
/root/ica-resilience/vs-inventory-retry.yamlにVirtualServiceを作成してください。spec.hosts[0]はinventory.ica-resilience.svc.cluster.localで、httpルールにtimeout: 3s、retries.attempts: 2、retries.perTryTimeout: 1sを指定し、retries.retryOnには5xxとconnect-failureを含めてください。
timeoutは、リトライを含む全体のデッドラインです。perTryTimeout x (attempts + 1)がtimeoutを超えると、リトライが実行されないことがあります。retryOnには、リクエストが届く前に失敗した場合も含めてください。
ヘッダーでゲートした障害注入
/root/ica-resilience/vs-inventory-fault.yamlにVirtualServiceを作成してください。ルールは2つです。最初のルールは、ヘッダーx-chaos-testがtrueのリクエストにだけ適用され、fault.delay.fixedDelay: 3s/fault.delay.percentage.value: 50/fault.abort.httpStatus: 503/fault.abort.percentage.value: 10を持ちます。2番目のルールは、matchもfaultもなく、ただルーティングするだけです。
本番のクラスターで、全トラフィックにfaultをかけることは、カオステストではなく障害です。マッチするルールと、通常のトラフィック用のルールを分けて、faultはマッチした側にだけ置いてください。
サーキットブレーカーの上限を決める
/root/ica-resilience/dr-inventory.yamlにDestinationRuleを作成してください。spec.hostはinventory.ica-resilience.svc.cluster.localで、trafficPolicyの下に、outlierDetection(consecutive5xxErrors 5、interval 10s、baseEjectionTime 30s、maxEjectionPercent 50)と、connectionPool(tcp.maxConnections 100、http.http1MaxPendingRequests 50)を置いてください。
connectionPoolとoutlierDetectionは、同じDestinationRuleのtrafficPolicyの下に並んで入ります。インスタンスが3つのときに、同時排除の割合を100にすると、何が起きるかを考えてみてください。
シャドウトラフィックを流す
/root/ica-resilience/vs-inventory-mirror.yamlにVirtualServiceを作成してください。inventory.ica-resilience.svc.cluster.localへ100%ルーティングしながら、inventory-canary.ica-resilience.svc.cluster.localへ20%ミラーリングし、全体のtimeoutは5sにしてください。
ミラーリングは応答を捨てるので、実際のrouteは1つだけ置きます。ミラーの対象はステップ3で作ったカナリアのServiceで、割合は、mirrorの隣の別のフィールドで指定します。