生成自定义部署管道(预览版)

Aspire 提供强大的 API,用于在发布和部署作期间从资源生成容器映像。 本文介绍启用编程容器映像创建和进度报告的关键组件。

概述

在发布和部署期间,容器映像生成器可用于为需要它们的资源创建映像。 Aspire 当资源需要容器映像时(例如使用 Docker Compose 发布时),使用此生成器。 此过程涉及两个主要组件:

这些 API 提供对映像生成过程的精细控制,并在长时间的生成作期间向用户提供实时反馈。

重要

这些 API 目前以预览版提供,可能会更改。 它们专为需要自定义控制容器映像生成和进度报告的高级方案而设计。 若要禁止显示这些 API 的警告,请参阅 编译器错误ASPIREPUBLISHERS001

何时使用这些 API

请考虑在这些方案中使用容器映像生成和进度报告 API:

  • 自定义部署目标:需要部署到需要特定映像格式或生成配置的平台时。
  • 复杂的生成管道:发布过程涉及用户应看到的多个步骤。
  • 企业方案:需要自定义进度报告才能与 CI/CD 系统或仪表板集成。
  • 自定义资源类型:实现需要参与发布和部署过程的自定义资源时。

注释

对于大多数标准 Aspire 应用程序,内置发布过程会自动生成容器映像,而无需这些 API。

资源容器映像生成器 API

IResourceContainerImageBuilderAspire.Hosting.Publishing 层中的核心服务,将资源定义转换成容器映像。 它会分析分布式应用程序模型中的每个资源,并确定是否:

  • 重用现有映像。
  • 使用.NET从dotnet publish /t:PublishContainer项目生成。
  • 从 Dockerfile 使用本地容器运行时生成。

容器生成选项

ContainerBuildOptions 类为容器生成提供强类型配置。 这个类允许您指定参数或选项:

  • 图像格式:Docker 或 开放容器倡议 (OCI) 格式。
  • 目标平台: Linux x64、Windows、ARM64 等。
  • 输出路径:保存生成的映像的位置。

容器运行时健康检查

只有当至少一个资源需要Docker构建时,才会由生成器执行容器运行时健康检查(Podman/Dockerfile)。 此更改消除了直接从 .NET 程序集发布的项目中的误报错误。 如果容器运行时是必需的,但不健康,构建器会显式抛出InvalidOperationException以提前暴露问题。

发布活动记者 API

API PublishingActivityProgressReporter 启用命令 aspire publishaspire deploy 期间的结构化进度报告。 这可减少长时间运行操作期间的不确定性,并提前发现故障。

API 概述和行为

进度报告器使用分层模型,并提供有保证的排序和线程安全作:

概念 Description CLI 渲染 行为
Step 顶级阶段,例如“生成映像”或“部署工作负荷”。 带有状态标志符号和已用时间的步骤消息。 形成严格的树结构;不支持嵌套步骤。
任务 步骤下嵌套的离散工作单元。 带有缩进的任务消息。 属于单个步骤;支持使用确定性的完成顺序进行并行创建。
完成状态 最终状态: CompletedWarningError ✅ (已完成)、️ ⚠(警告)、 ❌ (错误) 每个步骤或任务只转换一次至最终状态。

API 结构和用法

记者 API 提供对进度报告的结构化访问,具有以下特征:

  • 获取:从PublishingContext.ActivityReporterDeployingContext.ActivityReporter获取。
  • 步骤创建CreateStepAsync(title, ct) 返回一个 IPublishingActivityStep
  • 任务创建IPublishingActivityStep.CreateTaskAsync(title, ct) 返回一个 IPublishingActivityTask
  • 状态转换SucceedAsyncWarnAsyncFailAsync方法接受摘要消息。
  • 完成CompletePublishAsync(message, state, isDeploy, ct) 标记整个操作。
  • 排序:创建和完成事件保留调用顺序;更新已序列化。
  • 取消:所有 API 都接受 CancellationToken 取消并将其传播到 CLI。
  • 处置协定:处置步骤如果未完成,会自动完成,从而阻止孤立阶段。

示例:生成容器映像和报告进度

若要使用这些 API,请在应用模型中向资源添加一个 PublishingCallbackAnnotation、一个 DeployingCallbackAnnotation或两者。 可以通过在 IResource.Annotations 集合中添加批注来注释自定义(或内置)资源。

作为开发人员,可以选择:

  • 如果您的资源需要在发布和部署中执行工作,请同时使用两个批注。 例如,在发布期间生成映像并生成清单,然后在部署期间推送映像或配置部署目标。 发布始终在部署之前发生,因此可以在每个阶段中保持逻辑分开。

  • 仅当资源仅在发布过程中需要执行某些操作时才使用 PublishingCallbackAnnotation。 当您只需要构建工件或镜像时,这种情况很常见,但在部署期间不需要执行任何操作。

  • 仅当资源只需在部署期间执行操作时使用 DeployingCallbackAnnotation。 这适用于使用预生成映像的情况,只需部署或配置它们即可。

选择一个或多个与资源职责匹配的批注,使应用程序模型保持清晰且可维护。 通过这种分离,您可以清晰定义每个阶段的逻辑,但可以根据需要在任何一种回调中使用活动报告器和资源容器镜像构建器。

带批注的示例资源

例如,请考虑构造 ComputeEnvironmentResource 函数:

public ComputeEnvironmentResource(string name) : base(name)
{
    Annotations.Add(new PublishingCallbackAnnotation(PublishAsync));
    Annotations.Add(new DeployingCallbackAnnotation(DeployAsync));
}

实例化时,它会同时定义发布和部署回调注释。

ComputeEnvironmentResource (Resource) 类型的示例,假设您公开了一个扩展方法,以便用户能够添加计算环境:

using System.Diagnostics.CodeAnalysis;

[Experimental("ASPIRECOMPUTE001")]
public static class ComputeEnvironmentResourceExtensions
{
    public static IResourceBuilder<ComputeEnvironmentResource> AddComputeEnvironment(
        this IDistributedApplicationBuilder builder,
        [ResourceName] string name)
    {
        var resource = new ComputeEnvironmentResource(name);

        return builder.AddResource(resource);
    }
}

前面的代码:

AppHost 示例

在您的 AppHost 中,您可以按如下所示将 ComputeEnvironmentResource 添加到应用模型中:

var builder = DistributedApplication.CreateBuilder(args);

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

builder.AddProject<Projects.Api>("api")
       .WithReference(cache);

builder.AddComputeEnvironment("compute-env");

builder.Build().Run();

前面的代码使用 AddComputeEnvironment 扩展方法将它添加到 ComputeEnvironmentResource 应用程序模型。

发布回调注释

当您添加 ComputeEnvironmentResource 时,它会注册 PublishingCallbackAnnotation。 回调使用 PublishAsync 方法:

private static async Task PublishAsync(PublishingContext context)
{
    var reporter = context.ActivityReporter;
    var imageBuilder = context.Services.GetRequiredService<IResourceContainerImageBuilder>();

    // Build container images for all project resources in the application
    await using (var buildStep = await reporter.CreateStepAsync(
        "Build container images", context.CancellationToken))
    {
        // Find all resources that need container images
        var projectResources = context.Model.Resources
            .OfType<ProjectResource>()
            .ToList();

        if (projectResources.Count > 0)
        {
            // Configure how images should be built
            var buildOptions = new ContainerBuildOptions
            {
                ImageFormat = ContainerImageFormat.Oci,
                TargetPlatform = ContainerTargetPlatform.LinuxAmd64,
                OutputPath = Path.Combine(context.OutputPath, "images")
            };

            var buildTask = await buildStep.CreateTaskAsync(
                $"Building {projectResources.Count} container image(s)", context.CancellationToken);

            // Build all the container images
            await imageBuilder.BuildImagesAsync(
                projectResources, buildOptions, context.CancellationToken);

            await buildTask.SucceedAsync(
                $"Built {projectResources.Count} image(s) successfully", context.CancellationToken);
        }
        else
        {
            var skipTask = await buildStep.CreateTaskAsync(
                "No container images to build", context.CancellationToken);
                
            await skipTask.SucceedAsync("Skipped - no project resources found", context.CancellationToken);
        }

        await buildStep.SucceedAsync("Container image build completed", context.CancellationToken);
    }

    // Generate deployment manifests
    await using (var manifestStep = await reporter.CreateStepAsync(
        "Generate deployment manifests", context.CancellationToken))
    {
        var bicepTask = await manifestStep.CreateTaskAsync(
            "Write main.bicep", context.CancellationToken);

        // Write file to context.OutputPath …
        await bicepTask.SucceedAsync(
            $"main.bicep at {context.OutputPath}", context.CancellationToken);

        await manifestStep.SucceedAsync("Manifests ready", context.CancellationToken);
    }

    // Complete the publishing operation
    await reporter.CompletePublishAsync(
        completionMessage: "Publishing pipeline completed successfully",
        completionState: CompletionState.Completed,
        cancellationToken: context.CancellationToken);
}

前面的代码:

  • 实现生成容器映像并生成部署清单的发布管道。
  • 使用 IResourceContainerImageBuilder API 生成容器映像。
  • 使用 PublishingActivityProgressReporter API 报告进度和完成状态。

发布回调可用于 IResourceContainerImageBuilder 生成容器映像,而部署回调可能使用生成的映像并将其推送到注册表或部署目标。

部署回调批注

与发布回调一样,部署回调是使用 DeployingCallbackAnnotation 此方法注册的并调用 DeployAsync 方法:

private static async Task DeployAsync(DeployingContext context)
{
    var reporter = context.ActivityReporter;

    await using (var deployStep = await reporter.CreateStepAsync(
        "Deploy to target environment", context.CancellationToken))
    {
        var applyTask = await deployStep.CreateTaskAsync(
            "Apply Kubernetes manifests", context.CancellationToken);

        // Simulate deploying to Kubernetes cluster
        await Task.Delay(1_000, context.CancellationToken);

        await applyTask.SucceedAsync("All workloads deployed", context.CancellationToken);
        await deployStep.SucceedAsync("Deployment to cluster completed", context.CancellationToken);
    }

    // Complete the deployment operation
    await reporter.CompletePublishAsync(
        completionMessage: "Deployment completed successfully",
        completionState: CompletionState.Completed,
        isDeploy: true,
        cancellationToken: context.CancellationToken);
}

前面的代码:

  • 模拟将工作负荷部署到 Kubernetes 群集。
  • PublishingActivityProgressReporter使用 API 创建和管理部署步骤和任务。
  • 报告进度并将每个部署阶段标记为已完成。
  • 完成部署操作,并给出最终状态更新。
  • 通过提供的 CancellationToken 处理取消。

最佳做法

使用这些 API 时,请遵循以下准则:

图像构建

  • 始终为生产方案指定显式 ContainerBuildOptions
  • 在构建以进行部署时,请考虑目标平台的要求。
  • 使用 OCI 格式实现与容器注册表的最大兼容性。
  • 当容器运行时运行状况检查失败时处理 InvalidOperationException

进度报告

  • 在步骤中封装长时间运行的逻辑阶段,而不是发出原始任务。
  • 使标题保持简洁(60 个字符以下),因为 CLI 截断较长的字符串。
  • 每次发布或部署操作,恰好调用 CompletePublishAsync 一次。
  • 将警告视为可恢复,并允许后续步骤继续。
  • 使用明确的诊断将错误视为致命错误并快速失败。
  • 使用支持取消的异步操作来避免阻止事件处理。

状态管理

  • 每个步骤和任务都以 “正在运行” 状态启动,并完全转换为 “已完成”、“ 警告”或 “错误”。
  • 尝试多个状态转换时引发异常。
  • 利用记者保证有序事件并防止交错。
  • 销毁 IPublishingActivityStep 以自动完成未完成的步骤。

另请参阅