マイクロサービスが過負荷状態または部分的な障害に陥った場合、制御されていないトラフィックがシステム全体にカスケードし、健全なサービスまで停止させる可能性があります。ネットワークレベルでのサーキットブレーカー機能(Service Mesh (ASM) のサイドカープロキシ経由)は、バックエンドに到達する前に過剰なトラフィックを拒否します。この方法では、各サービスのアプリケーションコードを変更する必要はありません。Resilience4j などの従来のアプローチでは、サーキットブレーカーのロジックをアプリケーションコードに直接埋め込む必要があります。
connectionPool フィールドを 宛先ルール (DestinationRule) で設定することで、送信先サービスへの同時接続数および保留中のリクエスト数を制限できます。以下のセクションでは、関連パラメーターを説明し、4 種類の Pod スケーリング構成における動作を検証し、本番環境でのサーキットブレーカーのモニタリング方法について解説します。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
Container Service for Kubernetes (ACK) クラスターが、ご利用の Service Mesh (ASM) インスタンスに追加済みであること(「ASM インスタンスへのクラスターの追加」をご参照ください)
接続プールによるサーキットブレーカーの仕組み
対象サービスに対してサーキットブレーカーを有効化するには、connectionPool 設定を含む 宛先ルール (DestinationRule) を作成します。フィールドの完全なリファレンスについては、「」および「宛先ルール (Destination Rule)」をご参照ください。
接続プールの動作を制御するパラメーターは以下の 3 つです。
| パラメーター | 型 | 必須 | デフォルト値 | 説明 |
|---|---|---|---|---|
tcp.maxConnections | int32 | いいえ | 2^32−1 | 送信先ホストへの HTTP/1.1 または TCP 接続の最大数。クライアント側およびサーバー側の両方のサイドカープロキシで適用されます。単一のクライアント Pod が開ける接続数はこの値を超えることができず、単一のサーバー Pod が受け入れられる接続数も同様に制限されます。サーバー側の実効的なキャパシティは、min(クライアント Pod 数, サーバー Pod 数) × maxConnections となります。 |
http.http1MaxPendingRequests | int32 | いいえ | 1024 | 利用可能な接続を待機中のキューに格納される最大リクエスト数。この値を 0 に設定するとデフォルト値 (1024) が使用されるため、少なくとも 1 を指定してください。 |
http.http2MaxRequests | int32 | いいえ | 1024 | バックエンドへのアクティブなリクエストの最大数。 |
connectionPoolフィールドは接続数およびキューイングされたリクエスト数を制限しますが、ロードバランシングプールから不健全なホストを除外しません。エラー率に基づくホスト除外を行うには、同一の 宛先ルール (DestinationRule) 内でconnectionPoolとoutlierDetectionを併用してください。「宛先ルール (Destination Rule)」のoutlierDetectionフィールドについて詳しくは、こちらをご参照ください。
クライアント Pod とサーバー Pod がそれぞれ 1 つの場合、これらのパラメーターは予測可能な動作を示します。しかし、本番環境では通常、サービスが複数の Pod で実行されます。以下に、4 つの一般的なトポロジーにおけるサーキットブレーカーの動作を示します。
クライアント Pod が 1 つ、送信先サービスの Pod が 1 つ
クライアント Pod が 1 つ、送信先サービスの Pod が複数
クライアント Pod が複数、送信先サービスの Pod が 1 つ
クライアント Pod が複数、送信先サービスの Pod が複数
サンプルアプリケーションのデプロイ
サンプルセットアップには以下の 2 つのコンポーネントがあります。
サーバー:ポート 9080 で
/helloエンドポイントをリッスンする Flask アプリケーション。各リクエストの処理には 5 秒かかります(遅いバックエンドを模擬)。クライアント:1 回のバッチで 10 件の並列リクエストを送信する Python スクリプト。バッチは毎分の 0 秒、20 秒、40 秒に発行されるため、複数のクライアント Pod が同時にリクエストを送信します。
以下の YAML を保存し、
kubectl apply -f <file-name>.yamlを実行して、サンプルアプリケーションをデプロイします。Pod が実行中であることを確認します。想定される出力例:
kubectl get po | grep circuitcircuit-breaker-sample-client-d4f64d66d-fwrh4 2/2 Running 0 1m22s circuit-breaker-sample-server-6d6ddb4b-gcthv 2/2 Running 0 1m22s
宛先ルール (DestinationRule) を設定していない場合、サーバーはすべての 10 件の同時リクエストを処理し、すべての応答はステータスコード 200 を返します。
----------Info----------
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.016539812088013
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012614488601685
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.015984535217285
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.015599012374878
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012874364852905
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.018714904785156
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.010422468185425
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.012431621551514
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.011001348495483
Status: 200, Start: 02:39:20, End: 02:39:25, Elapsed Time: 5.01432466506958サーキットブレーカー用の宛先ルール (DestinationRule) の作成
送信先サービスに対してサーキットブレーカーを有効化するには、宛先ルール (DestinationRule) を定義します。詳細については、「宛先ルールの管理」をご参照ください。
以下のルールでは、TCP 接続数を 5 に制限しています。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: circuit-breaker-sample-server
spec:
host: circuit-breaker-sample-server
trafficPolicy:
connectionPool:
tcp:
maxConnections: 5シナリオ 1:クライアント Pod が 1 つ、送信先サービスの Pod が 1 つ
クライアント Pod を再起動し、そのログを確認します。10 件のリクエストすべてが成功しますが、約 5 秒で完了するのは 5 件のみです。残りのリクエストは、接続が解放されるまでキューに待機するため、10 秒以上かかります。
tcp.maxConnectionsのみを設定した場合、過剰なリクエストは失敗するのではなくキューに格納されます。デフォルトのキュー深さは 2^32 − 1 です。----------Info---------- Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.0167787075042725 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.011920690536499 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.017078161239624 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.018405437469482 Status: 200, Start: 02:49:40, End: 02:49:45, Elapsed Time: 5.018689393997192 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.018936395645142 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.016417503356934 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.019930601119995 Status: 200, Start: 02:49:40, End: 02:49:50, Elapsed Time: 10.022735834121704 Status: 200, Start: 02:49:40, End: 02:49:55, Elapsed Time: 15.02303147315979即時失敗(fail-fast)型のサーキットブレーカーを実現するには、
http.http1MaxPendingRequestsも制限します。宛先ルール (DestinationRule) を更新してください。詳細については、「宛先ルールの管理」をご参照ください。apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: circuit-breaker-sample-server spec: host: circuit-breaker-sample-server trafficPolicy: connectionPool: tcp: maxConnections: 5 http: http1MaxPendingRequests: 1クライアント Pod を再起動し、そのログを確認します。4 件のリクエストは即時に拒否され(ステータスコード 503)、5 件が送信先に到達し、1 件はキューに格納されて(空き接続を待って)約 10 秒後に完了します。
----------Info---------- Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.005339622497558594 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.007254838943481445 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.0044133663177490234 Status: 503, Start: 02:56:40, End: 02:56:40, Elapsed Time: 0.008964776992797852 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.018309116363525 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.017424821853638 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.019804954528809 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.01643180847168 Status: 200, Start: 02:56:40, End: 02:56:45, Elapsed Time: 5.025975227355957 Status: 200, Start: 02:56:40, End: 02:56:50, Elapsed Time: 10.01716136932373クライアントのサイドカープロキシからアクティブ接続数を確認します。想定される出力:クライアントプロキシから送信先 Pod へのアクティブ接続が 5 件であり、
maxConnections制限と一致します。kubectl exec $(kubectl get pod --selector app=circuit-breaker-sample-client --output jsonpath='{.items[0].metadata.name}') -c istio-proxy -- curl -X POST http://localhost:15000/clusters | grep circuit-breaker-sample-server | grep cx_activeoutbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.124:9080::cx_active::5
シナリオ 2:クライアント Pod が 1 つ、送信先サービスの Pod が複数
このシナリオでは、接続制限が Pod 単位で適用されるか、サービス単位で適用されるかを検証します。クライアント Pod が 1 つ、送信先サービスの Pod が 3 つの場合:
Pod 単位の制限:各 Pod が 5 接続を許可するため、合計で 15 接続になります。10 件のリクエストすべてが約 5 秒で成功するはずです。
サービス単位の制限:Pod 数に関係なく、合計で 5 接続のみ許可されます。スロットル動作はシナリオ 1 と一致します。
送信先サービスを 3 レプリカにスケールします。
kubectl scale deployment/circuit-breaker-sample-server --replicas=3クライアント Pod を再起動し、そのログを確認します。スロットルパターンはシナリオ 1 と同一です。送信先 Pod を増やしても、クライアントの接続制限は向上しません。接続制限は Pod 単位ではなく、サービス単位で適用されます。
----------Info---------- Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.011791706085205078 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.0032286643981933594 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.012153387069702148 Status: 503, Start: 03:06:20, End: 03:06:20, Elapsed Time: 0.011871814727783203 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.012892484664917 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.013102769851685 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.016939163208008 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.014261484146118 Status: 200, Start: 03:06:20, End: 03:06:25, Elapsed Time: 5.01246190071106 Status: 200, Start: 03:06:20, End: 03:06:30, Elapsed Time: 10.021712064743042アクティブ接続の分散状況を確認します。想定される出力:プロキシは接続を各 Pod に分散しており、各 Pod に 2 接続、合計 6 接続(5 接続ではない)となっています。Envoy および Istio のドキュメントにも記載されている通り、プロキシは接続数に関して若干の許容範囲を設けています。
kubectl exec $(kubectl get pod --selector app=circuit-breaker-sample-client --output jsonpath='{.items[0].metadata.name}') -c istio-proxy -- curl -X POST http://localhost:15000/clusters | grep circuit-breaker-sample-server | grep cx_activeoutbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.124:9080::cx_active::2 outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.158:9080::cx_active::2 outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local::172.20.192.26:9080::cx_active::2
シナリオ 3:クライアント Pod が複数、送信先サービスの Pod が 1 つ
レプリカの調整: サーバーを 1 に、クライアントを 3 にスケール
kubectl scale deployment/circuit-breaker-sample-server --replicas=1 kubectl scale deployment/circuit-breaker-sample-client --replicas=3クライアント Pod を再起動し、そのログを確認します。
各クライアントで 503 エラーの発生率が上昇します。各クライアントのサイドカープロキシは、独自に 5 接続の制限を適用しますが、単一の送信先サービスのサイドカープロキシもまた 5 接続の制限を適用します。すべてのクライアントからのリクエストの中で、同時に成功できるのは最大 5 件のみです。
クライアントプロキシのログで応答フラグを確認してください。
スロットルされたリクエストは、以下のいずれかのレスポンスフラグとともにステータスコード 503 を返します。
フラグ 意味 発生箇所 UOUpstream overflow(サーキットブレーカー) クライアントのサイドカープロキシがリクエストをローカルでスロットル URXUpstream retry/connection limit exceeded 送信先サービスのサイドカープロキシがリクエストを拒否 アクセスログの
DURATION、UPSTREAM_HOST、およびUPSTREAM_CLUSTERを確認することで、これら 2 つのフラグを区別できます。UOのリクエストは upstream host が存在せず(送信前にスロットル)、URXのリクエストは送信先プロキシに到達し、そこで拒否されています。送信先サービスのサイドカープロキシのログを確認して確認します。
送信先サービスのサイドカープロキシも、
UOフラグとともにステータスコード 503 を返します。これは、クライアントのサイドカープロキシのログに表示されるURXエントリが、送信先サービスのサイドカープロキシによって過剰な接続が拒否された結果であることを確認します。
リクエストフローのまとめ:
各クライアントのサイドカープロキシは、独自に 5 接続の制限を適用します。クライアントが 3 つある場合、クライアントのサイドカープロキシから並列で送出されるリクエストは最大 15 件になります。しかし、単一の送信先サービスのサイドカープロキシもまた 5 接続の制限を適用するため、5 件のみを受け入れ、残りは拒否します。拒否されたリクエストは、クライアントのサイドカープロキシのログに URX として表示されます。
シナリオ 4:クライアント Pod が複数、送信先サービスの Pod が複数
送信先サービスのスケーリングにより、全体的な成功確率が向上します。これは、各送信先 Pod のサイドカープロキシが独立して 5 接続を許可するためです。
送信先サービスを 2 レプリカ、クライアントを 3 レプリカに設定します。送信先 Pod が 2 つ(それぞれ 5 接続を許可)の場合、3 クライアントから送信される合計 30 件のリクエストのうち、1 バッチあたり 10 件が成功します。
kubectl scale deployment/circuit-breaker-sample-server --replicas=2 kubectl scale deployment/circuit-breaker-sample-client --replicas=3送信先サービスを 3 レプリカにスケールします。1 バッチあたり 15 件のリクエストが成功します。
kubectl scale deployment/circuit-breaker-sample-server --replicas=3送信先サービスを 4 レプリカにスケールします。それでも成功するのは依然として 15 件のみです。クライアントのサイドカープロキシの制限は、利用可能な送信先 Pod 数に関係なくクライアントごとに 5 接続で固定されています。クライアントが 3 つある場合、成功する同時リクエストの最大数は 3 × 5 = 15 件です。
kubectl scale deployment/circuit-breaker-sample-server --replicas=4
クライアントおよびサーバーの制約のまとめ
| 役割 | 制限の適用方法 |
|---|---|
| クライアント | 各クライアントのサイドカープロキシが独立して制限を適用します。maxConnections が 100 で、クライアント Pod 数が N の場合、すべてのクライアント間で最大 N × 100 件のリクエストが並列で送信可能です。この制限は個々の送信先 Pod ではなく、送信先サービス全体に適用されます。送信先 Pod が 200 個あっても、単一のクライアントのサイドカープロキシは 100 接続で上限となります。 |
| 送信先サービス | 各送信先 Pod のサイドカープロキシが独立して制限を適用します。アクティブな Pod が 50 個あり、maxConnections が 100 に設定されている場合、各 Pod はクライアントのサイドカープロキシから最大 100 接続を受け入れ、それ以上の接続はステータスコード 503 で拒否します。 |
サーキットブレーカーのメトリックのモニタリング
サーキットブレーカーが作動すると、Envoy はスロットルの検出および診断のためのメトリックを生成します。
| メトリック | タイプ | 説明 |
|---|---|---|
envoy_cluster_circuit_breakers_default_cx_open | Gauge | 1 の場合、接続プールのサーキットブレーカーがオープン(作動中)です。0 の場合は、閉じています。 |
envoy_cluster_circuit_breakers_default_rq_pending_open | Gauge | 1 の場合、保留中のリクエストキューがその上限に達しています。0 の場合は、上限未達です。 |
サーキットブレーカーのメトリックの有効化
サイドカープロキシの
proxyStatsMatcherを設定します。正規表現マッチ を選択し、値を.*circuit_breaker.*に設定します。詳細については、「proxyStatsMatcher」をご参照ください。circuit-breaker-sample-serverおよびcircuit-breaker-sample-clientのデプロイメントを再デプロイします。詳細については、「ワークロードの再デプロイ」をご参照ください。前述のシナリオからサーキットブレーカーのテストを再実行します。
クライアントのサイドカープロキシからメトリックを照会します。想定される出力:
kubectl exec -it deploy/circuit-breaker-sample-client -c istio-proxy -- curl localhost:15090/stats/prometheus | grep circuit_breaker | grep circuit-breaker-sample-serverenvoy_cluster_circuit_breakers_default_cx_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 1 envoy_cluster_circuit_breakers_default_cx_pool_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_default_remaining_cx{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_default_remaining_cx_pools{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 18446744073709551613 envoy_cluster_circuit_breakers_default_remaining_pending{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 1 envoy_cluster_circuit_breakers_default_remaining_retries{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 4294967295 envoy_cluster_circuit_breakers_default_remaining_rq{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 4294967295 envoy_cluster_circuit_breakers_default_rq_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_default_rq_pending_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_default_rq_retry_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_high_cx_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_high_cx_pool_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_high_rq_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_high_rq_pending_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0 envoy_cluster_circuit_breakers_high_rq_retry_open{cluster_name="outbound|9080||circuit-breaker-sample-server.default.svc.cluster.local"} 0
サーキットブレーカーのアラート設定
Managed Service for Prometheus を使用してサーキットブレーカーのメトリックを収集し、アラートルールを設定します。コンポーネント統合の詳細については、「コンポーネントの管理」をご参照ください。
すでに自己管理型の Prometheus インスタンスを使用して ASM のメトリックを収集している場合(「自己管理型 Prometheus インスタンスを使用した ASM インスタンスのモニタリング」をご参照ください)は、ステップ 1 をスキップしてください。
Managed Service for Prometheus で、データプレーンのクラスターを Alibaba Cloud ASM コンポーネントに接続するか、最新バージョンにアップグレードします。
カスタム PromQL 文を使用してアラートルールを作成します。詳細については、「カスタム PromQL 文を使用したアラートルールの作成」をご参照ください。以下のパラメーターを参考にしてください。
パラメーター 例 説明 カスタム PromQL 文 (sum by(cluster_name, pod_name, namespace) (envoy_cluster_circuit_breakers_default_cx_open)) != 0任意の接続プールでサーキットブレーカーが作動しているかどうかをチェックします。上流サービス名、Pod、名前空間でグループ化することで、スロットルが発生している場所を特定できます。 アラートメッセージ サーキットブレーカーが作動しています。TCP 接続制限に達しました。名前空間:{{$labels.namespace}}、Pod:{{$labels.pod_name}}、上流サービス:{{$labels.cluster_name}} 影響を受ける Pod、その名前空間、および上流サービスを特定します。