dapr を使った React / Express / Flask のマイクロサービスアプリケーションを Azure Container Apps にデプロイする

# はじめに
本記事は、`Microsoft Azure Tech` の `Qiita` アドベントカレンダー の記事です。昔から存在は知っていたけどなんとなく難しそうなイメージがあり、なかなか手を出せていなかった `dapr` ですが、今回はアドベントカレンダーということでしたので、思い切って勉強と開発をしてみました。`dapr` ということでマイクロサービスごとは疎結合で連携されているため、`Python` と `JavaScript` という技術スタックが異なるコンテナーが動くマイクロサービスを構築してみました。構築後は、`Azure Container Apps` にデプロイするところまでを記事にまとめています。本記事では、まず初めに `dapr` や `Azure Container Apps` などの説明をした後、構築したマイクロサービスのデモと説明をします。そして最後に、マイクロサービスのデプロイ手順について紹介します。

# マイクロサービスアーキテクチャとは
マイクロサービスアーキテクチャとは、大規模なアプリケーションを小さな独立したサービスに分割した構成のことです。マイクロサービスアーキテクチャを使用することで、システム全体の機能のコア領域を個別に開発、アップグレード、バージョン管理、スケーリングできるようになります。これにより、迅速な開発と保守が可能で、異なる技術スタックを利用できるため、効率的で柔軟性の高いアプリケーション開発が実現されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/8d7c1624-bb93-31df-37b2-7bf6401a8c27.png)

https://learn.microsoft.com/ja-jp/azure/service-fabric/service-fabric-overview-microservices#comparison-between-application-development-approaches

上の図は、モノリシックなアプリケーションとマイクロサービスアプリケーションの違いを概念図として比較したものになります。モノリシックなアプリケーションは、`Web` 3 層構造などの、`Web`、ビジネス、データなどの、機能層で分割された構造となっています。クラウドの導入が進み、従来のこうしたモノリシックなアプリケーションは、マイクロサービスアプリケーションアーキテクチャに向かう傾向にあります。マイクロサービスアプリケーションは、個々が小規模な個別サービスなので、あらゆる言語、あらゆるフレームワーク、バージョン、デプロイ、拡張、が可能で自由度が高いですが、複雑性が増すという課題もあります。次の見出しで紹介する `dapr` はこうした分散システムの複雑性を軽減することが可能です。

# dapr とは
`dapr`（`Distributed Application Runtime`）は、分散システムの開発を容易にするためのオープンソースのランタイムフレームワークです。異なるサービスのコミュニケーション、状態管理、エベント処理などの共通機能を抽象化し、開発者がアプリケーションを構築する際の複雑さを軽減します。様々なプログラミング言語やクラウドプラットフォームで利用可能であり、柔軟で拡張可能な分散システムの構築が可能です。`dapr` では、マイクロサービスアプリケーションを構成要素と呼ばれる独立した `API` に構築するための仕組みが体系化されています。`dapr` の機能について、以下で簡単にまとめます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/aa4dced5-e410-844d-f9f6-c53af757fa2e.png)

- `Service-to-service Invocation`
    - サービスの呼び出しのためのエンドポイントが作成され、他のサービスとの連携を容易に実現
- `State Management`
    - テートフルなサービスをに対応し、状態データを保存可能
- `Publish and Subscribe`
    - サービス同士の `Publish-Subscribe` メッセージングを実現可能
- `Input/Output Bindings`
    - 外部のイベントによるアプリのトリガーと、入出力インターフェイスが利用可能
- `Actors`
    - `Actor` パターンにより、サービス同士の連携が可能
- `Secrets`
    - 機密データを格納したストレージに接続してアクセス可能
- `Configuration`
    - アプリケーションに関する設定情報が登録可能
- `Distributed Lock`
    - ロックをしてリソースへの排他的なアクセスが可能
- `Workflows`
    - ビジネスロジックと統合を簡単に記述し、マイクロサービスの調整が可能
- `Cryptography`
    - 暗号化キーをアプリケーションに公開することなく、暗号化操作を実行可能

https://docs.dapr.io/concepts/overview/

# Azure Container Apps とは
`Azure Container Apps` は、コンテナー化されたアプリケーションを実行するためのサーバーレスプラットフォームです。`Azure Container Apps` を利用することで、保守するインフラストラクチャが少なくなるため、運用コストを削減できます。`Azure Container Apps` は 1 つ以上のコンテナーで作られたコンテナーアプリで構成されるため、個々がマイクロサービスとしての役割を果たします。

`Azure Container Apps` の一般的な用途には以下になります。
- `API` エンドポイントのデプロイ
- バックグラウンド処理ジョブのホスティング
- イベント駆動型処理の処理
- マイクロサービスの実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/f52bd55f-ddb0-2f71-57db-94946925375b.png)

`Azure Container Apps` は、`Container Apps Environment` と呼ばれるセキュリティ境界内にデプロイされます。また、`Azure Container Apps` は、スケーリング、バージョン管理、アップグレード、サービス検出、ネイティブの `dapr` 統合、といったマイクロサービスをデプロイする基盤を提供しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/b2070d48-2dc5-8d39-48bc-8e2439cf2681.png)

https://learn.microsoft.com/ja-jp/azure/container-apps/overview

# サンプルアプリの紹介・デモ
サンプルアプリのデモ動画を `Twitter` に載せました。このサンプルアプリは、りんご・ばななの在庫個数をリアルタイムで表示する `Web` アプリになっています。サンプルアプリの仕様として、りんご・ばななの在庫はそれぞれ 1000 個ずつあり、りんごは 1 秒ごとに 1 個 から 10 個の間の乱数、ばななは 2 秒ごとに 1 個 から 20 個の間の乱数の注文が入るようにするため、りんご・ばななの注文サービスは定期的に `dapr` に向けて `POST` 通信をするようにしています。このアプリの全体像を伝えるために、ブラウザで動く `Web` アプリだけを録画するよりも、他のマイクロサービスで出力されるログを同時に確認できたほうが、サービス全体の動きを理解しやすいと思うので、`Visual Studio Code` の画面でブラウザと複数のコンソール画面をまとめて 1 画面に録画しました。左上が `React` による在庫個数を表示したフロント画面であり、中央上が `Worker1` によるりんごの注文ログ、右上が `Worker2` によるばななの注文ログを表示しており、下が `Service` によるりんごとばななの受注ログ・在庫個数ログになります。

https://x.com/y__saito/status/1741105993831104828?s=20

サンプルアプリのソースコードは以下の `GitHub` リポジトリに公開しています。

https://github.com/yus04/aca-microservices-architecture

サンプルアプリは以下のソースコードを参考にして開発しました。

https://github.com/Azure-Samples/svc-invoke-dapr-python/tree/main


# アーキテクチャ
構築したマイクロサービスアプリケーションのアーキテクチャ図は以下になります。`Azure Container Apps Environments` 内に 5 つのマイクロサービスをデプロイし、それぞれに対して `Azure Container Apps` でホスティングしています。マイクロサービスはそれぞれ `Docker` でコンテナ化されており、その横にサイドカーとして動く `dapr` が配置されています。`dapr` 間は `gRPC` で通信しており、`dapr` とサーバー間は `HTTP` もしくは `gRPC` で通信をしています。それぞれのサービスで公開しているポート番号や通信経路は以下の図にまとめています。一方で今回はサンプルなので、`Vnet` による閉域化などの細かい設計はしていません。また、イングレスについても図にのせていませんが、イングレスエンドポイントは常にポート 443 で公開されています。

:::note info
`gRPC` は、`HTTP/2` によって高速化された通信が可能で、クライアントとサーバー間での双方向なストリーミング通信ができます。
:::

ローカルでのアーキテクチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/2507c31c-4d4f-a4fc-cd08-f720c9aa8b47.png)

Azure でのアーキテクチャ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/1de807ab-5918-4fae-a99b-07ea6c9bab43.png)

ローカルと `Azure` では、起動しているアプリケーションは構造は同じですが、公開ポートなど若干異なる点があります。また、マイクロサービスごとの名前、フレームワークと言語、役割についてそれぞれ以下の表にまとめます。

|マイクロサービス名|マイクロサービスのフレームワークと言語|役割|
|---|---|---|
|Web(front)|React(JavaScript)|りんご・ばななの在庫個数の表示画面|
|Web(backend)|Express(Node.js)|りんご・ばななの在庫個数伝達サービス|
|Service|Flask(Python)|りんご・ばななの受注と在庫管理サービス|
|Worker1|なし(Python)|りんごの注文サービス|
|Worker2|なし(Python)|ばななの注文サービス|

また、果物の個数が更新されるまでのデータフローは以下になります。

1. `Worker1` と `Worker2` が `dapr` に向けて果物注文のための `POST` リクエストを行います
1. `dapr` 間で `gRPC Request` が行われ、`dapr-Service` 間で `HTTP` 通信が行われます
1. `Service` で在庫数を変更した後、`Service-dapr` 間で `HTTP` 通信が行われます
1. `dapr` 間で `gRPC` 通信が行われた後、`Web backend` へ `HTTP` 通信が行われます
1. `Web backend` と `Web frontend` は `WebSocket` で繋がっており、`HTTP` 通信が行われます
1. `Web frontend` で果物の在庫個数が更新されます

# ローカル実行
サンプルアプリの紹介・デモでは、ローカル実行した様子を録画したものを載せています。`dapr run` を実行して `dapr` を使用するには、どうやら以下の 3 パターン方法しかないようだったので、ローカルで実行するときはアプリと `dapr` はプロセスとして起動し、コンテナーは利用しませんでした。当然、`Azure Container Apps` にデプロイする際には `Docker` コンテナ化する必要がありますが。

- アプリと `dapr` をプロセスとして起動する方法
- アプリがプロセスで `dapr` を `Docker` コンテナとして起動する方法
- アプリと `dapr` を 1 つの `Docker` コンテナ内で起動する方法

ローカル実行をするためのコマンドは [サンプルアプリを公開している GitHub リポジトリ](https://github.com/yus04/aca-microservices-architecture) の `README.md` にまとめているので必要であればそちらをご確認ください。

https://docs.dapr.io/operations/hosting/self-hosted/self-hosted-with-docker/

# デプロイ
ローカルで実行したマイクロサービスアプリケーションのデプロイは、以下の公式ドキュメントを参考にして `Azure CLI` にて実行しました。コマンドの詳細についてはローカル実行と同様に、 [サンプルアプリを公開している GitHub リポジトリ](https://github.com/yus04/aca-microservices-architecture) の `README.md` にまとめているので必要であればそちらをご確認ください。

## デプロイ結果
### Webfrontend のフロント画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/960a84eb-052f-ee1a-c4dc-72447f16220c.png)

### Webbackend のログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/be86552f-882f-ba6c-53f1-a75c9e24632e.png)

### service のログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/64be378c-6f6d-dfdb-586f-5520142b0f56.png)

### worker1 のログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/1d482d24-aef6-19a7-2628-758dc10f1dc9.png)

### worker2 のログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/f7753011-557a-e511-5165-999f9d400ab3.png)

無事、デプロイができました。



# おわりに
初めて、`dapr` に挑戦してみて戸惑う点も多かったですが、慣れてくると確実に便利な技術だと感じました。今回のマイクロサービスアーキテクチャでは `dapr` コンポーネントは扱わず、サービス呼び出し機能しか扱いませんでしたが、`PubSub` や `Secret` や `Bind` など、他にも `dapr` には数多くの機能があるので、試してみようと思います。