株式会社renue
renueについて
renueは、AIで業務を実装する会社です
AIコンサルティングから図面AI・広告運用エージェント・コールセンターAIまで、実際に動いているサービスをご覧いただけます。renueが何をしている会社か、まずはサービス一覧からご確認ください。
FastAPI + Celery + Azure運用は「定期タスクをどこで動かすか」の決断から始まる
FastAPIベースの本番バックエンドで非同期処理・バッチ処理を組む時、2026年の実務者が最初に悩むのは「Celery Beatで回すか、クラウドネイティブなジョブ基盤(Azure Container Apps Jobs、Google Cloud Run Jobs、AWS Batch等)で回すか」という選択です。両者は併用可能ですが、役割の切り分けを間違えると、スケジューラーの重複、監視の二重化、コスト分散、インシデント対応の混乱を招きます。
本記事では、FastAPI + Celery + Redis にクラウドのアプリ基盤とジョブ基盤を組み合わせた構成で整理した「役割分担設計」「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を1つに保つ必要があり、Beat停止時は投入が止まる、(2) 処理できるワーカーが停止している間は実行が遅れる、(3) 並列実行に必要なワーカー数とブローカー容量を別途設計する必要がある、という点に注意します。Beatはタスクの投入を担当し、実行側のCeleryワーカーは複数台・複数プロセスに拡張できます。
クラウドネイティブジョブ基盤(Azure Container Apps Jobs等)
Azure Container Apps Jobs、Google Cloud Run Jobs、AWS Batchなどのジョブ基盤では、アプリケーションから実行環境を分離できます。定期起動はAzureのScheduleジョブ、Cloud Run JobsとCloud Schedulerの連携、AWS BatchとEventBridge Schedulerの連携など、サービスごとに設定します。メリットは、(1) アプリケーションとジョブのライフサイクル分離、(2) 失敗時のリトライ設定を利用できること、(3) CPU/メモリをジョブ単位で指定できること、(4) クラウドのログ・監視基盤と連携できること、の4点です。再試行の単位・上限は各サービスの設定を確認します。Dead Letter Queueは全ジョブ基盤に共通の標準機能ではありません。例えばEventBridge SchedulerのDLQは、設定したSQSキューにターゲット呼び出しの配信失敗を保存する機能で、起動後のバッチ処理失敗とは別です。
一方で、(1) 新規ジョブ追加時にアプリとインフラの両方を更新する必要がある、(2) テナント横断処理や複雑な依存関係のあるタスクはやや扱いづらい、(3) 小さなタスク(数秒で終わる処理)には起動オーバーヘッドが大きい、という制約があります。
実務での役割分担
両者を併用する前提で、次のように役割を分担するのが実務的です。
- Celery Beat(アプリ同居):FastAPIアプリケーションと密結合する「データ同期」「API非同期処理」「テナント横断で軽量な定期処理」「リアルタイム応答のトリガー起点となるタスク」。例:外部SaaSからのデータ同期、KPIの日次集計、レポートの定期取得など。
- Azure Container Apps Jobs:アプリとは独立させたい「重いバッチ」「長時間処理」「大量トランザクションを扱うETL」「定型オペレーションの自動化」「時間指定の配信処理」「外部ATS連携の同期処理」「月次・四半期集計」。
この分担の判断軸は「(1) FastAPIアプリのメインコンテナと共有したいコンテキストがあるか」「(2) 失敗時の独立リトライが必要か」「(3) リソース割当を独立させたいか」の3点です。 Celeryのキュー別ルーティングで専用ワーカーを割り当てても、共有DBやCPU等の負荷まで分離されるわけではありません。キューの滞留と処理容量を監視し、必要な資源を確保します。
Celery Beat運用の実装パターン
Redis接続のSSL対応(Azure/AWS/GCP共通)
マネージドRedis(Azure Cache for Redis、AWS ElastiCache、GCP Memorystore等)は、クラスタ設定やセキュリティポリシーでSSL接続を要求するケースがあります。本記事では、環境変数の真偽値でSSLスキームを切り替える設計例を扱います。
- SSL無効時:`redis://:{password}@{host}:{port}/{db}`(パスワードありの場合)または `redis://{host}:{port}/{db}`
- SSL有効時:`rediss://:{password}@{host}:{port}/{db}` を使用。`rediss://` でTLS接続します。証明書検証を有効にし、`ssl_cert_reqs=required`(辞書設定では `ssl.CERT_REQUIRED`)と、接続先を検証できるCA証明書を設定します。証明書検証を無効にして接続エラーを解消する方法は避けます。
Celery 5.6の設定資料では、RedisのTLS接続について `ssl_cert_reqs` の明示を案内しています。特にRedis result backendの実装は、省略・不正値をエラーにします。一方、broker接続はKombuのRedisトランスポートを通るため、Celery 5.xの全接続で同じ失敗になるとは限りません。brokerとresult backendを別々に確認し、利用版に合うSSL設定を指定します。開発用と本番用の接続設定を分離し、各環境のRedisが要求するTLS・CA設定に合わせます。
マルチテナントのタスク自動検出
マルチテナントSaaSでCeleryを使う場合、各テナントの `tasks.py` や `tasks/` ディレクトリを起動時に自動検出してincludeする仕組みが有用です。次のロジックはアプリ側で実装する構成例です。Celeryのincludeは各ワーカーがimportするモジュールのリストであり、テナントディレクトリを自動走査する処理自体はCeleryの既定動作ではありません。
- `tenants/` ディレクトリを走査し、各サブディレクトリ(テナントID)を取得
- そのテナントに `tasks.py` または `tasks/` があるか判定
- ある場合、`テナントのタスクモジュール` をCelery includeリストに自動追加
これにより、新規テナント追加時にはディレクトリを作ってタスクファイルを配置し、走査処理を伴う起動時の再読込を行うことで検出できます。Celeryアプリ側への手動配線追加は不要です。この仕組みは、大量のテナントを運用するSaaSにおいて、開発速度と運用ミス防止の両方に効きます。
Beat Schedule(crontab)の設計
Celery Beatのスケジュール定義には `celery.schedules.crontab` を使います。タイムゾーンは `app.conf.timezone = "Asia/Tokyo"` で明示指定することが重要です。これを忘れるとUTCで動作し、朝9:00(JST)のつもりが同日18:00(JST)にタスクが走る事故が起きます。
実務では、次のようなスケジュール定義パターンがよく使われます。
- 毎日決まった時刻:`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)` のコメントを残し、理由と再開予定を明記する運用にすると、後から判断根拠を追えます。 実行状態の保存は既定のPersistentScheduler(shelve)、またはRedisを使うRedBeat等で管理します。
Celeryタスクのログ記録(signal hook)
Celeryには `task_prerun`, `task_postrun`, `task_failure`, `task_retry` などのシグナルが用意されており、これらを使ってタスク実行ログをDBやObservabilityツールに記録できます。タスクごとに開始・終了時刻、ステータス、エラー内容、実行引数をDBに記録し、運用ダッシュボードで可視化する構成が有効です。
このロガーはCeleryアプリの初期化時に1行のimportで読み込ませることで、シグナルハンドラが自動登録される設計になっています。副作用import(side-effect import)のパターンですが、ハンドラを登録するモジュールが各ワーカーで読み込まれるようにする実装方法の一つです。
Azure Container Apps Jobs運用の実装パターン
自動デプロイとTerraform同期
ジョブ基盤の構成をIaCで管理し、CIから自動デプロイする運用が有効です。既定ブランチへのマージで、変更検知・必要なイメージのみビルド・構成反映までを自動実行します。
この自動デプロイのトリガー対象は、ジョブ本体・インフラ定義・依存関係定義のパスに絞り込みます。無関係な変更ではジョブ再ビルドが発生しないよう、パスフィルタリングを厳密に設定するのが重要です。
ジョブごとの独立Dockerfile
Azure Container Apps Jobsは既存のコンテナイメージを指定して実行でき、ジョブごとに別のDockerfileを用意することは必須ではありません。同じイメージを複数ジョブから使う構成も可能です。本記事では、ジョブ単位にDockerfileを分け、各ジョブに必要な最小依存ライブラリだけをインストールする設計を扱います。
この分離により、(1) ジョブ間の依存競合を回避、(2) イメージサイズを最小化、(3) 特定ジョブだけ別バージョンのライブラリを使いたい要件に柔軟対応、が可能になります。ただしjob数が増えるとDockerfileの重複が増えるため、共通ベースイメージを別途ビルドして継承する工夫が必要です。
ジョブ運用の規律
renueでは以下の運用ルールを厳守しています。
- `az containerapp job update` の直接実行は緊急時のみ:通常運用ではGitHub Actions経由のTerraform applyに統一。手動更新はdriftの原因になります。
- 緊急で直接更新した場合、必ずdriftを解消:同じ変更をインフラ定義とTerraform stateに反映します。
- Azure APIを読むジョブはManaged Identity必須:`DefaultAzureCredential` を使うジョブには Job Identity を `SystemAssigned` or `UserAssigned` に設定。
- 必要RBACを最小権限で付与:対象RGの`Reader`、Log Analytics Workspaceの`Log Analytics Reader` / `Monitoring Reader` 等、ジョブ別に必要最小限のロールを付与。
主要ジョブの種類
クラウドのジョブ基盤で運用されるジョブの代表例(機能分類のみ):
- 定型タスク管理:日次のタスク・課題自動管理
- 週次サマリー配信:週次メッセージの生成・配信
- 候補者同期(ATS連携):複数時間帯での候補者データ同期
- 求人同期(ATS連携):複数時間帯での求人データ同期
- オンボーディング:オンボーディング資料の定期配信
定期実行するジョブはScheduleタイプのcron(UTCで評価)で時刻指定します。失敗したレプリカの再試行は `replicaRetryLimit` の設定に従い、0なら再試行しません。タイムアウトも考慮して設定します。重要なジョブは別途Slack通知フックで成功/失敗を記録します。
Celery Beat vs Azure Container Apps Jobsの比較早見表
| 軸 | Celery Beat | Azure Container Apps Jobs |
|---|---|---|
| 起動オーバーヘッド | 小(ワーカー常駐) | 中〜大(コンテナ起動時間) |
| リソース分離 | ワーカー共有または専用キュー・ワーカーに分離 | レプリカのCPU/メモリを指定。接続先DB等の共有資源は別途考慮 |
| 失敗時のリトライ | Celeryの設定に依存 | レプリカの再試行回数・タイムアウトを設定 |
| 監査ログ | Celeryのイベント・ログ等を収集する構成を設計 | Azure Monitor等へのログ出力・収集を設定 |
| 新規追加のしやすさ | 既存の実行環境に収まるならコード変更中心 | ジョブ定義と使用イメージを管理 |
| 小さなタスク(数秒)の適性 | 適している | オーバーヘッド大 |
| 長時間処理・バッチ | ワーカー占有リスクあり | 適している |
| テナント横断処理 | アプリと同じコンテキストで容易 | 可能だがやや面倒 |
| アプリとのライフサイクル結合度 | 同時デプロイも、ワーカー・Beatを分ける構成も可能 | アプリと独立してデプロイ可能 |
| デプロイ方式 | アプリと同時または別途デプロイ | Terraform + GitHub Actions等 |
renueの本番運用10原則
原則1:ジョブの性質でCelery BeatとAzure Jobsを使い分ける
「小さい・頻度高い・アプリと密結合」→ Celery Beat、「重い・頻度低い・独立リソース必要」→ Azure Container Apps Jobs、という線引きを最初に決めます。両方に散らばらせると監視と運用が二重化します。
原則2:Redis接続はSSL対応を環境変数で切替可能に設計する
環境変数の真偽値で `redis://` と `rediss://` を切り替える設計を初日から入れます。後付けは混乱の元です。
原則3:Celery Beatのタイムゾーンは `Asia/Tokyo` を明示する
デフォルトのUTCで動かすと、スケジュール定義と実運用時刻がズレて事故になります。`app.conf.timezone = "Asia/Tokyo"` を必ず設定します。
原則4:テナントタスクは自動検出の仕組みで新規追加を自動化する
自動検出の関数を用意し、テナント追加時にスケジューラ側の設定変更が不要になるようにします。
原則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のつもりが9:00 UTC(同日18:00 JST)に動く事故。初日から `Asia/Tokyo` 明示が必須。
落とし穴2:Redis SSLの `ssl_cert_reqs` 省略
Redis result backendでは、`ssl_cert_reqs` の省略・不正値がTLS設定エラーの原因になります。broker側とは設定経路が異なるため、利用版の設定資料を確認し、証明書検証を有効にする値と信頼するCAを指定します。
落とし穴3:Beatの二重起動
同じスケジュールを投入するCelery Beatは、稼働中のインスタンスを全体で1つに保ちます。Supervisordが1コンテナ内で1プロセスにしても、そのコンテナを複数レプリカで起動すればBeatは複数になります。Beat専用の単一インスタンスを明示し、デプロイ切替時の重複起動も防ぎます(Celery公式の単一スケジューラー要件)。
落とし穴4:ワーカーとBeatの同居起動問題
Supervisordで同じコンテナ内にGunicorn + Celery worker + Celery beatを同居起動する構成は便利ですが、レプリカやデプロイ切替でBeatが2つ以上立ち上がると重複投入につながります。同居構成を使う場合はApp Serviceを含む全実行環境でBeatの単一稼働を守ります。Webやワーカーをスケールさせる場合はBeatを別の単一稼働サービスへ分けます。
落とし穴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に登録したジョブが重複して同じ処理を実行する事故。役割分担をコードレビューで確認します。
まとめ: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バックエンド設計2026
- LLMOps実践ガイド2026
- MCP完全ガイド2026
- Function Calling完全ガイド2026
- AI内製化 vs 外注完全ガイド2026
- 生成AIセキュリティ完全ガイド2026
- 生成AI受託開発の費用相場2026
- AIコーディングエージェント徹底比較2026




