Azure Container Apps の Resiliency Policy ② - サービス検出のレジリエンシーでのリトライの構成と実行例

# 本記事について

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

本稿では、２つの 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/02993e18-6088-ea13-53e2-4f7a51a08664.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 と 429 をレスポンスで返すようにコードを修正します。今回の検証では、429 のレスポンスの時にリトライを実行するようにレジリエンシーポリシーを構成します。

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

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)

    random_num = random.randint(0, 1)
    if(random_num):
        print(f'Success as random = {random_num}', flush=True)
        return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}
    else:
        print(f'Failure as random = {random_num}', flush=True)
        return json.dumps({'success': False}), 429, {
        'ContentType': 'application/json'}
   
app.run(port=8001, host="0.0.0.0")
```
checkout と order-processor をそれぞれ、チュートリアルのフォルダーに入っている Dockerfile を使ってコンテナー化し、同じ Container Apps 環境のコンテナーアプリとしてデプロイします。(checkout は Ingress なし、order-processor は 8001ポートで受信する Ingress ありで構成します。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/b0e35045-e951-003a-7bae-0c476ee8a78a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/28e590d5-02d6-6047-777b-3caf7c1553bc.png)

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

レジリエンシー (回復性) は Azure Portal から構成できます。要求を受信する側の Container Apps のページ (今回は order-processor) で、下記のように構成ができます。今回の例では、429 が来たら再試行が走るようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/6de3468b-d588-1b2e-4631-98fa46a9a9f9.png)

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

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

checkout から order-processor へのリクエストは、ランダムで 200 と 429 のレスポンスがあるようになっているはずですが、自動的に 429 の場合はリトライが走り、すべて 200 で返ってきています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/7202c13f-c278-7eeb-ab98-304feff8aadf.png)

ここで回復性を無効化すると、下記のようなレスポンスに変わります。アプリケーションコード側では再試行を設定していないので、ランダムに来る 429 のレスポンスをそのまま受け取っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/a5dc6970-c847-5f9f-ece2-9fa4e028c56c.png)

メトリックで実際にリトライ処理が行われているかを確認することもできます。Resiliency Requests Retries というメトリックを見ると、Container Apps のサービス側で実行されたリトライ数を見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/f6cb99d9-86fb-25e4-9139-008afd1b3d78.png)

また、Log Analytics ワークスペースにシステムログを記録している場合は、リトライの実行をログでも見ることができます。

```Kusto: KQL クエリ例
ContainerAppSystemLogs
| where EventSource == "Resiliency"
| sort by TimeGenerated desc
| project TimeGenerated, ContainerAppName, EnvironmentName, EventSource, Type, Reason, Count, Log
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276225/11adbf0e-7710-9a8c-47e7-72c8c829fa94.png)


# 最後に

[次稿](https://qiita.com/YoshiakiOi/items/5c6b4a7edb9956ab2ce1)では、同ポリシーにおけるサーキットブレーカーの構成や挙動について見ていきます。

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