🐙

Azure Developer CLIでアプリコードからIaC自動生成できるの知ってました?

に公開

はじめに

Azure Developer CLI (azd)を使って、アプリのIaCを自動生成する方法を紹介します。

背景

以下の6月のazdアップデート情報でazd infra genという単語を発見しました。ベータ版に昇格されたということで早速試してみます。

https://devblogs.microsoft.com/azure-sdk/azure-developer-cli-azd-june-2025

アプリコードしかない状態からIaCを自動生成して、デプロイまでやってみます。

前提事項

  • アプリがある
    • Dockerfileがある
  • azdインストール済み
  • Azureサブスクリプション作成済み
  • azd auth login済み

ちなみにアプリは以下のような、Microsoft LearnのMCPを利用したAzure資格勉強用のエージェントです。PythonのStreamlitを使っています。

Streamlitアプリの実行画面

本記事の手順をすべて行い、IaCを自動生成した後のアプリコードは以下のようになります。

https://github.com/Tomodo1773/strands-mcp-agent

ちなみに今回はPythonアプリで行いましたが、ほかの言語でも同様にできるようです。フロントエンド(React)&バックエンド(Node.js)のアプリでもできました。

実行環境

  • Windows 11(WSL2)
  • Ubuntu(22.04.5)
  • azd 1.17.2

手順

(Python uv限定)requirements.txtの作成

最初にuv.lockからrequirements.txtを作成します。

この後のazd initではazdがアプリコードを検出してくれるのですが、requirements.txtがないと検出されませんでした。Pythonでも最初からrequirements.txtがある方、そもそもほかの言語の場合は不要です。

以下のコマンドでuv.lockからrequirements.txtを作成します。

cd src; uv export --format requirements-txt --no-editable > requirements.txt

Init作業(azd init)

次にazdを初期化します。このとき、ディレクトリにあるアプリコードを検出してくれます。

azd init

Scan current directoryを選択します。

azd init でカレントディレクトリをスキャンする選択肢

検出されるとDetected inのところにアプリコードのパスが表示されます。

azd init で検出されたアプリのパス

Azure Container Appsにデプロイするね、と書いてありますね。

Confirm and continue initializing my appを選択します。

azd init の最終確認画面

azure.yamlnext-steps.mdが作成されます。

azure.yamlは以下のようになっています。src配下のPythonアプリをDockerfileを使ってAzure Container Appsにデプロイするための設定が書かれています。

# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json

name: strands-mcp-agent
metadata:
    template: azd-init@1.17.2
services:
    src:
        project: src
        host: containerapp
        language: python
        docker:
            path: Dockerfile
resources:
    src:
        type: host.containerapp
        port: 8501

next-steps.mdは以降の手順の解説ですね。GitHubにあげているので興味ある人は見てみてください。

https://github.com/Tomodo1773/strands-mcp-agent/blob/main/next-steps.md

IaC作成(azd infra gen)

次にIaCを作成します。

azd infra gen

azd infra gen コマンドの実行結果

infraディレクトリが作成され、Bicepが自動生成されます。

生成された infra ディレクトリのファイル一覧

最初に書いてあったように、Container AppsにデプロイするためのBicepが生成されます。Container RegistryやApplication Insightsなども自動生成されます。

@description('The location used for all deployed resources')
param location string = resourceGroup().location

@description('Tags that will be applied to all resources')
param tags object = {}


param srcExists bool

@description('Id of the user or app to assign application roles')
param principalId string

@description('Principal type of user or app')
param principalType string

var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = uniqueString(subscription().id, resourceGroup().id, location)

// Monitor application with Azure Monitor
module monitoring 'br/public:avm/ptn/azd/monitoring:0.1.0' = {
  name: 'monitoring'
  params: {
    logAnalyticsName: '${abbrs.operationalInsightsWorkspaces}${resourceToken}'
    applicationInsightsName: '${abbrs.insightsComponents}${resourceToken}'
    applicationInsightsDashboardName: '${abbrs.portalDashboards}${resourceToken}'
    location: location
    tags: tags
  }
}
// Container registry
module containerRegistry 'br/public:avm/res/container-registry/registry:0.1.1' = {
  name: 'registry'
  params: {
    name: '${abbrs.containerRegistryRegistries}${resourceToken}'
    location: location
    tags: tags
    publicNetworkAccess: 'Enabled'
    roleAssignments:[
      {
        principalId: srcIdentity.outputs.principalId
        principalType: 'ServicePrincipal'
        roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
      }
    ]
  }
}

// Container apps environment
module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.4.5' = {
  name: 'container-apps-environment'
  params: {
    logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId
    name: '${abbrs.appManagedEnvironments}${resourceToken}'
    location: location
    zoneRedundant: false
  }
}

module srcIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.1' = {
  name: 'srcidentity'
  params: {
    name: '${abbrs.managedIdentityUserAssignedIdentities}src-${resourceToken}'
    location: location
  }
}
module srcFetchLatestImage './modules/fetch-container-image.bicep' = {
  name: 'src-fetch-image'
  params: {
    exists: srcExists
    name: 'src'
  }
}

module src 'br/public:avm/res/app/container-app:0.8.0' = {
  name: 'src'
  params: {
    name: 'src'
    ingressTargetPort: 8501
    scaleMinReplicas: 1
    scaleMaxReplicas: 10
    secrets: {
      secureList:  [
      ]
    }
    containers: [
      {
        image: srcFetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
        name: 'main'
        resources: {
          cpu: json('0.5')
          memory: '1.0Gi'
        }
        env: [
          {
            name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
            value: monitoring.outputs.applicationInsightsConnectionString
          }
          {
            name: 'AZURE_CLIENT_ID'
            value: srcIdentity.outputs.clientId
          }
          {
            name: 'PORT'
            value: '8501'
          }
        ]
      }
    ]
    managedIdentities:{
      systemAssigned: false
      userAssignedResourceIds: [srcIdentity.outputs.resourceId]
    }
    registries:[
      {
        server: containerRegistry.outputs.loginServer
        identity: srcIdentity.outputs.resourceId
      }
    ]
    environmentResourceId: containerAppsEnvironment.outputs.resourceId
    location: location
    tags: union(tags, { 'azd-service-name': 'src' })
  }
}
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer
output AZURE_RESOURCE_SRC_ID string = src.outputs.resourceId

(私のアプリ特有設定) Bicepの修正

私のアプリではOPENAI_API_KEY,LANGSMITH_API_KEYの環境変数を設定する必要があります。
IaC自動作成もさすがにそれは汲んでくれなかったので、それだけBicepに追記します。

resources.bicep
   @description('Principal type of user or app')
   param principalType string
   
+  @description('OpenAI API Key')
+  param openAiApiKey string
+  
+  @description('LangSmith API Key')
+  param langSmithApiKey string
+  
   var abbrs = loadJsonContent('./abbreviations.json')
   var resourceToken = uniqueString(subscription().id, 
  resourceGroup().id, location)
   
resources.bicep
       scaleMaxReplicas: 10
       secrets: {
         secureList:  [
+          {
+            name: 'openai-api-key'
+            value: openAiApiKey
+          }
+          {
+            name: 'langsmith-api-key'
+            value: langSmithApiKey
+          }
         ]
       }
       containers: [
resources.bicep
               name: 'PORT'
               value: '8501'
             }
+            {
+              name: 'OPENAI_API_KEY'
+              secretRef: 'openai-api-key'
+            }
+            {
+              name: 'LANGSMITH_API_KEY'
+              secretRef: 'langsmith-api-key'
+            }
           ]
         }
       ]
main.parameters.json
         },
         "principalType": {
           "value": "${AZURE_PRINCIPAL_TYPE}"
+        },
+        "openAiApiKey": {
+          "value": "${OPENAI_API_KEY}"
+        },
+        "langSmithApiKey": {
+          "value": "${LANGSMITH_API_KEY}"
         }
       }
   }

(私のアプリ特有設定) azd環境変数の設定

以下のコマンドで環境変数を設定します。

azd env new demo-app
azd env set OPENAI_API_KEY <your-openai-api-key>
azd env set LANGSMITH_API_KEY <your-langsmith-api-key>

デプロイする(azd up)

早速デプロイしてみます。

azd up

subscription name, regionなどを選択します。

azd up コマンドの実行画面

最後に表示されているEndpointにアクセスして、アプリが動いていることを確認します。

無事に開けました。

デプロイされたアプリの実行画面

Azureポータルで確認すると以下のようにリソースができていますね。

Azureポータルで作成されたリソースグループの画面

おわりに

azdを使うとContainer Apps限定とはいえ、アプリコードしかない状態からIaCを自動生成して、デプロイまで簡単にできてしまいます。
ベータ版ということで、まだ機能的には限定的ですが、IaCの自動生成はかなり便利でした。

また、azdにはそもそもazd pipeline configというコマンドでPipelineの設定も自動でできます。

本記事の趣旨と外れるので今回は触れませんでしたが、実際にazd initazd infra genazd pipeline configを一気に試してみましたが、かなり体験がよかったです。特にVive Codingでアプリを簡単に開発できるようになった世界とは相性が良い気がします。

個人的にはアップデートに注目したいなと思います。

以上です。ありがとうございました。

Discussion