「コンテナは本番で動かしたい。でもKubernetesクラスタの面倒は見たくないし、ノードのパッチ当てやスケーリングにも時間を割けない」——スタートアップや一人開発で本番のコンテナ基盤を組むとき、ほぼ必ずここに行き着きます。Azureにおけるその答えが Azure Container Apps(ACA) です。
私はAWS Fargateの上で、サーバーレスコンテナを本番運用してきました。経済産業大臣賞を受賞した木材流通B2B SaaS(API Gateway → NLB → ALB → ECS構成で221本のAPI)も、本番二重課金0件の決済基盤のワーカー群も、サーバーを1台も触らずにコンテナで回しています。Azure Container Appsは、その思想のAzure版です。AWSで「Fargateを選んだ後、本番でどう作るか」に詰まる勘所は、ほぼそのままACAに移植できます。
この記事は、Microsoft Learn公式ドキュメントに忠実でありながら、公式よりわかりやすく、かつ「どの場面でどう使うか」を実コードで示すことを目的にしています。環境設計・スケール・Ingress・リビジョン・回復性・セキュリティ・コストまで、本番に出すために必要なことを一気通貫で扱います。AWS Fargateとの直接比較は、別記事 Azure Container Apps vs AWS ECS on Fargate:サーバーレスコンテナ徹底比較 にまとめました。本稿は 「ACAを選んだ後、本番でどう作るか」 に集中します。
Azure Container Apps とは何か:公式の定義
公式の定義はシンプルです。
Azure Container Apps is a serverless platform that allows you to maintain less infrastructure and save costs while running containerized applications. Instead of worrying about server configuration, container orchestration, and deployment details, Container Apps provides all the up-to-date server resources required to keep your applications stable and secure.(— Azure Container Apps overview)
つまり ACA は、サーバー構成・コンテナオーケストレーション・デプロイの詳細を気にせず、コンテナを動かすことだけに集中するためのサーバーレス基盤です。OSのパッチも、Kubernetesのバージョンアップも、ノードのスケールも、すべてプラットフォーム側が面倒を見ます。
公式が挙げる代表的なユースケースは4つです。
- API エンドポイントのデプロイ(HTTP API・Webアプリ)
- バックグラウンド処理ジョブのホスティング(バッチ)
- イベント駆動処理(キュー・イベントソース起点)
- マイクロサービスの実行
何の上に構築されているか
ACAは独自技術ではなく、実績あるOSSの上に建てられたマネージド層です。これは「いつでもKubernetesの世界に地続きで降りられる」という安心材料になります。
Powered by Kubernetes and open-source technologies like Dapr, KEDA, and envoy.(— Comparing Container Apps with other Azure container options)
- Kubernetes:オーケストレーションの土台。ただし後述のとおりAPIは直接触らせない。
- KEDA(Kubernetes Event-driven Autoscaling):スケールの頭脳。HTTP・キュー・CPU/メモリなど多様なメトリクスでスケールする。
- Dapr(Distributed Application Runtime):マイクロサービスのサービス呼び出し・状態管理・Pub/Subを抽象化する。
- Envoy:エッジのHTTPプロキシ。TLS終端とルーティングを担う。
そして最も重要な境界線がこれです。
Azure Container Apps doesn't provide direct access to the underlying Kubernetes APIs. If you require access to the Kubernetes APIs and control plane, you should use Azure Kubernetes Service.(— compare-options)
ACAはKubernetes APIを直接公開しません。CRDもkubectlもHelmも使いません。この「隠蔽」こそがACAの価値です。Kubernetesの運用知識がなくても、Kubernetesのベストプラクティスに基づいたマネージド体験が得られる。逆に、Ingressコントローラを差し替えたい、DaemonSetを動かしたい、Operatorを入れたい——といったKubernetesの制御面そのものが必要なら、ACAではなくAKSを選びます。
いつ使うか:他のAzureコンテナ選択肢との比較
Azureには「コンテナを動かす」選択肢が複数あります。公式の比較ドキュメントを、意思決定に使える形に整理します。
| サービス | 一言でいうと | 選ぶべき場面 |
|---|---|---|
| Azure Container Apps | サーバーレスのコンテナ・マイクロサービス/ジョブ基盤 | 汎用コンテナ・マイクロサービス・イベント駆動・ジョブをK8s運用なしで動かす。迷ったらここ。 |
| Azure Kubernetes Service (AKS) | フルマネージドKubernetes | Kubernetes APIと制御面に直接アクセスしたい。任意のK8sワークロード。 |
| Azure App Service | Webアプリ向けPaaS | Webサイト・WebAPIに最適化。Web中心ならこちら。 |
| Azure Container Instances (ACI) | 単一Podの低レベル部品 | スケール・LB・証明書を自前で組む。より低レベルな構成ブロックが欲しい。 |
| Azure Functions | サーバーレスFaaS | イベント駆動の関数プログラミングモデル。トリガー+バインディング。 |
ACIとACAの違いは、公式の説明が秀逸です。
Concepts like scale, load balancing, and certificates aren't provided with ACI containers. For example, to scale to five container instances, you create five distinct container instances. Azure Container Apps provide many application-specific concepts on top of containers, including certificates, revisions, scale, and environments.(— compare-options)
ACIは「5インスタンスにスケールしたければ、5個のインスタンスを自分で作る」レベルの素材です。ACAはその上に証明書・リビジョン・スケール・環境といったアプリ運用の概念を載せた、完成度の高い基盤です。
AWS経験者向けの対応表:
ACA ≈ AWS Fargate / App Runner、AKS ≈ EKS、ACI ≈ ECS の単発タスク(RunTask)、Azure Functions ≈ AWS Lambda。Fargateで「タスク定義を書いてServiceに任せる」感覚は、ACAの「コンテナを定義してEnvironmentに任せる」感覚とほぼ同じです。詳細はクロスクラウド比較記事へ。
コアな構成要素:4つの登場人物の関係
ACAは用語が多くて最初に混乱します。本質は4つです。
Environment(環境:安全な境界。複数アプリ/ジョブをまとめ、VNetとログ先を共有)
└── Container App(アプリ:1つのサービス。望ましい状態を宣言する単位)
└── Revision(リビジョン:アプリの不変スナップショット=1つのバージョン)
└── Replica(レプリカ:スケールで増減する実行インスタンス)
└── Container(あなたのアプリのイメージ。+任意のsidecar/init)
Environment(環境)= 安全な境界
A Container Apps environment is a secure boundary around one or more container apps and jobs. The Container Apps runtime manages each environment by handling OS upgrades, scale operations, failover procedures, and resource balancing.(— Azure Container Apps environments)
環境はセキュリティ境界です。ランタイムがOSアップグレード・スケール・フェイルオーバー・リソース調整をすべて担います。そして、
When multiple container apps are in the same environment, they share the same virtual network and write logs to the same logging destination.
同じ環境のアプリはVNetとログ先(Log Analytics)を共有します。これが環境設計の判断軸になります。
- 単一環境にまとめる:関連サービスを管理したい、同じVNetに置きたい、Daprのサービス呼び出しで相互通信したい、ログ先を共有したい。
- 環境を分ける:計算リソースを絶対に共有させたくない、チーム/用途(本番 vs テスト)で隔離したい。
実務では「本番」「ステージング」を別環境にし、本番環境内で複数のマイクロサービス(公開API・内部ワーカー・管理画面)を相乗りさせる、という構成が定石です。AWSでいう「VPC+ECSクラスタ」を1つのEnvironmentに圧縮したイメージです。
⚠️ 環境の自動削除ポリシー:環境が90日間アイドル(アクティブなアプリ/ジョブが無い)、またはVNet/Azure Policyの設定不備で失敗状態が続くと、環境は自動削除されます。検証用環境を放置しない、最低1つはアクティブに保つ、を運用ルールに入れてください。
リソース設計:CPUとメモリは"組み合わせが固定"
ACA(Consumptionプラン)最大の落とし穴がここです。CPUとメモリは自由な組み合わせではなく、決められたペアからしか選べません。コンテナ内の全コンテナ(sidecar含む)の合計が、次のいずれかに一致する必要があります(公式の組み合わせ表)。
| vCPU(コア) | メモリ | vCPU(コア) | メモリ | |
|---|---|---|---|---|
0.25 | 0.5Gi | 2.0 | 4.0Gi | |
0.5 | 1.0Gi | 2.25 | 4.5Gi | |
0.75 | 1.5Gi | 2.5 | 5.0Gi | |
1.0 | 2.0Gi | 2.75 | 5.5Gi | |
1.25 | 2.5Gi | 3.0 | 6.0Gi | |
1.5 | 3.0Gi | 3.5 | 7.0Gi | |
1.75 | 3.5Gi | 4.0 | 8.0Gi |
法則は明快で、メモリ(GiB) = vCPU × 2 です。Fargateの「1024 CPUを選んだ瞬間にメモリ最低2GB」という制約とまったく同じ発想で、ACAではさらに比率が固定されています。「メモリは512MiBで足りるがCPUは2コア欲しい」ようなワークロードは、4GiBのメモリ代も払うことになります。だからサイズは"計測してから"決めるのが鉄則です。憶測で大きく取ると、使わないリソースに毎秒課金され続けます。
Consumption only 環境の上限:レガシーなConsumption only環境では、1アプリあたり最大2コア・4Giに制限されます(公式)。4コア・8GiBまで使いたい、より大きなハードウェアが欲しい場合は、既定のワークロードプロファイル環境を選んでください(後述)。
イメージとストレージの制約
- イメージ:
linux/amd64(x86-64)のLinuxイメージのみ。任意の公開/プライベートレジストリから取得可。 - 特権コンテナ不可:ホストレベルアクセスを伴うprivilegedモードは使えません。
- 最大イメージサイズ:Consumptionワークロードプロファイルでは、アプリ/ジョブのレプリカあたり合計最大8 GB。
- クラッシュ時:
If a container crashes, it automatically restarts.(公式)— コンテナが落ちれば自動再起動します。
コンテナ定義:タグ規律・sidecar・initコンテナ
コンテナはproperties.templateのcontainers配列に定義します。最初に押さえるべきはタグ規律です。
Avoid using static tags like
latestfor container images. Using static tags can lead to caching problems and can make your app difficult to troubleshoot. Instead, use unique tags for each deployment, such as a Git hash or date and time to ensure that updates are properly tracked and deployed.(— Containers in Azure Container Apps)
latestタグは本番で使わない。Gitハッシュや日時で一意のタグを付ける。これはACAに限らない鉄則ですが、ACAでは「リビジョン=不変スナップショット」という設計上、タグの一意性がトレーサビリティに直結します。
sidecar と init コンテナ
ほとんどのアプリはコンテナ1つですが、高度なシナリオでは複数定義できます。
- sidecarコンテナ:主コンテナと密結合な補助プロセス(共有ボリューム経由のログ転送、キャッシュ更新など)。
containers配列に追加。 - initコンテナ:主コンテナの前に走り、必ず成功してから主コンテナが起動する初期化処理(データのダウンロード、マイグレーションなど)。
initContainers配列に定義。
マイクロサービスの大半は「1サービス=1コンテナアプリ」にすべきです。公式も
For most microservice scenarios, the best practice is to deploy each service as a separate container app.と明言しています。複数コンテナの相乗りは、ライフサイクルを共有してよい密結合なケースに限定してください(SRPの原則)。
スケーリング:KEDAによる水平オートスケール
ACAの心臓部です。
To support this scaling behavior, Azure Container Apps uses KEDA (Kubernetes Event-driven Autoscaling). KEDA supports scaling against a variety of metrics like HTTP requests, queue messages, CPU and memory load, and event sources like Azure Service Bus, Azure Event Hubs, Apache Kafka, and Redis.(— Scaling in Azure Container Apps)
スケールは limits(上下限)・rules(条件)・behavior(挙動) の組み合わせです。
| スケール上下限 | 既定値 | 最小 | 最大 |
|---|---|---|---|
| 最小レプリカ数/リビジョン | 0 | 0 | 1,000 |
| 最大レプリカ数/リビジョン | 10 | 1 | 1,000 |
スケールルールは3カテゴリ。
- HTTP:同時HTTPリクエスト数(
concurrentRequests、既定10)。15秒ごとに「過去15秒のリクエスト数÷15」で算出。 - TCP:同時TCP接続数(
concurrentConnections)。 - Custom:CPU・メモリ・KEDAスケーラー(Service Bus / Event Hubs / Kafka / Redis / Queue Storage など)。
ルールを定義しなければ、**既定のスケールルール(HTTP、最小0・最大10)**が適用されます。
ゼロスケール:最大の魅力と最大の罠
ACAの目玉はゼロスケールです。Most applications can scale to zero.(公式)——アイドル時にレプリカを0にし、リクエストが来たら起こす。その間は課金されません。
ただし、2つの重要な注意があります。
⚠️ CPU/メモリスケールはゼロにできない:
Applications that scale on CPU or memory load can't scale to zero.(公式)CPU/メモリ負荷を計測するには、そもそもレプリカが動いている必要があるためです。ゼロスケールしたいなら、HTTPまたはイベント駆動ルールを使います。
🚨 Ingress無効+ゼロスケールの自爆:
Make sure you create a scale rule or set minReplicas to 1 or more if you don't enable ingress. If ingress is disabled and you don't define a minReplicas or a custom scale rule, your container app scales to zero and has no way of starting back up.(公式)——Ingressを無効にしたバックグラウンドワーカーで、最小レプリカ0かつスケールルール無しにすると、0に落ちた後で二度と起き上がれません。ワーカーには必ずイベント駆動ルールを付けるか、minReplicasを1以上にします。
スケール挙動:アルゴリズムを理解する
| 挙動 | 値 |
|---|---|
| ポーリング間隔 | 30秒 |
| クールダウン期間 | 300秒 |
| スケールアップ安定化ウィンドウ | 0秒 |
| スケールダウン安定化ウィンドウ | 300秒 |
| スケールアップ刻み | 1, 4, 8, 16, 32, ...(最大まで) |
| スケールダウン刻み | 落とすべきレプリカの100% |
| スケールアルゴリズム | desiredReplicas = ceil(currentMetricValue / targetMetricValue) |
このアルゴリズムは実務での容量計画に直結します。たとえばService BusキューのmessageCount: 5(1レプリカあたり5メッセージ)でキュー長が50なら、ceil(50/5) = 10レプリカが目標になります。スループットの目標から逆算してmessageCountを決めるのが正攻法です。
注意点:
Vertical scaling isn't supported.(垂直スケール非対応)。レプリカ1台のCPU/メモリは固定で、負荷対応は水平スケール(台数)だけです。AWS Fargateのターゲット追跡スケーリングと同じ「台数で捌く」発想に統一されています。
イベント駆動スケールの実装(Service Bus × マネージドID)
キュー駆動ワーカーの本番実装は、マネージドIDで認証するのが定石です(接続文字列をアプリに置かない)。BicepでのService Busスケールルール例:
resource app 'Microsoft.App/containerApps@2025-02-02-preview' = {
name: 'order-worker'
location: location
identity: { type: 'SystemAssigned' } // システム割当IDを有効化
properties: {
managedEnvironmentId: environmentId
configuration: {
activeRevisionsMode: 'single' // 非HTTPイベントルールではsingle必須
ingress: null // ワーカーはIngress無し
}
template: {
containers: [
{
name: 'worker'
image: 'myregistry.azurecr.io/order-worker:2026-06-26-a1b2c3d'
resources: { cpu: json('0.5'), memory: '1.0Gi' }
}
]
scale: {
minReplicas: 0
maxReplicas: 30
rules: [
{
name: 'servicebus-orders'
custom: {
type: 'azure-servicebus'
metadata: {
queueName: 'orders'
namespace: 'my-sb-namespace'
messageCount: '5' // 1レプリカが捌く目標メッセージ数
}
identity: 'system' // ← マネージドIDで認証(秘密を持たない)
}
}
]
}
}
}
}
公式も明言しています。
Where possible, use managed identity authentication to avoid storing secrets within the app.(— scale-app)
ワーカーのスケールターゲットはsystem(システム割当ID)にAzure Service Bus Data Receiverロールを付与すれば、接続文字列はどこにも保存せずに済みます。これは決済基盤でSQSワーカーを冪等化したときと同じ思想です——「少なくとも1回・順不同・失敗する」を前提に冪等に作る。スケールはプラットフォームが、正しさはコードの構造(冪等性キー)が保証します。
Ingress:HTTPSを自分で組まない
Ingressを有効にすると、ロードバランサも公開IPも証明書も自分で用意する必要がありません。
When you enable ingress, you don't need to create an Azure Load Balancer, public IP address, or any other Azure resources to enable incoming HTTP requests or TCP (Transmission Control Protocol) traffic.(— Ingress in Azure Container Apps)
External / Internal とプロトコル
- External:環境の公開IP経由でインターネットから到達可能。
- Internal:同じ環境内からのみ到達可能。インターネットには非公開。
マイクロサービス構成では、公開API=External、内部サービス=Internalに分けるのがセキュリティの定石です。「公開フロントが内部ワーカーに渡す」という二段構えで攻撃面を絞ります。
HTTP Ingressが自動で提供するもの
HTTP Ingressを有効にすると、次がすべて自動で付いてきます。
- TLS終端、HTTP/1.1 と HTTP/2、WebSocket と gRPC のサポート
- 常にTLS 1.2/1.3を使うHTTPSエンドポイント
- ポート80/443の公開(80→443へ自動リダイレクト)
- 完全修飾ドメイン名(FQDN)
- そして——
Request time out is 240 seconds(— ingress-overview)
HTTPリクエストのタイムアウトは240秒です。これより長い処理(重いレポート生成、大きなアップロードの同期処理など)は、HTTPで待たせずジョブやキューに逃がす設計にします。
セキュリティの落とし穴:X-Forwarded-For
IngressはクライアントメタデータをHTTPヘッダで渡します。ここに重要な注意があります。
X-Forwarded-For… Only the rightmost IP is provided by Azure Container Apps. Any other values must be validated by the user to prevent IP spoofing.(— ingress-overview)
X-Forwarded-Forは最右端のIPだけがACA由来で信頼でき、それ以外はクライアントが詐称できます。IP制限やレート制限を実装するとき、左端のIPを鵜呑みにすると簡単に回避されます。信頼境界はサーバー側、という原則どおり、外部入力は検証する——ここはアプリ側の責務です。
その他のIngress機能:IP制限、クライアント証明書(mTLS)、セッションアフィニティ(スティッキー)、CORS、リビジョン間トラフィック分割。なおポート36985は内部ヘルスチェック用に予約されており、アプリでは使えません。
ヘルスプローブ:startup / liveness / readiness
ACAは3種のプローブで状態を監視します(Health probes)。
| プローブ | 役割 |
|---|---|
| Startup | アプリが正常に起動したかを、初期起動フェーズで確認する |
| Liveness | アプリがまだ生きていて応答するかを確認する(失敗→再起動) |
| Readiness | レプリカがリクエストを受ける準備ができたかを確認する(失敗→トラフィック除外) |
制約は明確です:プローブはTCPまたはHTTP(S)のみ、execプローブとgRPCは非対応、各タイプ1コンテナにつき1つずつ。HTTPプローブの成功条件は 200以上400未満 のステータスコードです。
依存先まで見るliveness(実コード)
公式が示すJavaScriptの例は、liveness=「自分が生きている」ではなく「依存先まで含めて健全」を表現する良いパターンです。
const express = require("express");
const app = express();
// liveness: DBやファイルシステムなど依存の健全性まで確認してから 200 を返す
app.get("/liveness", (req, res) => {
let isSystemStable = false;
// check for database availability
// check filesystem structure, etc.
// set isSystemStable to true if all checks pass
res.status(isSystemStable ? 200 : 503).end();
});
// readiness: 受け入れ可能になってから 200。起動直後のウォームアップ中は 503 を返す
app.get("/readiness", (req, res) => {
res.status(isWarmedUp() ? 200 : 503).end();
});
既定プローブと「起動が遅いアプリ」
Ingressを有効にすると、各タイプを自分で定義しない限り**既定プローブ(IngressのターゲットポートへのTCP)**が自動追加されます。重要なのは「マルチリビジョンモードでは、readinessが成功するまでトラフィックを切り替えない」という挙動です——これがゼロダウンタイムの土台になります。
起動に時間がかかるアプリ(JVMのウォームアップ、大きなモデルのロードなど)は、initialDelaySeconds・periodSeconds・failureThresholdを緩めて、準備前に再起動されるのを防ぎます。
"probes": [
{
"type": "Startup",
"httpGet": { "path": "/startup", "port": 8080 },
"initialDelaySeconds": 3,
"periodSeconds": 3,
"failureThreshold": 30
},
{
"type": "Liveness",
"httpGet": { "path": "/liveness", "port": 8080 },
"periodSeconds": 10,
"failureThreshold": 3
},
{
"type": "Readiness",
"httpGet": { "path": "/readiness", "port": 8080 },
"initialDelaySeconds": 3,
"periodSeconds": 5,
"failureThreshold": 48
}
]
リビジョンとデプロイ:ゼロダウンタイム・Blue/Green・カナリア
変更管理はリビジョンで行います。
Change management in Azure Container Apps is powered by revisions, which are a snapshot of each version of your container app.(— Update and deploy changes)
リビジョンは不変(immutable)・版管理(versioned)・自動生成で、既定で100個の非アクティブリビジョンを履歴として保持します。デプロイとは「新しいリビジョンを作って切り替える」ことです。
2つのモード
| モード | 挙動 | 既定 |
|---|---|---|
| Single(単一) | 新リビジョンが準備完了したら自動でトラフィックを全切替。失敗時は旧リビジョンに留まる。旧リビジョンは自動破棄。 | ✅ |
| Multiple(複数) | 複数リビジョンを同時アクティブにし、トラフィックを%で分割。Blue/Green・A/B・カナリアに使う。 | — |
単一モードのゼロダウンタイム
In single revision mode, Container Apps ensures your app doesn't experience downtime when creating a new revision. The existing active revision isn't deactivated until the new revision is ready.(— revisions)
新リビジョンが「準備完了」とみなされるのは、プロビジョニング成功 + 旧リビジョンのレプリカ数までスケール + 全レプリカがstartup/readinessプローブを通過したときです。それまで旧リビジョンが100%のトラフィックを受け続けます。これはAWS Fargateの「ローリング更新+デプロイサーキットブレーカーで自動ロールバック」と同じく、失敗したら切り替わらない安全側の挙動です。
複数モードでBlue/Green・カナリア
複数リビジョンモードでは、トラフィックを%で割り当てられます。たとえば新版に10%だけ流すカナリアリリース:
# 新リビジョンを10%、現行を90%に(カナリア)
az containerapp ingress traffic set \
--name my-api --resource-group my-rg \
--revision-weight my-api--green=10 my-api--blue=90
# 問題なければ100%へ昇格(Blue/Greenの切替)
az containerapp ingress traffic set \
--name my-api --resource-group my-rg \
--revision-weight my-api--green=100
さらにラベルを使えば、https://...---green.<env>.azurecontainerapps.io のような安定URLを特定リビジョンに割り当て、テストユーザーだけを新版に通すといった検証ができます。ラベルはリビジョン間で移動してもURLが変わらないのが利点です。
revision-scope と application-scope:秘密の再起動に注意
変更には2種類あり、新リビジョンが作られるかどうかが変わります。
- revision-scope(
properties.template):コンテナ・イメージ・スケールルール等 → 新リビジョンを生成。 - application-scope(
properties.configuration):シークレット値・リビジョンモード・Ingress・Dapr設定等 → 新リビジョンを生成しない(全リビジョンに即時反映)。
ここに実務の罠があります。シークレット値を更新しても、自動では既存リビジョンに反映されません。
Secret values (revisions must be restarted before a container recognizes new secret values)(— revisions)
シークレットをローテーションしたら、リビジョンを再起動するか新リビジョンをデプロイして初めて反映されます。これは後述のKey Vault参照(自動再起動あり)と挙動が違うので、運用Runbookに明記しておきます。
グレースフルシャットダウン:SIGTERM と冪等性
本番の回復性で最も重要なのに、見落とされがちなのがここです。コンテナは次の場面で停止します(Application lifecycle management)。
- アプリがスケールイン(レプリカ減少)したとき
- アプリが削除されたとき
- リビジョンが非アクティブ化されたとき
そして停止時の挙動はこう定義されています。
When a shutdown starts, the container host sends a SIGTERM message to your container. The code in the container can respond to this operating system-level message to handle termination. If your application doesn't respond within 30 seconds to the SIGTERM message, then SIGKILL terminates your container.(— application-lifecycle-management)
SIGTERMを受けてから30秒以内に綺麗に終われなければ、SIGKILLで強制終了されます(猶予はterminationGracePeriodSecondsで延長可、既定30秒)。これはAWS FargateのstopTimeoutとまったく同じパターンです。スケールインのたびに処理が中断されうるので、**SIGTERMを掴んで「受付を止め→処理中を終わらせ→接続を閉じる」**ハンドラが必須です。
// Node.js:SIGTERMでグレースフルに終了する(30秒以内に完了させる)
const server = app.listen(8080);
let shuttingDown = false;
process.on("SIGTERM", async () => {
if (shuttingDown) return; // 冪等:二重シャットダウンを防ぐ
shuttingDown = true;
// 1) readinessを落としLBから外す(新規受付を止める)
// 2) 進行中のHTTPリクエストを捌き切る
server.close(async () => {
// 3) DB接続プール・キュー購読を綺麗に閉じる
await pool.end();
await queueConsumer.close();
process.exit(0);
});
// 保険:猶予が尽きる前に強制終了(SIGKILLを待たない)
setTimeout(() => process.exit(1), 25_000).unref();
});
そして公式の警告——
Containers restart regularly, so don't expect state to persist inside a container. Instead, use external caches for expensive in-memory cache requirements.(— application-lifecycle-management)
コンテナ内に状態を持たない。インメモリキャッシュが必要なら、Redisなどexternal cacheに逃がす。レプリカは再起動・増減を前提に、ステートレスに設計する。キュー駆動ワーカーなら、SIGTERMで途中まで処理したメッセージが再配信されても壊れないよう、冪等性キーで重複を吸収します。スケールはプラットフォームの仕事、冪等性はコードの構造——この分担を徹底すれば、ゼロスケールしても二重課金も取りこぼしも起きません。
シークレットとマネージドID:認証情報をコードから消す
本番のセキュリティは「秘密をどこに置かないか」で決まります。ACAはマネージドIDとKey Vault参照で、認証情報をコードからもイメージからも消せます。
マネージドID:パスワードを持たない
A managed identity from Microsoft Entra ID allows your container app to access other Microsoft Entra protected resources. … Your app connects to resources with the managed identity. You don't need to manage credentials in your container app.(— Managed identities in Azure Container Apps)
- システム割当ID:アプリと一体。アプリ削除で自動削除。アプリ1つにつき1つ。
- ユーザー割当ID:独立したリソース。複数のアプリ/リソースで再利用可。
用途:ACRからのイメージpull、Key Vaultの秘密取得、スケールルールの認証、Azure SQL / Storage / Service Busへの接続。
アプリ側のコードはDefaultAzureCredentialで抽象化できます。ローカル開発(開発者の資格情報)と本番(マネージドID)で同じコードが動くのが利点です。
# Python:マネージドIDでKey Vault / Blobへ。コードに秘密を一切書かない
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
# ローカルではaz login、本番ではコンテナのマネージドIDを自動で使い分ける
credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://my-vault.vault.azure.net", credential=credential)
db_password = client.get_secret("db-password").value
🔐 最小権限のlifecycle設定:API版
2024-02-02-preview以降、identitySettingsのlifecycleで、マネージドIDをInit/Main/All/Noneのどのフェーズで使えるか制御できます。たとえば「ACRのpullにしか使わないID」はNoneにして、コンテナ内のコードからは一切使えないようにできます。万一コンテナが侵害されても、アクセスできるリソースを絞れる——最小権限の原則をプラットフォームレベルで強制できます。
Key Vault参照:秘密を直接書かない
Avoid specifying the value of a secret directly in a production environment. Instead, use a reference to a secret stored in Azure Key Vault.(— Manage secrets in Azure Container Apps)
シークレットはアプリスコープの名前/値ペアで、secretRefで環境変数から参照します。本番では値を直書きせず、Key Vault参照にします。マネージドIDにKey Vault Secrets Userロールを付与すれば、ACAがKey Vaultから値を取得します。
# Key Vault参照のシークレットを環境変数として注入(ユーザー割当IDで認証)
az containerapp create \
--resource-group my-rg --name my-api --environment my-env \
--image myregistry.azurecr.io/my-api:2026-06-26-a1b2c3d \
--user-assigned "$UAMI_ID" \
--secrets "db-password=keyvaultref:https://my-vault.vault.azure.net/secrets/db-password,identityref:$UAMI_ID" \
--env-vars "DB_PASSWORD=secretref:db-password"
Key Vault参照は自動ローテーションが効きます。
When newer versions become available, the app automatically retrieves the latest version within 30 minutes. Any active revisions that reference the secret in an environment variable is automatically restarted to pick up the new value.(— manage-secrets)
URIにバージョンを含めなければ、新バージョンを30分以内に取得し、参照リビジョンを自動再起動して反映します(直書きシークレットは手動再起動が必要だったのと対照的)。完全に固定したいなら、URIにバージョンを明示します。
Jobs:バッチ・スケジュール・イベント駆動
「常駐サービス」ではなく「走って終わる処理」はジョブで扱います。
Azure Container Apps jobs enable you to run containerized tasks that run for a finite duration and then stop.(— Jobs in Azure Container Apps)
アプリとジョブは同じ環境で動き、ネットワークやログを共有します。トリガーは3種類。
- Manual:オンデマンド(CLI・ポータル・ARM API)。データ移行などの単発処理。
- Schedule:cron式(UTC評価)。夜間のレポート生成などの定期処理。
- Event:KEDAスケーラーでイベント起点(キューのメッセージ等)。セルフホストのGitHub Actions Runner / Azure Pipelinesエージェントにも使える。
ジョブの主要設定(公式):
| 設定 | 意味 |
|---|---|
replicaTimeout | レプリカ完了を待つ最大秒数 |
replicaRetryLimit | 失敗レプリカのリトライ上限(0でリトライなし) |
parallelism | 1実行あたりのレプリカ数(多くは1) |
replicaCompletionCount | 成功とみなすのに完了が必要なレプリカ数 |
スケジュールジョブ(毎日0時にレポート生成)の例:
az containerapp job create \
--name nightly-report --resource-group my-rg --environment my-env \
--trigger-type "Schedule" --cron-expression "0 0 * * *" \
--replica-timeout 1800 --replica-retry-limit 1 \
--replica-completion-count 1 --parallelism 1 \
--image myregistry.azurecr.io/report:2026-06-26-a1b2c3d \
--cpu "0.5" --memory "1.0Gi"
⚠️ ジョブの制約:
The following features aren't supported: Dapr; Ingress and related features such as custom domains and SSL certificates.(公式)——ジョブはIngressもDaprも持ちません。リトライ前提なので、ジョブ本体も冪等に作ります(同じメッセージを2回処理しても壊れない)。
「常駐APIサービス(App)」「定期バッチ(Schedule Job)」「キュー駆動ワーカー(Event Job または App+カスタムスケール)」を同じ環境・同じデプロイ基盤で回せるのが、運用の認知負荷を劇的に下げます。
ネットワーキングとプラン:Consumption / Dedicated / Flex
ワークロードプロファイル=計算リソースの選択
A workload profile determines the type and amount of compute and memory resources available to container apps deployed in an Azure Container Apps environment.(— Workload profiles)
プロファイルは3種類。
- Consumption:サーバーレス。オンデマンドでスケールし、アイドル時はゼロスケール可。使った分だけ課金。
0.25–4 vCPU / 0.5–8 GiB。バースト・予測不能な負荷に最適。 - Dedicated:予約済みの専用プール。VMのサイズ/種別を選び、複数アプリを相乗りさせ、インスタンス単位で課金。安定負荷で割安になりうる。
- D系(汎用)
D4–D32:4–32 vCPU / 16–128 GiB - E系(メモリ最適化)
E4–E32:4–32 vCPU / 32–256 GiB - GPU(NC系 A100):大規模推論・学習向け
- D系(汎用)
- Flex(プレビュー):Consumptionの手軽さとDedicatedの性能を折衷。
/25サブネットが必要で、ゼロスケール不可。
迷ったらConsumptionです。Fargateと同じく「サーバー管理コスト(人件費)が最大のコスト」だからです。安定して常時高負荷で原価が支配的になったら、Dedicatedへの移行を検討します。
VNetとサブネット
ネットワークは環境が持ち、環境タイプでサブネット要件が変わります(Networking)。
| 環境タイプ | 対応プラン | 主な機能 | 最小サブネット |
|---|---|---|---|
| Workload profiles(既定) | Consumption, Dedicated | UDR・NAT Gateway・Private Endpoint対応 | /27 |
| Consumption only(レガシー) | Consumption | UDR・NAT Gateway等は非対応 | /23 |
本番でアウトバウンドを制御したい(Azure Firewall経由でegressロックダウン)、Private Endpointで内部アクセスに限定したい——といった要件があるなら、ワークロードプロファイル環境+専用サブネットを選びます。サブネットはACA環境専用(他サービスと共有不可)で、ネットワークタイプは作成後に変更できない点に注意。
[Internet] → [Application Gateway + WAF] → [Internal ACA Environment (VNet)]
├─ public-api (Internal Ingress)
├─ order-worker (Ingress無し / Service Bus駆動)
└─ nightly-report (Schedule Job)
↓ egress
[UDR → Azure Firewall] → 許可した宛先のみ
これはWAFの多層防御(AWS WAF / Cloud Armorの設計)と同じ発想を、Azure側でApplication Gateway + WAF + Internal環境 + Firewall egressに置き換えた形です。
コスト設計:従量課金・無料枠・idleレート
Consumptionプランの課金は2種類です(Billing)。
- リソース消費:vCPU秒・GiB秒(割り当て量×秒)
- HTTPリクエスト:受信リクエスト数
そして毎月サブスクリプションごとに無料枠があります。
The following resources are free during each calendar month, per subscription:
- The first 180,000 vCPU-seconds
- The first 360,000 GiB-seconds
- The first 2 million HTTP requests(— Billing in Azure Container Apps)
18万vCPU秒・36万GiB秒・200万リクエストが毎月無料です。0.5 vCPUのアプリなら、無料枠だけで月あたり約100時間(36万GiB秒÷1GiB側で計算しても十分)動かせる計算で、検証や小規模サービスは実質無料で運用できます。
3つの課金状態を理解する
| 状態 | 条件 | 課金 |
|---|---|---|
| ゼロ | レプリカ0(ゼロスケール中) | 課金なし |
| idle(割引) | minReplicas>0かつ最小数で待機中・アイドル | 割引レート |
| active(通常) | 最小数を超える、または処理中 | 通常レート |
idle判定は厳密で、全コンテナ起動済み・HTTPリクエスト処理なし・0.01 vCPU未満・ネットワーク1000 bytes/秒未満の全条件を満たすときだけ割引になります。「最低1台は常駐させたいが、暇なときは安く」という運用に効きます。
その他のコストの要点:
- 外部からのリクエストのみ課金。環境内のサービス間通信は課金対象外。
Health probe requests aren't billable.(ヘルスプローブも無料)。 - ジョブは常にactiveレート(idleなし)。実行が終われば消費は止まる。
- マネージドOpenTelemetryエージェントは追加の計算コストなしで動く(後述)。
コスト最適化の定石:①まずゼロスケールできるワークロード(HTTP/イベント駆動)はゼロに落とす。②常駐が要るものは
minReplicasを最小にしてidleレートを効かせる。③安定高負荷はDedicatedで原価を平準化。④latestを避けてイメージを小さく保ち、起動を速くしてゼロスケールの“冷たさ”を減らす。考え方はFargateのSpot/Graviton/Savings Plansによる最適化と同型です。
可観測性:ログ・メトリクス・OpenTelemetry
環境内の全アプリは、既定で共通のLog Analyticsワークスペースにログを送ります(environment)。収集されるのは:
- コンテナの
stdout/stderrストリーム - アプリのスケールイベント
- Daprサイドカーのログ(有効時)
- システムレベルのメトリクスとイベント
ACAはマネージドOpenTelemetryエージェントを提供し、トレース・メトリクス・ログをOTLPでエクスポートできます。前述のとおり追加の計算コストなしで動くため、可観測性のためにサイドカーを自前で建てる必要がありません。
実務では、構造化ログに相関IDを通し、トレースで分散処理を追い、SLO/エラーバジェットで判断する——という設計をそのまま持ち込めます。原則はOpenTelemetryによる三本柱の相関と同じで、ACAは「三本柱を出す土台」をマネージドで提供してくれる、と捉えるのが正確です。
IaCとCI/CD:Bicep / Terraform / GitHub Actions(OIDC)
本番では、アプリを宣言的なコードで定義し、鍵レスCI/CDで出荷します。
Terraform(azurerm)
AWSをTerraformで運用してきたなら、ACAもazurermプロバイダで同じ流儀で書けます。
resource "azurerm_container_app_environment" "main" {
name = "prod-env"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}
resource "azurerm_container_app" "api" {
name = "public-api"
container_app_environment_id = azurerm_container_app_environment.main.id
resource_group_name = azurerm_resource_group.main.name
revision_mode = "Single"
identity { type = "SystemAssigned" }
ingress {
external_enabled = true
target_port = 8080
transport = "auto"
traffic_weight {
latest_revision = true
percentage = 100
}
}
template {
min_replicas = 1 # idleレートを効かせつつ常駐(ゼロスケールの冷たさを避ける)
max_replicas = 20
container {
name = "api"
image = "myregistry.azurecr.io/public-api:2026-06-26-a1b2c3d"
cpu = 0.5
memory = "1.0Gi"
liveness_probe {
transport = "HTTP"
path = "/liveness"
port = 8080
}
readiness_probe {
transport = "HTTP"
path = "/readiness"
port = 8080
}
}
http_scale_rule {
name = "http-rule"
concurrent_requests = 100
}
}
}
Terraformのモジュール設計・ステート分離・ドリフト検知の考え方は、AWSと共通です(Terraformモジュール設計とドリフト検知)。
GitHub Actions × OIDC(鍵レス)
長期シークレット(サービスプリンシパルのパスワード)をGitHubに置くのは負債です。**Microsoft Entraのフェデレーション資格情報(OIDC)**で、鍵を1つも保存せずにデプロイします。
name: deploy-aca
on:
push: { branches: [main] }
permissions:
id-token: write # OIDCトークンの発行に必須
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2 # 鍵レス:client-id/tenant-id/subscription-idのみ
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy revision
run: |
TAG="$(date +%Y-%m-%d)-${GITHUB_SHA::7}" # 一意タグ(latest禁止)
az containerapp update \
--name public-api --resource-group my-rg \
--image myregistry.azurecr.io/public-api:"$TAG"
OIDCによる鍵レスCI/CDの設計思想は、AWS/GCPと完全に同型です(GitHub Actions OIDCで鍵を捨てる)。Azureでは「フェデレーション資格情報」という名前で、同じ「短命トークンを信頼する」仕組みを提供します。
手早く試すだけなら
az containerapp upが便利です。ソースまたはイメージから、環境・レジストリ・アプリを一気に作ってくれます。ただし本番はIaCで。upは学習とPoC、Bicep/Terraformは本番、と使い分けます(YAGNI:最初から本番構成を作り込まず、まず動かして計測してから固める)。
本番投入チェックリスト
ACAを本番に出す前に、この記事の要点を確認用リストにまとめます。
- 環境:本番/ステージングを別環境に。検証環境を90日放置しない(自動削除対策)。
- リソース:CPU/メモリは計測してright-sizing(メモリ=vCPU×2の固定比率)。
latestタグ禁止、一意タグを使う。 - スケール:HTTP/イベント駆動でゼロスケール。Ingress無効ワーカーは
minReplicas≥1かスケールルール必須(自爆防止)。CPU/メモリスケールはゼロにできない点を理解。 - Ingress:公開=External、内部=Internal。240秒超の処理はジョブ/キューへ。
X-Forwarded-Forは最右端のみ信頼し検証する。 - プローブ:startup/liveness/readinessを定義。起動が遅いなら閾値を緩める。
- デプロイ:単一モードでゼロダウンタイム、または複数モードでカナリア/Blue-Green。シークレット更新後はリビジョン再起動を忘れない。
- 回復性:
SIGTERMを30秒以内に処理するグレースフルシャットダウン。状態はコンテナ外(Redis等)へ。ワーカーは冪等に。 - セキュリティ:認証情報はマネージドID+Key Vault参照でコードから消す。
identitySettingsのlifecycleで最小権限。特権コンテナ不可。 - ネットワーク:本番はワークロードプロファイル環境+専用サブネット(
/27)。egressはUDR+Firewallでロックダウン。 - コスト:無料枠(18万vCPU秒・36万GiB秒・200万req)とidleレートを設計に織り込む。
- 可観測性:Log Analytics+マネージドOTelエージェント。相関ID・SLOで運用する。
- IaC/CI/CD:Bicep/Terraformで宣言的に。GitHub Actions × OIDC(フェデレーション資格情報)で鍵レス。
まとめ:Fargateの勘所はACAに移植できる
Azure Container Appsは、**「サーバー管理という最大のコスト(人件費)を消し、本番品質そのものに集中する」**ためのサーバーレス・コンテナ基盤です。Kubernetes・KEDA・Dapr・Envoyという実績あるOSSの上に、証明書・リビジョン・スケール・環境というアプリ運用の概念をマネージドで載せてくれます。
そして本番品質は、結局のところコードと設計の構造で決まります——KEDAのスケールルールとゼロスケールの罠、Ingressの240秒境界とX-Forwarded-Forの検証、SIGTERMを掴むグレースフルシャットダウンと冪等性、マネージドID+Key Vault参照で認証情報を消すこと。これらはAWS Fargateで本番運用してきた勘所と、驚くほど同型です。クラウドが変わっても、「壊れない・止めない・安く・安全に作る」原則は変わりません。
一人×生成AIで、AWSでもAzureでも、速く・安く・安全にコンテナ基盤を本番化する——その設計をお手伝いします。サーバーレスコンテナでの本番構築や、AWSからの移行・マルチクラウド構成のご相談は、お問い合わせからお気軽にどうぞ。