はじめに
Azure Container Apps学び直し-エンタープライズ構成【前編】では閉域ネットワークを、Azure Container Apps学び直し-エンタープライズ構成【補足】ではアプリと基盤の仕様を解説しました。
後編では、別テナントの情報を読むための認証と、コンテナーイメージのビルドやデプロイについて書きます。
が、タイトルに「Azure Container Apps」と付けているのにも関わらず、ACAに関わる部分が大変少なくなってしまいました。スミマセン。
アプリ登録
マルチテナントのアプリ登録
このアプリは管理テナントで動き、プロジェクトごとの別テナント(監視対象テナント)のAzure情報を読みに行く。
そのためMicrosoft Entra IDのアプリ登録は単一テナントではなく、マルチテナント(AzureADMultipleOrgs)にしている。
# 管理テナントでアプリ登録を作成
az login --tenant '<管理テナントID>'
az ad app create --display-name '<アプリ名>' --sign-in-audience AzureADMultipleOrgs --query appId --output tsv
アプリ登録は管理テナントにだけ行えばよく、これによりアプリケーションIDが付与されるので、管理対象テナントにアプリのサービスプリンシパルを作成する。
# 対象テナントで、そのアプリのサービスプリンシパルを作成
az login --tenant '<管理対象テナントID>'
az ad sp create --id '<アプリのアプリケーションID>'
管理対象テナントで別のアプリ登録やシークレットを作る必要はない。
管理対象テナントのエンタープライズ アプリケーションに、管理テナントのアプリケーション IDのアプリが表示されていれば、サービスプリンシパルが作成されている状態だが、ない場合は、管理対象テナントの管理者権限を持つアカウントにてCloud Shellなどで az ad sp create を実行する。
特定のテナントのみ許可(プレビュー)
マルチテナントアプリケーションは、アプリケーションIDを知る他テナントの管理者がサービスプリンシパルを作成して利用できるが、「特定のテナントのみ許可する (プレビュー)」を使うとアプリ登録側で利用可能なテナントを制限できるようになる。

設定箇所:
- (管理テナント)Microsoft Entra ID → アプリの登録 → <対象のアプリ名> → Authentication (Preview) → Supported accountsタブ → 特定のテナントのみを許可する (プレビュー)
クロステナント認証の仕組みと資格情報
クロステナント認証とは、管理テナントで動く収集ジョブ/Webアプリが、管理対象テナントのAzureリソースを読み取るための認証である。
管理テナントのアプリは、資格情報を使って対象テナントのEntra IDからトークンを取得し、そのトークンでリソースのAPIを呼び出す。
資格情報にクライアントシークレットを使う場合、シークレットの保管や期限管理、定期的なローテーションが必要になるが、アプリ登録に実行環境のマネージドIDを信頼させるフェデレーションを設定すれば、シークレットを発行・保管・更新せずにトークンを取得でき、漏洩や期限切れのリスクと運用負荷を減らせる。
クライアントシークレットを使う場合
収集ジョブ/Webアプリは、実行環境のマネージドIDでKey Vaultからクライアントシークレットを取得し、そのシークレットを使って管理対象テナントのEntra IDからAPI用トークンを取得する。
マネージドIDとフェデレーションを使う場合
事前に管理テナントのアプリ登録に収集ジョブ/WebアプリのマネージドIDを信頼するフェデレーション資格情報を設定する。実行時はマネージドIDで取得したトークンをアプリの資格情報として提示し、対象テナントのEntra IDからAPI用トークンを取得する。
他テナントからの情報取得
クライアントシークレット方式の実装例
収集ジョブとWebアプリは、マネージドIDでKey Vaultからシークレットを取得する。取得したシークレットと監視アプリのアプリケーションIDを使い、対象テナント用の ClientSecretCredential を作成する。
def credential() -> DefaultAzureCredential:
return DefaultAzureCredential(exclude_interactive_browser_credential=True)
def monitoring_client_secret(settings: Settings) -> str | None:
if not settings.monitoring_key_vault_uri or not settings.monitoring_client_secret_name:
return None
secret = SecretClient(settings.monitoring_key_vault_uri, credential()).get_secret(
settings.monitoring_client_secret_name
)
return secret.value
def target_credential(target: TenantTarget, settings: Settings) -> TokenCredential:
secret = monitoring_client_secret(settings)
if settings.monitoring_client_id and secret:
return ClientSecretCredential(
tenant_id=target.tenant_id,
client_id=settings.monitoring_client_id,
client_secret=secret,
)
return credential()
管理対象テナント側の権限設定
管理対象のAzureリソースを読むには、対象のサブスクリプションなど必要なスコープにAzure RBACロールを割り当てる。Microsoft Graphからライセンス情報などを取得するには、アプリ登録のAPIアクセス許可に必要な「アプリケーションのアクセス許可」を追加する必要がある。
シークレット管理
クライアントシークレットはKey Vaultに保存し、収集ジョブとWebアプリはそれぞれのマネージドIDで取得する。
Key Vaultへの登録はKey Vaultのプライベートエンドポイントに接続できる仮想マシンから行う。実行者には作業中だけ Key Vault Secrets Officer を付与し、登録後に削除する。
Connect-AzAccount -Tenant '<管理テナントID>' -Subscription '<管理サブスクリプションID>' | Out-Null
$secret = Read-Host 'Monitoring app client secret' -AsSecureString
try {
Set-AzKeyVaultSecret -VaultName '<Key Vault名>' -Name '<シークレット名>' `
-SecretValue $secret -ErrorAction Stop | Out-Null
} finally {
$secret.Dispose()
}
イメージのビルドとCI/CD
az acr build は、Azure Container Registry(ACR)が管理するビルドエージェントを使い、DockerイメージのビルドとACRへのプッシュを一度に行うコマンドである。
(ローカル環境にDockerをインストールする必要がない)
ACRのパブリックアクセスと信頼されたサービスのバイパスを無効にし、プライベート エンドポイント経由の接続に限定する構成では、通常のACR TasksエージェントからACRへ接続できない。
このためビルド用の仮想マシンをACRのプライベート エンドポイントに到達できる仮想ネットワーク内に配置し、その上でビルドとプッシュを行う。
| 方式 | 特徴・注意点 |
|---|---|
| ACR Tasks(ACRが管理するビルドエージェント) | タスクの認証・権限に加えてネットワークバイパスが必要。システム割り当てマネージドIDを使う場合は networkRuleBypassAllowedForTasks の許可も必要 |
| ACR Tasks(専用エージェントプール) | プレビュー機能でACR Premiumが必要。Linuxのみ対応し、東日本リージョンは2026年10月時点で未提供 |
| 仮想ネットワーク内のVMでDocker build・push | ACRのプライベート エンドポイントに接続できるVM上でイメージをビルドし、ACRへプッシュする |
ビルドとプッシュ
ビルド用LinuxマシンからACRの名前解決を確認する。
レジストリ本体(<ACR名>.azurecr.io)と同リージョンのデータエンドポイント(<ACR名>.<リージョン>.data.azurecr.io)を確認する。
registry_host='<ACR名>.azurecr.io'
data_host='<ACR名>.<リージョン>.data.azurecr.io'
getent hosts "$registry_host"
getent hosts "$data_host"
curl --connect-timeout 5 -sS -o /dev/null "https://$registry_host"
curl --connect-timeout 5 -sS -o /dev/null "https://$data_host"
各FQDNがプライベートIPに解決され、TCP 443でHTTPS接続できることを確認する。
接続を確認した同じマシンでイメージをビルドする。
sudo docker build --platform linux/amd64 \
--file tenant-monitor/Dockerfile \
--tag "<ACR名>.azurecr.io/<イメージ名>:<タグ>" .
プッシュにはビルド用LinuxマシンのマネージドIDを使う。ACRのトークンを取得してDockerへ標準入力で渡す。
docker login はトークンをDockerの設定ファイルに保存することがあるため、処理後にログアウトし、CIでは実行ごとに資格情報を破棄する。
set -euo pipefail
registry='<ACR名>.azurecr.io'
access_token=''
cleanup() {
sudo docker logout "$registry" >/dev/null 2>&1 || true
unset access_token
}
trap cleanup EXIT
az login --identity --output none
access_token=$(az acr login --name '<ACR名>' --expose-token --query accessToken --output tsv)
printf '%s' "$access_token" | sudo docker login "$registry" \
--username '00000000-0000-0000-0000-000000000000' --password-stdin
sudo docker push "$registry/<イメージ名>:<タグ>"
--expose-token で得たトークンを使う場合、Dockerのユーザー名は上記のゼロGUIDを使う。
CI/CD
イメージのビルドとプッシュはビルド用Linuxマシンで手動実行する。
CI/CD化する場合も、ビルド用LinuxマシンをバックエンドVNet内のセルフホステッドランナーとして使い、ビルド、プッシュ、デプロイを行う。
CI/CDパイプライン自体は今回の環境では未構築。
CI/CDと実行環境の通信要件
CIサービスからジョブを受け取り、ビルドなどの処理を実行して結果を返す仮想マシンまたはエージェントをランナーと呼ぶが、ソースコード、ベースイメージ、依存パッケージの取得と、CIサービスへのログ・実行結果の送信に必要なアウトバウンド通信を行う。本構成ではNAT Gatewayで経路を用意しているが、一般的なHub-Spoke構成であれば、Azure Firewallで制御する。
Azure DevOpsのMicrosoftホスト型エージェントは、VNet内のACR用プライベート エンドポイントに接続できない。そのため、ビルド用LinuxマシンをVNet内のセルフホステッドランナーとして使う(ACRのプライベート リンクの制約)。
| 送信元 | 送信先 | 用途 |
|---|---|---|
| ビルド用Linuxマシン | GitHub/Azure DevOps | ソース取得、ジョブ取得、ログ・実行結果の送信 |
| ビルド用Linuxマシン | ベースイメージ配信元、PyPIなど | Dockerイメージのベース取得、依存パッケージのダウンロード。ミラーを使う場合はミラーを宛先とする |
| ビルド用Linuxマシン | Azure Instance Metadata Service | マネージドIDのトークン取得 |
| ビルド用Linuxマシン(ワークロードIDフェデレーションを使用) | Microsoft Entra ID | フェデレーション資格情報によるトークン交換 |
| ビルド用Linuxマシン | Azure Resource Manager | Azureリソースのデプロイ・更新 |
| ビルド用Linuxマシン | ACR用プライベート エンドポイント | ビルドしたイメージのプッシュ。レジストリとデータエンドポイントの名前解決が必要 |
| Container Apps環境(バックエンドVNet統合) | ACR用プライベート エンドポイント | 実行時のコンテナー イメージ取得 |
デプロイ
一覧表示アプリと収集ジョブの更新
通常の更新は、新しいタグでbuild/pushしてから、イメージタグを変えて再デプロイする。build/pushは前章の通りビルド用Linuxマシンで行い、再デプロイ以降のコマンドはAzure Resource Managerへの操作として実行する。この再デプロイで、一覧表示アプリと収集ジョブの両方が新しいイメージになる。一覧表示アプリはSingleモード(Bicepの既定)のため、新リビジョンの準備ができた時点で全トラフィックが切り替わる。いきなり反映せず、what-if で差分を確認してから流す。
az deployment group what-if --resource-group '<リソースグループ名>' --template-file '<テンプレートファイル>' `
--parameters "imageTag=$imageTag"
az deployment group create --name '<デプロイ名>' --resource-group '<リソースグループ名>' --template-file '<テンプレートファイル>' `
--parameters "imageTag=$imageTag"
ジョブは次のスケジュールを待たずに、手動で起動して確認できる。
az containerapp job start --name '<収集ジョブ名>' --resource-group '<リソースグループ名>'
az containerapp job execution list --name '<収集ジョブ名>' --resource-group '<リソースグループ名>' --output table
未検証の参考情報
パイプライン関連
認証・権限例
パイプラインは未構築のため、以下はCI/CD化する場合の認証方式とRBACの例である。
GitHub ActionsではOIDC、Azure DevOpsではワークロードIDフェデレーション、ビルド用Linuxマシン上のランナーではマネージドIDを使い、信頼元を限定する。デプロイ用IDは監視アプリのIDと分離する。
| 用途 | 付与する対象 | 必要な権限 |
|---|---|---|
| イメージのpush | ACR | AcrPush |
| アプリ/ジョブの更新、トラフィック変更 | 対象のContainer App/Container Apps Job | アプリは Container Apps Contributor、ジョブは Container Apps Jobs Contributor。トラフィック変更はアプリの権限で行う |
| Bicepで基盤を更新 | デプロイ先のリソース グループ |
Contributor。BicepでRBAC割り当ても作成する場合は、対象スコープに Role Based Access Control Administrator などのロール割り当て権限を別途付与する |
| 実行時のイメージpull | アプリ/ジョブのマネージドID | ACRに AcrPull
|
ACRを「RBAC Registry + ABAC Repository Permissions」モードにした場合、pushには Container Registry Repository Writer、pullには Container Registry Repository Reader を対象リポジトリのスコープで付与する。
参考:Azure での OpenID Connect の構成、Azure 資源管理者サービス接続を使用します。 - Azure Pipelines | Microsoft Learn、Azure Container Registry での Azure 属性ベースのアクセス制御 (Azure ABAC) リポジトリのアクセス許可 - Azure Container Registry | Microsoft Learn
手順例
- ソース取得、テスト、イメージbuildを行い、コミットIDなどの一意なタグでACRへpushする。同じリリース用タグは上書きしない
- 管理APIから新しいイメージを指定してデプロイする。実行時のpullはACA自身が行うため、ACAからACRへのPE経路・DNS・pull権限も確認する
- Application Gatewayの事前確認用ホスト名から新リビジョンを確認する。
/healthzだけでなく画面とAPIも検証する - 承認後にトラフィックを切り替え、Application Gateway経由でも確認する。ジョブは実行結果と保存データを別に確認する
3は後述のブルーグリーン/カナリアで使う手順である。Singleモードでは、更新後に通常URLで検証する。
ブルーグリーン/カナリアリリース
ブルーグリーンとカナリアは、ACAのリビジョンとトラフィック分割で実現する。
対象はアプリでジョブは対象外である。
Container AppsのMultipleリビジョンモードでは、リビジョンにラベルを割り当てると、そのリビジョン専用のURLが発行される。このURLを「ラベルURL」と呼び、段階リリースではトラフィック切り替え前の動作確認に使う。
旧版(ブルー)のリビジョン名を控え、モードをMultipleにして旧版に100%を固定してから、新タグのイメージで新リビジョン(グリーン)を作る。
$blue = az containerapp show --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' --query properties.latestReadyRevisionName --output tsv
$green = "<一覧表示アプリ名>--$newTag"
az containerapp revision set-mode --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' --mode multiple
az containerapp ingress traffic set --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' --revision-weight "$blue=100"
az containerapp update --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' `
--image '<ACR名>.azurecr.io/<イメージ名>:<新しいタグ>' --revision-suffix $newTag
az containerapp ingress traffic set --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' --revision-weight "$blue=100" "$green=0"
az containerapp revision label add --name '<一覧表示アプリ名>' --resource-group '<リソースグループ名>' --revision $green --label '<ラベル名>'
旧版を先に100%で固定する。
固定せずに update すると、新リビジョンにトラフィックが流れてしまう。ラベルURLは https://<一覧表示アプリ名>---<ラベル名>.<ACA環境の既定ドメイン> の形式で、トラフィックが0%でも新版に直接アクセスできる。
ACAに直接到達できればラベルURLに対してアクセスし動作確認を行うが、本構成での事前確認はDMZのApplication Gatewayを経由する。
(DMZ VNetのApplication GatewayからACAのプライベートエンドポイントに対してのみ接続経路を用意しており、バックエンドVNetのビルド用Linuxマシンからの経路がないため)
Application Gatewayに事前確認用ホスト名のリスナーとルールを追加し、ラベルURLをバックエンドに設定すれば、事前確認用ホストのアクセスだけを新リビジョンへ転送できる。バックエンドHTTP設定のホスト名にもラベルURLのFQDNを指定し、DMZ VNetのDNSでACA用プライベートIPに解決する。
参考:Azure Container Apps の変更を更新してデプロイする - Microsoft Learn、Azure Application Gateway バックエンド設定の構成 - Microsoft Learn
切り替えは、重みを変えるだけである。
# ブルーグリーン:新版へ100%
az containerapp ingress traffic set ... --revision-weight "$blue=0" "$green=100"
# カナリア:10% → 50% → 100% と段階的に
az containerapp ingress traffic set ... --revision-weight "$blue=90" "$green=10"
# ロールバック:旧版へ100%
az containerapp ingress traffic set ... --revision-weight "$blue=100" "$green=0"
カナリアの各段階では、HTTP 5xx、応答時間、正常性、リビジョン別のログ(az containerapp logs show --revision $green)を新旧で比べる。次の段階に進むまでの観測時間や許容値は、事前に決めておく。採用が決まったら、保持期間が過ぎてから旧版を az containerapp revision deactivate で非アクティブ化する。0%のリビジョンにも費用がかかるためである。
まとめ
ACAを通じてエンタープライズレベルの構成の一例を組み、それらの解説をしてきました。
ここまでお読み頂きありがとうございました。
