# Azure Container Apps CI/CD ガイド：GitHub Actions・OIDC鍵レス・Bicep・Blue/Greenリビジョンで安全に自動デプロイ

> Azure Container AppsのCI/CDを本番品質で組む実装ガイド。azure/container-apps-deploy-action、OIDC（フェデレーション資格情報）による鍵レス認証、ACR pullのマネージドID、Bicepによる宣言的デプロイ、リビジョンのトラフィック分割によるBlue/Green・カナリア、自動ロールバックまでを、Microsoft Learn公式に忠実なYAML/Bicep/az CLIで解説します。

- 公開日: 2026-06-26
- 著者: 友田 陽大
- タグ: Azure, Container Apps, CI/CD, GitHub Actions, Bicep, DevOps, インフラ, セキュリティ
- URL: https://tomodahinata.com/blog/azure-container-apps-cicd-github-actions-oidc-bicep-blue-green-guide

## 要点

- 公式のazure/container-apps-deploy-actionで、Dockerfile/ソース/既存イメージのいずれからもビルド＆デプロイできる。コミットごとに新リビジョンが作られる
- 認証はサービスプリンシパル（AZURE_CREDENTIALS）でも組めるが、本番はOIDC（フェデレーション資格情報）で長期シークレットを1つも置かない鍵レスCI/CDにする
- ACRからのイメージpullはマネージドID（AcrPullロール）が推奨。CIはpush、アプリはpullをそれぞれ最小権限のIDで行う
- デプロイはイメージタグをGitコミットSHAで一意にする（latch禁止）。単一リビジョンモードでゼロダウンタイム、複数モードでトラフィック分割によるBlue/Green・カナリア・即時ロールバック
- 本番はBicep/Terraformで宣言的に。az containerapp upは学習とPoC、IaCは本番、と使い分ける

---

デプロイは「動けばいい」ではなく、**安全に・繰り返し可能に・鍵を漏らさずに**回す仕組みが本番品質を決めます。長期シークレットをGitHubに置く、`latest`タグで上書きする、失敗時に戻せない——どれも事故の温床です。

この記事は、[Microsoft LearnのGitHub Actionsドキュメント](https://learn.microsoft.com/en-us/azure/container-apps/github-actions)に忠実に、Azure Container Apps（ACA）の**本番CI/CD**を組み立てます。私はAWSで[OIDCによる鍵レスCI/CD](/blog/github-actions-oidc-keyless-cicd-aws-gcp-guide)と[FargateのBlue/Greenデプロイ](/blog/aws-ecs-fargate-cicd-blue-green-codedeploy-github-actions-guide)を本番運用してきました。「短命トークンを信頼する」「失敗したら切り替えない」というデプロイの原則は、Azureでも同型です。ACA全体は [Azure Container Apps 本番運用ガイド](/blog/azure-container-apps-production-guide) を参照してください。

---

## 全体像：コミット → 新リビジョン

> Azure Container Apps allows you to use GitHub Actions to publish revisions to your container app. As commits are pushed to your GitHub repository, a workflow is triggered which updates the container image in the container registry. Azure Container Apps creates a new revision based on the updated container image.（— [Publish revisions with GitHub Actions](https://learn.microsoft.com/en-us/azure/container-apps/github-actions)）

流れはシンプル：**コミット → ワークフロー起動 → イメージをビルドしてレジストリへ → ACAが新リビジョンを作成**。ACAの[リビジョン](/blog/azure-container-apps-production-guide)は不変スナップショットなので、デプロイ＝新リビジョンへの切り替えです。

---

## 公式アクション：azure/container-apps-deploy-action

> To build and deploy your container app, you add the `azure/container-apps-deploy-action` action to your GitHub Actions workflow.

このアクションは3つのシナリオに対応します。

- **Dockerfileからビルドしてデプロイ**
- **Dockerfile無しでソースからビルドしてデプロイ**（.NET・Java・Node.js・PHP・Python）
- **既存のコンテナイメージをデプロイ**

### 既存イメージをデプロイ（最も制御しやすい）

ビルドを別ステップで行い、**一意タグ（コミットSHA）**のイメージをデプロイする形が、本番では最も追跡しやすい：

```yaml
- name: Build and deploy Container App
  uses: azure/container-apps-deploy-action@v1
  with:
    acrName: myregistry
    containerAppName: my-container-app
    resourceGroup: my-rg
    imageToDeploy: myregistry.azurecr.io/app:${{ github.sha }}
```

公式も警告しています。

> If you're building a container image in a separate step, make sure you use a unique tag such as the commit SHA instead of a stable tag like `latest`.

**`latest`は使わず、`${{ github.sha }}`等で一意に**。これはキャッシュ問題と「どのリビジョンが何のコードか分からない」事故を防ぎます。

---

## 認証：サービスプリンシパル vs OIDC（鍵レス）

### 最短だが負債になりうる：サービスプリンシパル

公式のスターターは、サービスプリンシパルのJSON資格情報を`AZURE_CREDENTIALS`シークレットに置く方式です。

```azurecli
az ad sp create-for-rbac --name my-app-credentials \
  --role contributor \
  --scopes /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/my-container-app-rg \
  --json-auth
```

動きますが、**長期の資格情報（パスワード）をGitHubに保存**します。漏洩リスクとローテーション負担が残る。学習やPoCならよいですが、本番では次のOIDCにします。

### 本番の正解：OIDC（フェデレーション資格情報）

GitHub ActionsのOIDCトークンを**Microsoft Entraのフェデレーション資格情報**で信頼し、**長期シークレットを1つも置かない**。`azure/login`は`client-id`/`tenant-id`/`subscription-id`だけで認証します。

```yaml
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

      - name: Log in to Azure (OIDC, keyless)
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Build and deploy Container App
        uses: azure/container-apps-deploy-action@v1
        with:
          acrName: myregistry
          containerAppName: my-container-app
          resourceGroup: my-rg
          imageToDeploy: myregistry.azurecr.io/app:${{ github.sha }}
```

フェデレーション資格情報は、対象のEntraアプリ／マネージドIDに「このリポジトリの`main`ブランチからのトークンを信頼する」という条件（subject）を登録するだけ。**鍵の保存・ローテーションが消えます**。考え方はAWSのIAM OIDCプロバイダと完全に同型です（[GitHub Actions OIDCで鍵を捨てる](/blog/github-actions-oidc-keyless-cicd-aws-gcp-guide)）。

---

## レジストリ認証：push と pull を分ける

CI（push）とアプリ（pull）は、それぞれ別の最小権限IDで認証します。

> To pull images, Azure Container Apps uses either managed identity (recommended) or admin credentials to authenticate with the Azure Container Registry.（— [github-actions](https://learn.microsoft.com/en-us/azure/container-apps/github-actions)）

**アプリのイメージpullはマネージドID（推奨）**。アプリのマネージドIDに`AcrPull`ロールを付与し、レジストリ設定で`--identity`を指定します。

```azurecli
# アプリにシステム割当IDを付与
az containerapp identity assign \
  --name my-container-app --resource-group my-rg --system-assigned

# そのIDにACRのAcrPullロールを付与（pull専用の最小権限）
az role assignment create \
  --assignee <MANAGED_IDENTITY_PRINCIPAL_ID> \
  --role AcrPull --scope <ACR_RESOURCE_ID>

# レジストリをマネージドID認証に設定
az containerapp registry set \
  --name my-container-app --resource-group my-rg \
  --server myregistry.azurecr.io --identity system
```

これで**レジストリのパスワードをどこにも持たない**。CIはpush権限、アプリはpull権限だけ——責務と権限が分離されます（最小権限の原則）。

> GHCR等の非ACRレジストリも使えます。その場合は`az containerapp registry set --server ghcr.io`で認証情報（PATの`read:packages`）を設定します。公開イメージでも認証設定は必要です。

---

## デプロイ戦略：Blue/Green・カナリア・ロールバック

### 単一リビジョンモード：ゼロダウンタイム（既定）

何も設定しなければ、ACAは**新リビジョンが準備完了（全プローブ通過＋旧台数までスケール）してから自動で全切替**します。失敗すれば旧リビジョンに留まる。多くのサービスはこれで十分です。AWS Fargateの「ローリング＋デプロイサーキットブレーカー」と同じ安全側の挙動です。

### 複数リビジョンモード：トラフィック分割

慎重に出したいなら複数リビジョンモードにし、**トラフィックを%で割り当て**ます。

```bash
# 複数リビジョンモードに（一度だけ）
az containerapp revision set-mode --name my-api --resource-group my-rg --mode multiple

# カナリア：新版に10%だけ
az containerapp ingress traffic set --name my-api --resource-group my-rg \
  --revision-weight my-api--green=10 my-api--blue=90

# 問題なければ昇格（Blue/Green切替）
az containerapp ingress traffic set --name my-api --resource-group my-rg \
  --revision-weight my-api--green=100

# 異常を検知したら即ロールバック（旧版へ100%戻す）
az containerapp ingress traffic set --name my-api --resource-group my-rg \
  --revision-weight my-api--blue=100
```

**ロールバックが「旧リビジョンに100%戻す」だけ**で済むのが、不変リビジョンモデルの強みです。旧版はアクティブに残っているので、再ビルドも再デプロイも要らず、**秒で戻せる**。

> パイプラインに組み込むなら、デプロイ後にスモークテスト→メトリクス監視→`traffic set`で昇格、という段階を自動化します。SLO違反を検知したら自動でロールバックする、というガード（[症状ベースのアラート設計](/blog/opentelemetry-observability-production-tracing-metrics-logs)）を足すと、本番品質が一段上がります。

---

## IaC：Bicep / Terraform で宣言的に

クリック操作や`az`の手打ちは再現性がなく、技術的負債になります。本番は**宣言的なコード**で。

### Bicep（Azureネイティブ）

```bicep
param image string   // CIから一意タグを渡す（例: myregistry.azurecr.io/app:<sha>）

resource app 'Microsoft.App/containerApps@2025-02-02-preview' = {
  name: 'my-api'
  location: location
  identity: { type: 'SystemAssigned' }
  properties: {
    managedEnvironmentId: environmentId
    configuration: {
      activeRevisionsMode: 'single'
      ingress: { external: true, targetPort: 8080, transport: 'auto' }
      registries: [
        { server: 'myregistry.azurecr.io', identity: 'system' }  // マネージドIDでpull
      ]
    }
    template: {
      containers: [
        { name: 'api', image: image, resources: { cpu: json('0.5'), memory: '1.0Gi' } }
      ]
      scale: { minReplicas: 1, maxReplicas: 20 }
    }
  }
}
```

CIからは`az deployment group create --parameters image=...:${{ github.sha }}`でこのBicepを流す。**インフラもアプリも同じパイプラインで宣言的にデプロイ**できます。Terraform（`azurerm_container_app`）派なら同じ構成をHCLで書けます（[本番運用ガイドのTerraform例](/blog/azure-container-apps-production-guide)）。

### スターターワークフローの自動生成

ゼロから書くのが面倒なら、`az containerapp github-action add`系のCLIでスターターワークフローを生成できます。まず動かして、そこから本番要件（OIDC・IaC・トラフィック分割）に育てる——KISS/YAGNIの順序です。

> `az containerapp up --source . --ingress external` は、リソース作成・イメージビルド・レジストリ保存・デプロイを一気にやる学習用の最短コマンド。**PoCは`up`、本番はIaC**と使い分けます。

---

## CI/CDチェックリスト

- [ ] **認証はOIDC（フェデレーション資格情報）**で鍵レス。`AZURE_CREDENTIALS`の長期シークレットを本番で使わない。
- [ ] **イメージタグはコミットSHA**で一意。`latest`禁止。
- [ ] **ACR pullはマネージドID（AcrPull）**。CIのpushとアプリのpullを別IDで最小権限に。
- [ ] **デプロイは単一モードでゼロダウンタイム**、慎重に出すなら複数モードで**カナリア→昇格**。
- [ ] **ロールバックは`traffic set`で旧リビジョンへ**（秒で戻る）。
- [ ] **インフラはBicep/Terraform**で宣言的に。手打ち・クリックを排除。
- [ ] デプロイ後に**スモークテスト＋SLO監視**、違反で自動ロールバック。

---

## まとめ

ACAのCI/CDは、`azure/container-apps-deploy-action`でビルド＆デプロイし、**コミットごとに不変リビジョンを作る**シンプルなモデルです。本番品質の鍵は4つ——**OIDC鍵レス認証**、**コミットSHAの一意タグ**、**マネージドIDによる最小権限pull**、**トラフィック分割によるBlue/Green＋秒で戻せるロールバック**。これらはAWSで鍵レスCI/CDとBlue/Greenを運用してきた原則と、語彙が違うだけで同型です。

鍵レスCI/CD・Blue/Greenデプロイ・IaC化のご相談は[お問い合わせ](/contact)へ。本番運用全体は [Azure Container Apps 本番運用ガイド](/blog/azure-container-apps-production-guide) をどうぞ。
