はじめに
個人開発している Web アプリにおいて、Docker コンテナを用いたバッチジョブを構築する必要がありました。色々検討した結果、Service Bus トリガーを用いた Azure Container Apps Jobs で構築しましたので、結果を共有します。
Azure クラウドネイティブかつ、超低コスト(月額10円以下)で Docker コンテナバッチジョブを実行したい場合に、有効な方法になると思います。
アーキテクチャ
全体像は以下の画像の通りです。
ポイントとしては以下の通りです。
- 費用面
- コンテナイメージは Docker Hub にプッシュします。1リポジトリであれば無料です。
Azure Container Registry だと最低でも日額25円ほどかかり、月750~800円ほどかかります。 - メッセージトリガーとして、Serivce Bus の Basic プランを使用します。
機能がかなり限定されるものの、ランニングコストは不要で、100万リクエストあたり7円という、低価格で利用できます。 - コンテナの実行環境として、Azure Container Apps Jobsを使用します。
Service Bus にキューイングされたメッセージ数に応じてレプリカの数を調整しますので、メッセージが1通もない時はレプリカ数を0にすることができます。アイドル時のランニングコストをなくし、ほぼ従量課金プランの無料利用枠に収める運用が可能です。
- コンテナイメージは Docker Hub にプッシュします。1リポジトリであれば無料です。
- セキュリティ面
- Service Bus と Container Apps Jobs 間の認証はシステム割り当てマネージドIDによるRBAC (ロールベースアクセス制御) を用いることで、接続文字列が漏洩する可能性を排除します。
- Docker Hub との通信については、ユーザーIDと Personal access token を使って認証を行います。Personal access token は Key Vault でシークレットとして安全に管理します。 Container Apps Jobs からはシステム割り当てマネージドIDによる RBAC で認証し、シークレットにアクセスします。
- オプションとして、サービスエンドポイント や プライベートエンドポイント を用いることで、Azure サービス間の通信を閉域化することも可能です。ただし、その場合は Service Bus のサービスレベルを引き上げる必要があります。
- その他
- ログ記録用として、Application Insights と Log Analytics ワークスペースを構築します。
- Docker コンテナのベースイメージに、.NET 8.0 ベースの Azure Function ランタイムを使用します。
起動状態の管理などに必要なため、Azure Functions ランタイムが内部で使う既定のストレージとして、ストレージアカウントを1つ用意します。もちろん、RBACによる認証を行ってアクセスします。
環境構築
Bicep (IaC) で記載しています。以下の GitHub リポジトリにて公開しておりますので、ご参考ください。
詳しい構築方法は README.md をご覧ください。
https://github.com/eXpresser-UXM/azure-container-apps-service-bus-trigger
考察
1. Pub/Sub 方式の非同期バッチの場合は、Container Apps でなく Container Apps Jobs を使う
最初は Conatiner Apps で構築していたのですが、処理していない時でも常にレプリカが立ち上がってしまい、課金額が跳ね上がってしまいました。
Container Apps Jobs で構築し、カスタムスケールルールを設定することで、キューにメッセージが入っていない時は起動しないという設定が適切であると理解しました。
また、こちらのブログ記事 によると、Azure Container Apps が Azure Functions にネイティブサポートするようになったという記載がありますので、もっと適切な方法があるのではないかと思っています。ざっくり、「KEDAでもスケールルールを書かなきゃいけないところが、Functions のトリガー定義だけでスケーリングしてくれるようになるのかな?」という理解で、色々試してみましたが、うまく動作せず断念しました。
2. Docker Hub PAT を Key Vault 参照で使うするためにはユーザー割り当てマネージドIDを使う
Docker Hub の Personal Access Token はシークレットなので、漏洩リスクがあります。
Container Apps Jobs にもシークレットの保存領域はありますが、なるべく Key Vault のシークレットに保管し、マネージドIDで取得するような形が適切かと思います。
その時問題になるのが、いわゆる「卵が先か鶏が先か」問題。
システム割り当てマネージドIDは Conatiner Apps Jobs リソースを作ってから初めてできる一方で、リソースを作るタイミングでリポジトリにアクセスできないと、エラーになってリソースを作ることができません。
どうしたもんかなーと悩んでいた所、以下の stack overflow の記事がヒットしました。
How do I configure my Bicep scripts to allow a container app to pull an image from an ACR using managed identity across subscriptions
事前にユーザー割り当てマネージドIDを作って IAM ロールを付与しておき、Container Apps Jobs 側でユーザー割り当てマネージドIDを使うようにすれば解決できます。
リソースを作る段階で Key Vault シークレットにアクセスしたいといったケースにおいて使える Tips で、勉強になりました。
3. インスタンスを起動するときにイメージがPullされる
プログラムの変更が伴う際に、どのタイミングで最新化されるのか疑問だったのですが、どうやらコンテナのレプリカが変わり、インスタンスを起動するときに都度イメージをPullする仕組みになっているようでした。
なので、プログラムを変更し、docker push でリポジトリに上げてしまえば最新のソースが動くようになるようです。
現在は Azure DevOps Pipelines で docker build と docker push を行うようにデプロイパイプラインを組んで動かしています。
結局いくら掛かったの?
実際に自分が作っているWebサービスに構築の上、料金がいくらほど掛かったか共有します。
10月末ごろ本番環境に構築して、2週間ほど運用した結果、394リクエストで 1.68円 という金額になっています。今後進めると大体月額 4~5円ぐらいかと思います。めっちゃ安い。
実行される回数や起動時間が限られていれば、かなり使い物になるものと感じています。
まとめ
この記事では、Docker コンテナを用いたバッチジョブを構築するにあたって、Azure 上でなるべく安く運用することを目標に、アーキテクチャを検討してみました。
結果として、Azure Container Apps Jobs を活用し、スケールルールで Service Bus キュー内のメッセージに応じてレプリカ数を調整すればよいということが分かりました。
実際に2週間ほど運用した結果、2円弱の超低コストで運用することができました。
普通は Functions で組むのがベターですが、OSに依存するような特殊なバッチを作りこむ必要がある場合に、有効な方法だと思います。
最後までお読みいただきありがとうございました。
宣伝
趣味の鉄道写真をベースして、自身が撮影した写真を掲載しているホームページを運営しています。
Urban eXpress Museum
趣味活動と自己啓発を兼ねて、鉄道の配線略図作成に特化したWebアプリを開発・運営しています。
配線略図エディタ

