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

Azure Developer CLI(azd)已扩展,以支持部署 .NET.NET Aspire 项目。 使用本指南逐步指导您如何使用 .NET Aspire创建并将 Azure Container Apps 项目部署到 Azure Developer CLI。 在本教程中,你将了解以下概念:

  • 了解 azd 集成如何与 .NET.NET Aspire 项目配合使用
  • 使用 Azure 在 .NET Aspire 项目的 azd 上配置和部署资源
  • 使用 azd 生成 Bicep 基础结构和其他模板文件

先决条件

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

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

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

winget install microsoft.azd

Azure Developer CLI 的集成工作原理

azd init 工作流为 .NET.NET Aspire 项目提供自定义支持。 下图说明了此流在概念上的工作原理,以及如何集成 azd 和 .NET.NET Aspire:

展示“azd”在部署 .NET.NET Aspire 项目时的内部处理插图。

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

注意

azd 还使你能够将生成的 Bicep 输出到项目中的 infra 文件夹中,可以在 从 .NET.NET Aspire 应用模型生成 Bicep 部分阅读详细信息。

配置和部署 .NET.NET Aspire 入门应用

本节中的步骤演示了如何创建一个 .NET Aspire 起始应用,并处理应用资源的配置和部署,以便在 Azure 使用 azd

创建 .NET.NET Aspire 初学者应用

使用 .NET 命令创建新的 .NET Aspiredotnet new 项目。 还可以使用 Visual Studio创建项目。

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

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

初始化模板

  1. 打开新的终端窗口,并 cd.NET 解决方案的 .NET 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
    
  4. 扫描目录后,azd 会提示你确认它找到正确的 .NET.NET 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:描述应用的服务,例如 .NET 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. 若要部署 .NET Aspire 项目,请向 Azure AD 进行身份验证,以调用 Azure 资源管理 API。

    azd auth login
    

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

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

    azd up
    

    重要

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

  3. 出现提示时,请选择应将资源部署到的订阅和位置。 选择这些选项后,将部署 .NET.NET 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 .NET 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 容器应用中环境变量的屏幕截图。

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

部署应用程序更新

执行 azd up 命令时,基础 Azure 资源被 预配,接着生成容器映像,并将其部署到托管.NET 项目的容器应用 .NET 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 .NET ASPIRE DASHBOARD>

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

azd deploy webfrontend

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

部署基础设施更新

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

若要查看此操作,请将 AppHost 项目中 Program.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.

基于 .NET.NET Aspire 项目模型生成 Bicep

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

azd 包括通过以下命令输出其用于配置的 Bicep 脚本的能力:

azd config set alpha.infraSynth on
azd infra synth

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

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

基础结构\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 的详细信息,请参阅 什么是 Bicep?

来自 .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 synth 命令后,调用 azd provisionazd deploy 时,它们使用 Bicep 和生成的支持文件。

重要

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

用于调试的隔离环境

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

azd env new

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

清理资源

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

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

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