使用Azure Developer CLI将Aspire项目部署到Azure Container Apps(深入指南)

Azure Developer CLI (azd) 已扩展为支持部署Aspire项目。 使用本指南逐步完成创建和部署 Aspire 项目到 Azure Container Apps 的过程,并使用 Azure Developer CLI。 在本教程中,你将了解以下概念:

  • 探索azd如何与Aspire项目集成工作
  • 使用 azd 在 Azure 上为 Aspire 项目配置和部署资源
  • 使用 azd 生成 Bicep 基础结构和其他模板文件

先决条件

若要使用 Aspire,需要在本地安装以下各项:

有关详细信息,请参阅 Aspire 设置和工具以及 Aspire SDK

你还需要在本地安装。 常见的安装选项包括:

winget install microsoft.azd

Azure Developer CLI集成的工作原理

工作流 azd init 为 Aspire 项目提供自定义支持。 下图说明了此流程的概念以及azd和Aspire的集成方式:

部署 Aspire 项目时“azd”内部处理的插图。

  1. azd 针对 Aspire 项目时,它会通过特殊命令(dotnet run --project AppHost.csproj --output-path manifest.json --publisher manifest)启动 AppHost,从而生成 Aspire 清单文件
  2. 清单文件通过 azd provision 子命令逻辑处理,以仅在内存中生成 Bicep 文件(默认情况下)。
  3. 生成 Bicep 文件后,将使用 ARM API 触发部署,目标是前面提供的订阅和资源组。
  4. 配置基础 Azure 资源后, azd deploy 将执行子命令逻辑,该逻辑使用相同的 Aspire 清单文件。
  5. 作为部署 azd 的一部分,调用 dotnet publish 使用 .NET内置容器发布支持来生成容器映像。
  6. 生成容器映像后 azd ,它会将它们推送到预配阶段创建的 ACR 注册表。
  7. 最后,在容器映像位于 ACR 中后, azd 使用 ARM 更新资源以开始使用新版本的容器映像。

注释

azd 此外,还可以将生成的 Bicep 输出到项目的 infra 文件夹中,相关信息可在 从 Aspire 应用模型生成 Bicep 部分了解。

配置和部署 Aspire 入门应用

本部分中的步骤演示如何创建 Aspire 启动应用并处理预配以及将应用资源部署到 Azure 使用 azd

创建Aspire起始应用

使用Aspire命令创建新dotnet new项目。 还可以使用Visual Studio创建项目。

dotnet new aspire-starter --use-redis-cache -o AspireSample
cd AspireSample
dotnet run --project AspireSample.AppHost\AspireSample.AppHost.csproj

前面的命令基于Aspire模板创建新aspire-starter项目,其中包含对缓存的Redis依赖项。 它运行 Aspire 项目,验证一切是否正常工作。

初始化模板

  1. 打开新的终端窗口,并 cd 进入解决方案的 Aspire 目录中。

  2. 执行 azd init 命令以使用 azd初始化项目,这将检查本地目录结构并确定应用的类型。

    azd init
    

    有关 azd init 命令的详细信息,请参阅 azd init

  3. 提示你使用三个应用初始化选项时,选择azd

    ? How do you want to initialize your app?  [Use arrows to move, type to filter]
    > Use code in the current directory
      Select a template
      Create a minimal project
    
  4. 扫描目录后, azd 提示你确认它找到了正确的 AspireAppHost 项目。 选择确认并继续初始化我的应用选项。

    Detected services:
    
      .NET (Aspire)
      Detected in: D:\source\repos\AspireSample\AspireSample.AppHost\AspireSample.AppHost.csproj
    
    azd will generate the files necessary to host your app on Azure using Azure Container Apps.
    
    ? Select an option  [Use arrows to move, type to filter]
    > Confirm and continue initializing my app
      Cancel and exit
    
  5. 输入环境名称,该名称用于命名 Azure 中预配的资源,并管理不同的环境,例如 devprod

    Generating files to run your app on Azure:
    
      (✓) Done: Generating ./azure.yaml
      (✓) Done: Generating ./next-steps.md
    
    SUCCESS: Your app is ready for the cloud!
    You can provision and deploy your app to Azure by running the azd up command in this directory. For more information on configuring your app, see ./next-steps.md
    

azd 生成多个文件并将其放入工作目录中。 这些文件包括:

  • azure.yaml:描述应用的服务,例如 Aspire AppHost 项目,并将其映射到 Azure 资源。
  • .azure/config.json:配置文件,告知 azd 当前活动环境是什么。
  • .azure/aspireazddev/.env:包含特定于环境的覆盖项。

azure.yaml 文件具有以下内容:

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

name: AspireSample
services:
  app:
    language: dotnet
    project: .\AspireSample.AppHost\AspireSample.AppHost.csproj
    host: containerapp

资源命名

创建新 Azure 资源时,必须遵循命名要求。 对于 Azure Container Apps,名称长度必须为 2-32 个字符,由小写字母、数字和连字符组成。 名称必须以字母开头,以字母数字字符结尾。

有关详细信息,请参阅资源的命名规则和限制Azure

初始部署

  1. 若要部署 Aspire 项目,请向 Azure AD 进行身份验证以调用 Azure 资源管理 API。

    azd auth login
    

    上一个命令将启动浏览器以对命令行会话进行身份验证。

  2. 进行身份验证后,从 AppHost 项目目录运行以下命令来预配和部署应用程序。

    azd up
    

    重要

    若要将容器映像推送到 Azure 容器注册表(ACR),需要具有 Microsoft.Authorization/roleAssignments/write 访问权限。 这可以通过在注册表上启用 管理员用户 来实现。 打开 Azure 门户,导航到 ACR 资源/设置/访问密钥,然后选择 “管理员用户 ”复选框。 有关详细信息,请参阅 “启用管理员用户”。

  3. 出现提示时,请选择应将资源部署到的订阅和位置。 选择这些选项后, Aspire 将部署项目。

    By default, a service can only be reached from inside the Azure Container Apps environment it is running in. Selecting a service here will also allow it to be reached from the Internet.
    ? Select which services to expose to the Internet webfrontend
    ? Select an Azure Subscription to use:  1. <YOUR SUBSCRIPTION>
    ? Select an Azure location to use: 1. <YOUR LOCATION>
    
    Packaging services (azd package)
    
    
    Provisioning Azure resources (azd provision)
    Provisioning Azure resources can take some time.
    
    Subscription: <YOUR SUBSCRIPTION>
    Location: <YOUR LOCATION>
    
      You can view detailed progress in the Azure Portal:
      <LINK TO DEPLOYMENT>
    
      (✓) Done: Resource group: <YOUR RESOURCE GROUP>
      (✓) Done: Container Registry: <ID>
      (✓) Done: Log Analytics workspace: <ID>
      (✓) Done: Container Apps Environment: <ID>
    
    SUCCESS: Your application was provisioned in Azure in 1 minute 13 seconds.
    You can view the resources created under the resource group <YOUR RESOURCE GROUP> in Azure Portal:
    <LINK TO RESOURCE GROUP OVERVIEW>
    
    Deploying services (azd deploy)
    
      (✓) Done: Deploying service apiservice
      - Endpoint: <YOUR UNIQUE apiservice APP>.azurecontainerapps.io/
    
      (✓) Done: Deploying service webfrontend
      - Endpoint: <YOUR UNIQUE webfrontend APP>.azurecontainerapps.io/
    
    Aspire Dashboard: <LINK TO DEPLOYED Aspire DASHBOARD>
    
    SUCCESS: Your up workflow to provision and deploy to Azure completed in 3 minutes 50 seconds.
    

    命令 azd 的最后一行输出是指向 Azure 门户的链接,显示了所有已部署的 Azure 资源:

    显示已部署资源的门户屏幕截图 Azure 。

此应用程序中部署了三个容器:

  • webfrontend:包含初学者模板中 Web 项目的代码。
  • apiservice:包含初学者模板中 API 服务项目中的代码。
  • cache:一个 Redis 容器映像,用于向前端提供缓存。

就像在本地开发中一样,已自动处理连接字符串的配置。 在这种情况下, azd 负责解释应用程序模型并将其转换为相应的部署步骤。 例如,请考虑注入到容器中的 webfrontend 连接字符串和服务发现变量,以便它知道如何连接到 Redis 缓存和 apiservice

Webfrontend 容器应用中环境变量的屏幕截图。

有关项目如何处理 Aspire 连接字符串和服务发现的详细信息,请参阅 Aspire 业务流程概述

部署应用程序更新

azd up执行命令时,底层Azure资源被预配,生成容器映像,并将其部署到托管Aspire项目的容器应用。 通常,一旦开发正在进行,并且 Azure 部署资源时,每次更新代码时,都不需要预配 Azure 资源,这对于开发人员内部循环尤其如此。

若要加快代码更改的部署速度, azd 支持在容器映像中部署代码更新。 这是通过使用azd deploy命令完成的:

azd deploy
Deploying services (azd deploy)

  (✓) Done: Deploying service apiservice
  - Endpoint: <YOUR UNIQUE apiservice APP>.azurecontainerapps.io/

  (✓) Done: Deploying service webfrontend
  - Endpoint: <YOUR UNIQUE webfrontend APP>.azurecontainerapps.io/

Aspire Dashboard: <LINK TO DEPLOYED Aspire DASHBOARD>

无需每次部署所有服务。 azd 了解 Aspire 项目模型,只需使用以下命令部署指定的服务之一:

azd deploy webfrontend

有关详细信息,请参阅 Azure Developer CLI 文档:azd deploy

部署基础设施更新

每当项目内的 Aspire 依赖项结构发生更改时, azd 都必须重新预配基础 Azure 资源。 该 azd provision 命令用于将这些更改应用到基础结构。

若要查看实际效果,请将 AppHost 项目中的AppHost.cs 文件更新为以下内容:

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

// Add the locations database.
var locationsdb = builder.AddPostgres("db").AddDatabase("locations");

// Add the locations database reference to the API service.
var apiservice = builder.AddProject<Projects.AspireSample_ApiService>("apiservice")
    .WithReference(locationsdb);

builder.AddProject<Projects.AspireSample_Web>("webfrontend")
    .WithReference(cache)
    .WithReference(apiservice);

builder.Build().Run();

保存文件并发出以下命令:

azd provision

azd provision 命令通过创建容器应用来托管 Postgres 数据库来更新基础结构。 该 azd provision 命令未更新容器的 apiservice 连接字符串。 若要更新连接字符串以指向新预配 Postgres 的数据库, azd deploy 需要再次调用该命令。 当你感到疑惑时,使用 azd up 来进行预配和部署。

清理资源

请记住清除您在本演练中创建的 Azure 资源。 因为 azd 知道在其中创建了资源的资源组,因此可以使用以下命令关闭环境:

azd down

上一个命令可能需要一些时间才能执行,但完成资源组及其所有资源时,应将其删除。

Deleting all resources and deployed code on Azure (azd down)
Local application code is not deleted when running 'azd down'.

  Resource group(s) to be deleted:

    • <YOUR RESOURCE GROUP>: <LINK TO RESOURCE GROUP OVERVIEW>

? Total resources to delete: 7, are you sure you want to continue? Yes
Deleting your resources can take some time.

  (✓) Done: Deleting resource group: <YOUR RESOURCE GROUP>

SUCCESS: Your application was removed from Azure in 9 minutes 59 seconds.

从 Aspire 项目模型生成 Bicep

尽管开发团队可以自由使用 azd up (或 azd provisionazd deploy命令进行开发和生产目的的部署,但一些团队可能会选择生成 Bicep 文件,这些文件可以作为版本控制的一部分进行查看和管理(这也允许将这些 Bicep 文件作为更大复杂 Azure 部署的一部分引用)。

有关为生产方案自定义生成的基础结构的综合指南,请参阅 “自定义 AspireAzure 部署”。

azd 包括通过以下命令输出用于预配的 Bicep 的功能:

azd config set alpha.infraSynth on
azd infra gen

在本指南中使用的初学者模板示例中执行此命令后,会在 AppHost 项目目录中创建以下文件:

  • infra/main.bicep:表示部署的主要入口点。
  • infra/main.parameters.json:用作主 Bicep 的参数(映射到 .azure 文件夹中定义的环境变量)。
  • infra/resources.bicep:定义 Azure 支持 Aspire 项目模型所需的资源。
  • AspireSample.Web/manifests/containerApp.tmpl.yaml:容器 webfrontend应用定义。
  • AspireSample.ApiService/manifests/containerApp.tmpl.yaml:容器 apiservice应用定义。

基础结构\resources.bicep 文件不包含容器应用本身的任何定义(除了一些作为依赖项的容器应用,如Redis 和Postgres):

@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 = {}

var resourceToken = uniqueString(resourceGroup().id)

resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: 'mi-${resourceToken}'
  location: location
  tags: tags
}

resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
  name: replace('acr-${resourceToken}', '-', '')
  location: location
  sku: {
    name: 'Basic'
  }
  tags: tags
}

resource caeMiRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(containerRegistry.id, managedIdentity.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
  scope: containerRegistry
  properties: {
    principalId: managedIdentity.properties.principalId
    principalType: 'ServicePrincipal'
    roleDefinitionId:  subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
  }
}

resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: 'law-${resourceToken}'
  location: location
  properties: {
    sku: {
      name: 'PerGB2018'
    }
  }
  tags: tags
}

resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = {
  name: 'cae-${resourceToken}'
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: logAnalyticsWorkspace.properties.customerId
        sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
      }
    }
  }
  tags: tags
}

resource cache 'Microsoft.App/containerApps@2023-05-02-preview' = {
  name: 'cache'
  location: location
  properties: {
    environmentId: containerAppEnvironment.id
    configuration: {
      service: {
        type: 'redis'
      }
    }
    template: {
      containers: [
        {
          image: 'redis'
          name: 'redis'
        }
      ]
    }
  }
  tags: union(tags, {'aspire-resource-name': 'cache'})
}

resource locations 'Microsoft.App/containerApps@2023-05-02-preview' = {
  name: 'locations'
  location: location
  properties: {
    environmentId: containerAppEnvironment.id
    configuration: {
      service: {
        type: 'postgres'
      }
    }
    template: {
      containers: [
        {
          image: 'postgres'
          name: 'postgres'
        }
      ]
    }
  }
  tags: union(tags, {'aspire-resource-name': 'locations'})
}
output MANAGED_IDENTITY_CLIENT_ID string = managedIdentity.properties.clientId
output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.properties.loginServer
output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = managedIdentity.id
output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = containerAppEnvironment.id
output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = containerAppEnvironment.properties.defaultDomain

有关使用 Bicep 自动化部署的更多信息,请参阅 Azure

服务项目中容器应用.NET的定义分别包含在每个项目中目录中的 manifests 文件中。 项目 webfrontend 中的示例如下:

location: {{ .Env.AZURE_LOCATION }}
identity:
  type: UserAssigned
  userAssignedIdentities:
    ? "{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}"
    : {}
properties:
  environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}
  configuration:
    activeRevisionsMode: single
    ingress:
      external: true
      targetPort: 8080
      transport: http
      allowInsecure: false
    registries:
    - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}
      identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}
  template:
    containers:
    - image: {{ .Env.SERVICE_WEBFRONTEND_IMAGE_NAME }}
      name: webfrontend
      env:
      - name: AZURE_CLIENT_ID
        value: {{ .Env.MANAGED_IDENTITY_CLIENT_ID }}
      - name: ConnectionStrings__cache
        value: {{ connectionString "cache" }}
      - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES
        value: "true"
      - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES
        value: "true"
      - name: services__apiservice__0
        value: http://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}
      - name: services__apiservice__1
        value: https://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}
tags:
  azd-service-name: webfrontend
  aspire-resource-name: webfrontend

执行 azd infra gen 命令后,当调用 azd provisionazd deploy 时,它们会使用 Bicep 及生成的支持文件。

重要

如果 azd infra gen 再次调用,它将将任何修改的文件替换为新生成的文件,并在执行此作之前提示你进行确认。

用于调试的独立环境

由于 azd 可以轻松预配新环境,因此每个团队成员都可以使用独立的云托管环境来调试与生产紧密匹配的设置中的代码。 执行此作时,每个团队成员应使用以下命令创建自己的环境:

azd env new

这将再次提示用户输入订阅和资源组信息,后续的azd upazd provisionazd deploy调用将默认使用此新环境。 --environment开关可以用于这些命令来切换环境。

清理资源

运行以下 Azure CLI 命令,在不再需要创建的 Azure 资源时删除资源组。 删除资源组也会删除其中包含的资源。

az group delete --name <your-resource-group-name>

有关详细信息,请参阅 清理 Azure中的资源。