renue

ARTICLE

Celery + Redis + Azure Container Apps Jobs 本番運用ガイド2026|FastAPI向けバッチ基盤役割分担10原則

公開日: 2026/4/7

FastAPI + Celery + Azure運用は「定期タスクをどこで動かすか」の決断から始まる

FastAPIベースの本番バックエンドで非同期処理・バッチ処理を組む時、2026年の実務者が最初に悩むのは「Celery Beatで回すか、クラウドネイティブなジョブ基盤(Azure Container Apps Jobs、GCP Cloud Run Jobs、AWS Batch等)で回すか」という選択です。両者は併用可能ですが、役割の切り分けを間違えると、スケジューラーの重複、監視の二重化、コスト分散、インシデント対応の混乱を招きます。

本記事では、renueがFastAPI(0.115系)+ Celery(5.4系)+ Redis + Azure App Service + Azure Container Apps Jobsの構成で複数テナント・複数事業を運用する中で確立した「役割分担設計」「SSL付きRedisの設定」「Celery Beat crontab運用」「テナント横断タスク自動検出」「Azure Container Apps Jobsのcron設計」「本番運用の10原則」を、匿名化して共有します。Django + Celeryのガイドとは異なり、FastAPI + SQLAlchemy + マルチテナント + Azure特有の落とし穴に焦点を当てた実装記事です。

関連記事としてマルチテナントFastAPIバックエンド設計ガイドLLMOps実践ガイドMCP完全ガイドも併せてご参照ください。

2つのスケジューラーをどう役割分担するか

Celery Beat(アプリケーション内スケジューラー)

Celery Beatは、Celeryと同じアプリケーションコードベース内でスケジュールを定義し、Redis等のブローカーを経由してCeleryワーカーに定期タスクを投入する方式です。メリットは、(1) コード変更と同じPRで追加・変更が完結、(2) アプリケーションのコンテキスト(モデル、設定、依存ライブラリ)をそのまま使える、(3) 既存のFastAPIアプリとの統合が自然、の3点です。

一方で、(1) Beat自体が単一障害点になりがち、(2) ワーカー再起動時にスケジュール遅延が起きる、(3) 大規模並列実行には向かない、という制約があります。

クラウドネイティブジョブ基盤(Azure Container Apps Jobs等)

Azure Container Apps Jobs、GCP Cloud Run Jobs、AWS Batchなどのクラウドネイティブジョブ基盤は、アプリケーションとは独立したコンテナイメージで、スケジュールもクラウド側のcron設定で管理します。メリットは、(1) アプリケーションとジョブのライフサイクル分離、(2) 失敗時の自動リトライやDead Letter Queueが標準装備、(3) リソース割り当て(CPU/メモリ)をジョブごとに独立制御、(4) 監査ログがクラウドネイティブに残る、の4点です。

一方で、(1) 新規ジョブ追加時にアプリとインフラの両方を更新する必要がある、(2) テナント横断処理や複雑な依存関係のあるタスクはやや扱いづらい、(3) 小さなタスク(数秒で終わる処理)には起動オーバーヘッドが大きい、という制約があります。

renueの実運用での役割分担

renueでは、両者を併用する前提で以下のように役割を分担しています。

  • Celery Beat(アプリ同居):FastAPIアプリケーションと密結合する「データ同期」「API非同期処理」「テナント横断で軽量な定期処理」「リアルタイム応答のトリガー起点となるタスク」。例:外部ATSからの候補者同期、広告KPIの日次同期、スカウトレポート定期取得、Devin等の外部エージェントのステータス巡回、週次AIインサイト生成など。
  • Azure Container Apps Jobs:アプリとは独立させたい「重いバッチ」「長時間処理」「大量トランザクションを扱うETL」「PMOタスク自動化」「オンボーディング・フィードバック系の時間指定配信」「HERP等の候補者同期の複数時間帯バージョン」「月次・四半期集計」。

この分担の判断軸は「(1) FastAPIアプリのメインコンテナと共有したいコンテキストがあるか」「(2) 失敗時の独立リトライが必要か」「(3) リソース割当を独立させたいか」の3点です。

Celery Beat運用の実装パターン

Redis接続のSSL対応(Azure/AWS/GCP共通)

マネージドRedis(Azure Cache for Redis、AWS ElastiCache、GCP Memorystore等)は、クラスタ設定やセキュリティポリシーでSSL接続を要求するケースがあります。renueでは環境変数 `REDIS_SSL` のboolean判定でSSLスキームを動的に切り替えています。

  • SSL無効時:`redis://:{password}@{host}:{port}/{db}`(パスワードありの場合)または `redis://{host}:{port}/{db}`
  • SSL有効時:`rediss://:{password}@{host}:{port}/{db}?ssl_cert_reqs=CERT_NONE` を使用。`rediss://` プロトコルで接続し、SSL証明書検証はCERT_NONE(検証スキップ)に設定することが、Azure/GCP等のマネージドRedisで安定動作するパターンです。

ここで重要なのは、`ssl_cert_reqs` パラメータを省略するとCelery 5.x系では接続が失敗することです。これはCeleryの既知の挙動で、SSL接続時は明示指定が必要です。ローカル開発ではSSLを無効にし、本番環境でのみSSLを有効化する設定分離が推奨されます。

マルチテナントのタスク自動検出

マルチテナントSaaSでCeleryを使う場合、各テナントの `tasks.py` や `tasks/` ディレクトリを起動時に自動検出してincludeする仕組みが有用です。renueでは以下のロジックを採用しています。

  1. `tenants/` ディレクトリを走査し、各サブディレクトリ(テナントID)を取得
  2. そのテナントに `tasks.py` または `tasks/` があるか判定
  3. ある場合、`src.tenants.{tenant_id}.tasks` をCelery includeリストに自動追加

これにより、新規テナント追加時にはディレクトリを作ってタスクファイルを配置するだけでCeleryが自動検出します。`celery_app.py` への手動配線追加は不要です。この仕組みは、大量のテナントを運用するSaaSにおいて、開発速度と運用ミス防止の両方に効きます。

Beat Schedule(crontab)の設計

Celery Beatのスケジュール定義には `celery.schedules.crontab` を使います。タイムゾーンは `app.conf.timezone = "Asia/Tokyo"` で明示指定することが重要です。これを忘れるとUTCで動作し、朝9:00のつもりが深夜18:00(前日)にタスクが走る事故が起きます。

renueの実運用では、以下のようなスケジュール定義パターンを使っています。

  • 毎日決まった時刻:`crontab(hour=9, minute=0)` — 平日朝の候補者同期等
  • 毎週決まった曜日:`crontab(hour=9, minute=0, day_of_week="mon")` — 週次プレスリリース同期等
  • 平日のみ:`crontab(hour=11, minute=0, day_of_week="mon-fri")` — 平日専用レポート系
  • 10分ごと:`crontab(minute="*/10")` — 外部エージェントのステータス巡回など
  • 毎月1日:`crontab(hour=8, minute=30, day_of_month=1)` — 月次集計・貢献度配布など

スケジュール定義はPythonの辞書構造で管理するため、バージョン管理・レビュー・A/Bテストが容易です。一時停止したいジョブには `# NOTE: 一時停止中(YYYY-MM-DD)` のコメントを残し、理由と再開予定を明記する運用にすると、後から判断根拠を追えます。

Celeryタスクのログ記録(signal hook)

Celeryには `task_prerun`, `task_postrun`, `task_failure`, `task_retry` などのシグナルハンドラが用意されており、これらを使ってタスク実行ログをDBやObservabilityツールに記録できます。renueでは、タスクごとに「開始時刻」「終了時刻」「ステータス(SUCCESS/FAILURE/RETRY)」「エラー内容」「実行引数」をDBに記録する独自のロガーを実装し、後から運用ダッシュボードで可視化しています。

このロガーは `celery_app.py` の import段階で1行だけ `import src.shared.lib.celery_task_logger # noqa: F401` の形式で読み込ませることで、シグナルハンドラが自動登録される設計になっています。副作用import(side-effect import)のパターンですが、Celeryのシグナル登録では標準的な実装方法です。

Azure Container Apps Jobs運用の実装パターン

自動デプロイとTerraform同期

renueでは、Azure Container Apps Jobsの構成をTerraformで管理し、GitHub Actionsで自動デプロイする運用を取っています。mainブランチへのPRマージで、(1) 変更グループを検知 → (2) 必要なジョブイメージのみACR build → (3) Terraform applyで構成反映、という流れが自動実行されます。

この自動デプロイのトリガー対象は `jobs/app/**`、`jobs/infra/container_apps/jobs/**`、`jobs/infra/container_apps/terraform/**`、`src/**`、`requirements.lock`等に絞り込みます。無関係な変更ではジョブ再ビルドが発生しないよう、パスフィルタリングを厳密に設定するのが重要です。

ジョブごとの独立Dockerfile

Azure Container Apps Jobsは、ジョブごとに独立したDockerfileとイメージを持ちます。renueでは `jobs/app/{job_name}/Dockerfile` の構造で、各ジョブに必要な最小依存ライブラリだけをインストールする設計にしています。

この分離により、(1) ジョブ間の依存競合を回避、(2) イメージサイズを最小化、(3) 特定ジョブだけ別バージョンのライブラリを使いたい要件に柔軟対応、が可能になります。ただしjob数が増えるとDockerfileの重複が増えるため、共通ベースイメージを別途ビルドして継承する工夫が必要です。

ジョブ運用の規律

renueでは以下の運用ルールを厳守しています。

  • `az containerapp job update` の直接実行は緊急時のみ:通常運用ではGitHub Actions経由のTerraform applyに統一。手動更新はdriftの原因になります。
  • 緊急で直接更新した場合、必ずdriftを解消:同じ変更を `jobs/infra/container_apps/jobs/**/*.yaml` と Terraform stateに反映します。
  • Azure APIを読むジョブはManaged Identity必須:`DefaultAzureCredential` を使うジョブには Job Identity を `SystemAssigned` or `UserAssigned` に設定。
  • 必要RBACを最小権限で付与:対象RGの`Reader`、Log Analytics Workspaceの`Log Analytics Reader` / `Monitoring Reader` 等、ジョブ別に必要最小限のロールを付与。

主要ジョブの種類

renueで本番稼働しているAzure Container Apps Jobsの代表例(機能分類のみ):

  • PMO daily:朝のPMOタスク・課題自動管理(8:00 JST)
  • 週次サマリー配信:日曜朝の組織内週次メッセージ生成・配信
  • ランチペアリング:金曜の社員ランチペアリング通知
  • 従業員フィードバック:金曜夜のフィードバック配信
  • 候補者同期(ATS連携):朝・昼・夕・深夜の4時間帯で候補者データ同期
  • 求人同期(ATS連携):昼と深夜の2時間帯で求人データ同期
  • 面接メモリマインダー:毎朝9:00 JST
  • オンボーディング:平日昼のプレゼンテーション配信
  • 貢献度配布:毎月1日朝の貢献度自動配布

各ジョブはcron(UTC時間で設定)で時刻指定され、失敗時はAzure Container Apps側のリトライ機構で自動再実行されます。重要なジョブは別途Slack通知フックで成功/失敗を記録します。

Celery Beat vs Azure Container Apps Jobsの比較早見表

Celery BeatAzure Container Apps Jobs
起動オーバーヘッド小(ワーカー常駐)中〜大(コンテナ起動時間)
リソース分離他タスクとワーカー共有ジョブごとに独立
失敗時のリトライCeleryの設定に依存クラウドネイティブで標準装備
監査ログ自前実装Azure Monitor等で標準収集
新規追加のしやすさコード変更のみコード + インフラ定義が必要
小さなタスク(数秒)の適性適しているオーバーヘッド大
長時間処理・バッチワーカー占有リスクあり適している
テナント横断処理アプリと同じコンテキストで容易可能だがやや面倒
アプリとのライフサイクル結合度密結合疎結合
デプロイ方式アプリデプロイと同じTerraform + GitHub Actions等

renueの本番運用10原則

原則1:ジョブの性質でCelery BeatとAzure Jobsを使い分ける

「小さい・頻度高い・アプリと密結合」→ Celery Beat、「重い・頻度低い・独立リソース必要」→ Azure Container Apps Jobs、という線引きを最初に決めます。両方に散らばらせると監視と運用が二重化します。

原則2:Redis接続はSSL対応を環境変数で切替可能に設計する

`REDIS_SSL` boolean で `redis://` と `rediss://?ssl_cert_reqs=CERT_NONE` を切り替える設計を初日から入れます。後付けは混乱の元です。

原則3:Celery Beatのタイムゾーンは `Asia/Tokyo` を明示する

デフォルトのUTCで動かすと、スケジュール定義と実運用時刻がズレて事故になります。`app.conf.timezone = "Asia/Tokyo"` を必ず設定します。

原則4:テナントタスクは自動検出の仕組みで新規追加を自動化する

`find_tenant_tasks()` のような自動検出関数を作り、テナント追加時にcelery_app.pyを触らなくても済むようにします。

原則5:Celeryタスクのログはシグナルハンドラで統一記録

`task_prerun`, `task_postrun`, `task_failure`, `task_retry` をフックしてDBやObservabilityに統一記録します。各タスクに個別のログ処理を書くと漏れが出ます。

原則6:Azure Container Apps JobsはTerraform + GitHub Actionsで自動化

`az containerapp job update` の手動実行は緊急時のみとし、通常はmainブランチのPRマージで自動デプロイされる仕組みにします。手動運用が日常化するとdriftが必ず発生します。

原則7:Azure APIを叩くジョブはManaged Identity必須

API Keyや接続文字列を環境変数で渡すのは最終手段。Managed Identity + 最小権限RBACが標準です。

原則8:ジョブ別に最小依存のDockerfileを持つ

モノリシックな共通イメージではなく、ジョブごとにDockerfileを分離し、必要最小限の依存だけを含めます。イメージサイズとビルド時間の両面で効きます。

原則9:変更グループ検知でビルド対象を絞る

GitHub Actionsのワークフローで `paths` フィルタを設定し、変更のないジョブは再ビルドしません。開発速度とACRコスト両面で効きます。

原則10:重要ジョブはSlack通知で成功/失敗を可視化

クラウドネイティブのログだけでは気づきにくい失敗(結果が0件だけど例外は出ていない等)をキャッチするために、重要ジョブはSlackに成功/失敗サマリーを通知します。

本番運用で襲ってくる8つの落とし穴

落とし穴1:Beatのタイムゾーン未設定

UTCのままBeatを動かし、9:00 JSTのつもりが18:00 UTCに動いて翌日処理になる事故。初日から `Asia/Tokyo` 明示が必須。

落とし穴2:Redis SSLの `ssl_cert_reqs` 省略

Azure Redisで接続が失敗する典型原因。`CERT_NONE` を明示すると安定します。

落とし穴3:Beatの二重起動

Celery Beatは1プロセスだけが動いていることが前提です。SupervisordやContainer Apps Replicaで複数プロセス走らせると、同じタスクが複数回実行されます。Beat専用の単一インスタンスを明示します。

落とし穴4:ワーカーとBeatの同居起動問題

Supervisordで同じコンテナ内にGunicorn + Celery worker + Celery beatを同居起動する構成は便利ですが、Beatが2つ以上立ち上がる条件では事故が起きます。App Serviceで動かす場合は常に1インスタンスで固定します。

落とし穴5:Azure Container Apps Jobsのリソース割当不足

メモリ不足でOOMkillされて無音で失敗するケース。ジョブごとに適切なCPU/メモリを設定し、監視アラートを組み込みます。

落とし穴6:Managed Identity未設定でAzure API読めない

`DefaultAzureCredential` を使うのに Job Identity が未設定だと権限エラー。最初から `SystemAssigned` を入れます。

落とし穴7:手動 `az containerapp job update` でのdrift

緊急で手動更新した内容がTerraform stateに反映されず、次のdeployで上書き戻りする事故。手動更新後は必ずTerraform stateに同期します。

落とし穴8:ジョブの重複実行(beatとcron両方で登録)

Celery Beatに登録したタスクと、Azure Container Apps Jobsのcronに登録したジョブが重複して同じ処理を実行する事故。役割分担をコードレビューで確認します。

FAQ

Q1. Celery Beatは本当に必要ですか?

クラウドネイティブジョブ基盤で完全代替できるなら不要です。ただし、アプリケーションと密結合する軽量タスクや、テナント横断処理、外部APIの巡回系タスクは、Celery Beatの方が実装が簡潔です。役割分担での併用が現実解です。

Q2. Redis以外のブローカーは使えますか?

RabbitMQ、Azure Service Bus、AWS SQSなども使えます。Azure Service Busは堅牢ですがresult backendに使えないため、backendはRedisまたはDBを別途用意する必要があります。renueではRedisに統一しています。

Q3. Celery workerとBeatを同じコンテナで動かしていいですか?

可能ですが、Beatは1プロセスだけが動いていることが前提です。複数レプリカでBeatが重複起動しないよう、Supervisord設定でBeatを1プロセスに固定するか、専用のBeat-onlyコンテナを別途立てるのが安全です。

Q4. テナント別のタスク実行優先度を分けるには?

Celeryのキューを分けます。`@app.task(queue="tenant_a_high")` のように重要テナント専用キューを作り、対応するワーカーを別プロセスで起動します。これにより、1テナントの重いバッチが他テナントのレスポンスを遅延させる事故を防げます。

Q5. Azure Container Apps JobsとGCP Cloud Run Jobsの違いは?

機能的には似ています。renueはAzureメイン+一部GCP Cloud Run Jobsも併用しています(議事録処理・動画処理等の特定ワークロード)。差分は、(1) リージョンの地理的配置、(2) IAM統合の実装、(3) 課金モデルの細かな違い、の3点です。既存インフラと揃える判断が無難です。

Q6. Celery Beatのスケジュール変更は無停止でできますか?

PR → マージ → デプロイで変更は反映されますが、Beatプロセスの再起動が必要な場合があります。重要なジョブは「旧スケジュールで1回動く → 再起動 → 新スケジュールで動く」の挙動を想定して変更します。

Q7. ジョブ失敗時の通知はどう設計しますか?

(1) Celery側はsignal hookでSlack通知、(2) Azure Container Apps Jobs側はLog Analytics + Alertルールでトリガー通知、の二段構えが推奨です。重要ジョブは成功時もサマリー通知を出すと、「無音で失敗していた」に気づけます。

Q8. Celery Beatのスケジュール情報はどこに保存されますか?

デフォルトでは `celerybeat-schedule` というファイルにSQLiteで保存されます。ただしコンテナ化する場合はこのファイルが永続化されないため、Redis Backedの`RedBeatScheduler`や DatabaseSchedulerを使う構成が推奨です。本番ではファイルベースは避けます。

まとめ:Celery BeatとAzure Jobsは「役割分担」で共存させる

FastAPI + Celery + Redis + Azure構成の本番運用は、Celery BeatとAzure Container Apps Jobsのどちらか一方に寄せるのではなく、「アプリと密結合する軽量タスクはBeat、重いバッチ・独立リソース必要なジョブはAzure Jobs」という役割分担で共存させるのが現実解です。SSL対応Redis・テナントタスク自動検出・タイムゾーン明示・Managed Identity・Terraform自動デプロイ・Slack通知の6点を初日から組み込むことで、長期安定運用が可能になります。

renueは、複数のAIエージェント事業を内製運用する中で、この設計パターンを確立してきました。「Celery BeatとAzure Jobsの役割分担を設計したい」「既存のBeat運用で事故が起きていて改善したい」「Azure Container Apps JobsのTerraform化を相談したい」などの具体的ご相談をお受けしています。

renueにCelery + Azure本番運用設計の相談をする

renueは、FastAPI + Celery + Redis + Azure App Service + Azure Container Apps Jobs構成を複数事業で本番運用する実体験から、非同期処理・バッチジョブ・スケジューラ設計の伴走支援を提供しています。設計レビューから本番運用の改善まで、実装粒度でご相談をお受けしています。

無料相談はこちら

関連記事