Azure Container Apps の Resiliency Policy ③ - サービス検出のレジリエンシーでのサーキットブレーカーの構成と実行例

# 本記事について

[本シリーズの最初の記事](https://qiita.com/YoshiakiOi/items/1ec0352f90159d886ae6)では、Container Apps においてリトライやタイムアウトなどを提供する Resiliency Policy について、その概要をご紹介しました。

また、[前記事](https://qiita.com/YoshiakiOi/items/e95dffc376eaf01c877a)では、２つの Container Apps 間通信で、[**サービス検出のレジリエンシー**](https://learn.microsoft.com/ja-jp/azure/container-apps/service-discovery-resiliency?tabs=bicep)の機能を使って、リトライを組み込む方法と実例を確認しました。

そして、今回は**サーキットブレーカー**の挙動を見ていきます。

# 利用するサンプルについて

[こちら](https://learn.microsoft.com/ja-jp/azure/container-apps/microservices-dapr-service-invoke?pivots=python)の Dapr 用の Python サンプルを Dapr を使わない形に改修して利用します。

もし実際に試されたい場合は、チュートリアルに従って、Git Clone し、この記事の内容に沿って変更いただくことで実施いただけます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/c71692df-2814-cb7c-f19b-55b64a18e84d.png)

そして、今回は以下のような動作をするようにアプリケーションや回復性ポリシーを構成し、サーキットブレーカーの挙動を見ていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/797df368-ff61-53ce-969c-0adae29eb4e9.png)


## Python コードの変更

前記事と同様にcheckout アプリの app.py のコードを下記のように変更します。Dapr を介さずに order-processor アプリ (Container Apps 名を order-processor とする) と通信させ、かつエラーを受け取ったときはそれがわかるように出力を変更しています。

```python: checkout app.py
import json
import time
import logging
import requests
import os

logging.basicConfig(level=logging.INFO)

base_url = 'http://order-processor'
headers = {'content-type': 'application/json'}

while(True):
  for i in range(1, 20):
    order = {'orderId': i}

    try:
      # Invoking a service
      result = requests.post(
          url='%s/orders' % (base_url),
          data=json.dumps(order),
          headers=headers
      )
      result.raise_for_status()
    except requests.exceptions.RequestException as e:
      print('Order passed: ' + json.dumps(order), flush=True)
      print('Error: ' + str(e), flush=True)
    else:
      print('Order passed: ' + json.dumps(order), flush=True)
      print('Response: ' + str(result.json()), flush=True)

    time.sleep(1)
  time.sleep(10)
  ```

また、order-processor 側は、５分毎にレスポンスを 200 と 500 で切り替えるようにコードを修正します。今回の検証では、500 のレスポンスの時にサーキットブレーカーをトリガーするようにレジリエンシーポリシーを構成します。

```python: order-processor app.py
from flask import Flask, request
import json
import datetime

app = Flask(__name__)

print("Hey Im here")

@app.route('/orders', methods=['POST'])
def getOrder():
    random_num = 1
    data = request.json
    print('Order received : ' + json.dumps(data), flush=True)

    dt_now = datetime.datetime.now()
    dt_now_min = dt_now.minute

    #*0分から*4分までは200, *5分から*9分までは500を返す
    if(dt_now_min % 10 <5):
        print(f'Success as dt_now_min = {dt_now_min}', flush=True)
        return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}
    else:
        print(f'Failure as dt_now_min = {dt_now_min}', flush=True)
        return json.dumps({'success': False}), 500, {
        'ContentType': 'application/json'}
   
app.run(port=8001, host="0.0.0.0")
```

前記事と同様に用意された Dockerfile を使って docker build を行い、イメージを Azure Container Registry にプッシュします。そして、それを各 Container Apps に展開します。

# Container Apps でサービス検出のレジリエンシーを構成する

レジリエンシー (回復性) は Azure Portal から構成できます。要求を受信する側の Container Apps のページ (今回は order-processor) で、下記のように構成ができます。今回の例では、500 と 503 が来たら再試行が走るようにし、かつサーキットブレーカーの設定を下記スクリーンショットのように入れてみます。500が5回発生すると、120秒間のサーキットブレーカーが発動し、すべてのノードへのルーティングを停止する設定になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/9b8573da-227a-0ee4-e9c1-d09be5517404.png)

# サービス検出のレジリエンシーの実行例

このサンプルアプリでは、checkout 側のコンソールに出力があるので、その内容を確認します。

checkout から order-processor へのリクエストは、時間に応じて 200 と 500 のレスポンスがあるようになっているはずですが、500 の場合はリトライが走ります。そして 500 が 5回観測されるとサーキットブレーカーが起動するため、checkout アプリ側はサーキットブレーカーから返される 503 を受け取って処理しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/ebde96b2-e9c2-90a0-b34f-4caba165ffd7.png)

上記の例だと、07:04台のリクエストは 200 を受け取っていますが、07:05台のリクエストは 503 のサーキットブレーカーからのレスポンスを受け取っていることが分かります。アプリ側からは最初 500 のレスポンスが入っていたはずですが、自動的にリトライが行われているため、リトライ処理の最終結果である 503 のレスポンスが checkout アプリ側の処理に使われています。

また、order-processor 側のコンソールログからもサーキットブレーカーの起動を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/ca072211-844a-0284-28e5-91bffde9ca44.png)

上記のスクリーンショットを見てみると、最初 order-processor は 500 のレスポンスを行っていましたが、あるタイミングで要求元からのリクエストが届かず処理が行われないようになり、ログも出ないタイミングがあることがわかります。そして、約120秒後に再度 order-processor へリクエストが届き始め、500 のレスポンスを開始しています。

このリクエストの届かない120秒間が発生しているのは、先ほどの回復性ポリシーの設定で、サーキットブレーカーの評価間隔を120秒にしたためです。

# 最後に

＊本稿は、個人の見解に基づいた内容であり、所属する会社の公式見解ではありません。また、いかなる保証を与えるものでもありません。正式な情報は、各製品の販売元にご確認ください。