.NET .NET Aspire 编排概述
.NET .NET Aspire 提供用于在分布式应用程序中表达资源和依赖项的 API。 除了这些 API,还有一些工具和框架 可实现多种引人注目的场景。 编排器旨在用于本地开发目的,不支持在生产环境中使用。
在继续之前,请考虑在 .NET.NET Aspire中使用的一些常见术语:
- 应用模型:构成分布式应用程序(DistributedApplication)的资源集合,这些资源在 Aspire.Hosting.ApplicationModel 命名空间中定义。 有关更正式的定义,请参阅 定义应用模型。
- 应用主机/Orchestrator 项目:用于协调和管理 应用模型的 .NET 项目,按照惯例以 *.AppHost 后缀命名。
- 资源:资源 是应用程序的依赖部分,例如 .NET 项目、容器、可执行、数据库、缓存或云服务。 它表示应用程序中可以管理或引用的任何一个部分。
- 集成:集成是 应用主机 的 NuGet 包,它可以为 资源 建模,或者是为在用户应用中使用而配置的 client 包。 有关详细信息,请参阅 .NET.NET Aspire 集成概述。
- 引用:引用定义资源之间的连接,用 WithReference API 表示为依赖项。 有关详细信息,请参阅 参考资源 或 参考现有资源。
注意
.NET .NET Aspire的业务流程旨在通过简化云原生应用的配置和互连的管理来增强 本地开发 体验。 虽然它是一个宝贵的开发工具,但它并不旨在取代生产环境系统,如 Kubernetes,这些系统专门设计为在该上下文中脱颖而出。
定义应用模型
.NET .NET Aspire 使你能够无缝生成、预配、部署、配置、测试、运行和观察分布式应用程序。 所有这些功能都是通过利用 应用模型 实现的,该模型概述了 .NET.NET Aspire 解决方案中的资源及其关系。 这些资源包括应用依赖的项目、可执行文件、容器和外部服务和云资源。 在每一个 .NET.NET Aspire 解决方案中,都有一个指定的 应用主机项目,其中通过 IDistributedApplicationBuilder上的可用方法对应用模型进行精确定义。 此生成器通过调用 DistributedApplication.CreateBuilder获得。
// Create a new app model builder
var builder = DistributedApplication.CreateBuilder(args);
// TODO:
// Add resources to the app model
// Express dependencies between resources
builder.Build().Run();
应用托管项目
应用主机项目处理运行属于 .NET.NET Aspire 项目的所有项目。 换句话说,它负责协调应用模型中的所有应用。 该项目本身是一个 .NET 可执行项目,它引用了 📦Aspire。Hosting.AppHost NuGet 包,并将 IsAspireHost
属性设置为 true
,还引用了 .NET.NET Aspire SDK。
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<IsAspireHost>true</IsAspireHost>
<!-- Omitted for brevity -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
</ItemGroup>
<!-- Omitted for brevity -->
</Project>
以下代码介绍了一个应用主机 Program
,其中包含两个项目引用和一个 Redis 缓存:
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice");
builder.AddProject<Projects.AspireApp_Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(cache)
.WaitFor(cache)
.WithReference(apiService)
.WaitFor(apiService);
builder.Build().Run();
前面的代码:
- 使用 CreateBuilder 方法创建新的应用模型生成器。
- 使用 AddRedis 方法添加名为“cache”的 Redis
cache
资源。 - 使用 AddProject 方法添加名为“apiservice”的项目资源。
- 使用 AddProject 方法添加名为“webfrontend”的项目资源。
- 指定项目具有使用 WithExternalHttpEndpoints 方法的外部 HTTP 终结点。
- 添加对
cache
资源的引用,并通过 WithReference 和 WaitFor 方法等待它准备就绪。 - 添加对
apiservice
资源的引用,并使用 WithReference 和 WaitFor 方法等待其准备就绪。
- 使用 Build 和 Run 方法生成和运行应用模型。
示例代码使用 .NET AspireRedis 托管集成。
若要帮助可视化应用主机项目与它描述的资源之间的关系,请考虑下图:
每个资源必须唯一命名。 此图显示了每个资源及其之间的关系。 容器资源名为“cache”,项目资源名为“apiservice”和“webfrontend”。 Web 前端项目引用缓存和 API 服务项目。 以这种方式表达引用时,Web 前端项目表示它依赖于这两个资源,即“cache”和“apiservice”。
内置资源类型
.NET .NET Aspire 项目由一组资源组成。 📦 Aspire.Hosting.AppHost NuGet 包中的主要基资源类型在下表中有所描述:
方法 | 资源类型 | 描述 |
---|---|---|
AddProject | ProjectResource | 例如,.NET 项目,ASP.NET Core Web 应用。 |
AddContainer | ContainerResource | 容器映像,例如 Docker 映像。 |
AddExecutable | ExecutableResource | 可执行文件,例如 Node.js 应用程序。 |
AddParameter | ParameterResource | 可用于 表示外部参数的参数资源。 |
项目资源表示属于应用模型的 .NET 项目。 向应用主机项目添加项目引用时,.NET.NET Aspire SDK 会为每个引用的项目生成 Projects
命名空间中的类型。 有关详细信息,请参阅 .NET.NET Aspire SDK:项目引用。
若要将项目添加到应用模型,请使用 AddProject 方法:
var builder = DistributedApplication.CreateBuilder(args);
// Adds the project "apiservice" of type "Projects.AspireApp_ApiService".
var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice");
通过将同一项目的多个实例添加到应用模型,可以复制和横向扩展项目。 若要配置副本,请使用 WithReplicas 方法:
var builder = DistributedApplication.CreateBuilder(args);
// Adds the project "apiservice" of type "Projects.AspireApp_ApiService".
var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
.WithReplicas(3);
前面的代码将“apiservice”项目资源的三个副本添加到应用模型。 有关详细信息,请参阅 .NET.NET Aspire 仪表板:资源副本。
参考资源
引用表示资源之间的依赖关系。 例如,可以想象一个场景:您的 Web 前端依赖于 Redis 缓存。 请考虑以下示例应用主机 Program
C# 代码:
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
builder.AddProject<Projects.AspireApp_Web>("webfrontend")
.WithReference(cache);
“webfrontend” 项目资源使用 WithReference 为“缓存”容器资源添加依赖项。 这些依赖项可以表示连接字符串或 服务发现 信息。 在前面的示例中,环境变量 注入到名称 ConnectionStrings__cache
的“webfronend”资源中。 此环境变量包含 webfrontend
用来通过 .NET AspireRedis 集成连接到 Redis 的连接字符串,例如 ConnectionStrings__cache="localhost:62354"
。
正在等待资源
在某些情况下,可能需要等待资源准备就绪,然后再启动另一个资源。 例如,可能需要等待数据库准备就绪,然后再启动依赖于它的 API。 若要表达此依赖项,请使用 WaitFor 方法:
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("postgres");
var postgresdb = postgres.AddDatabase("postgresdb");
builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
.WithReference(postgresdb)
.WaitFor(postgresdb);
在前面的代码中,“apiservice”项目资源等待“postgresdb”数据库资源进入 KnownResourceStates.Running阶段。 示例代码显示了 .NET AspirePostgreSQL 集成,但相同的模式可以应用于其他资源。
其他情况可能需要等待某个资源在 KnownResourceStates.Exited 或 KnownResourceStates.Finished 完成运行后,再启动从属资源。 若要等待资源运行完成,请使用 WaitForCompletion 方法:
var builder = DistributedApplication.CreateBuilder(args);
var postgres = builder.AddPostgres("postgres");
var postgresdb = postgres.AddDatabase("postgresdb");
var migration = builder.AddProject<Projects.AspireApp_Migration>("migration")
.WithReference(postgresdb)
.WaitFor(postgresdb);
builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
.WithReference(postgresdb)
.WaitForCompletion(migration);
在前面的代码中,“apiservice”项目资源会等待“迁移”项目资源运行完成之后才开始。 “迁移”项目资源等待“postgresdb”数据库资源进入状态 KnownResourceStates.Running。 例如,在启动 API 服务之前,在想要运行数据库迁移的情况下,这非常有用。
用于添加和表达资源的 API
.NET .NET Aspire 托管集成 和 client 集成 都作为 NuGet 包提供,但它们的作用不同。 虽然 client 集成 为在应用主机范围外使用应用提供 client 库配置,托管集成 提供用于在应用主机中表达资源和依赖项的 API。 有关详细信息,请参阅 .NET.NET Aspire 集成概述:集成责任。
表达容器资源
若要表达 ContainerResource,请通过调用 AddContainer 方法将其添加到 IDistributedApplicationBuilder 实例:
var builder = DistributedApplication.CreateBuilder(args);
var ollama = builder.AddContainer("ollama", "ollama/ollama")
.WithBindMount("ollama", "/root/.ollama")
.WithBindMount("./ollamaconfig", "/usr/config")
.WithHttpEndpoint(port: 11434, targetPort: 11434, name: "ollama")
.WithEntrypoint("/usr/config/entrypoint.sh")
.WithContainerRuntimeArgs("--gpus=all");
有关详细信息,请参阅
前面的代码通过镜像 ollama/ollama
添加了一个名为“ollama”的容器资源。 容器资源配置了多个绑定挂载、一个带名称的HTTP终结点、一个解析到Unix shell脚本的入口点,以及使用WithContainerRuntimeArgs方法的容器运行参数。
自定义容器资源
可以自定义所有 ContainerResource 子类以满足特定要求。 使用 托管集成 为容器资源建模但需要修改时,这非常有用。 当你有 IResourceBuilder<ContainerResource>
时,可以链接到任何可用的 API 调用以修改该容器资源。
.NET
.NET Aspire 容器资源通常指向已固定的标记,但你可能想要改用 latest
标记。
为了帮助说明这一点,请想象一个使用 .NET AspireRedis 集成的方案。 如果 Redis 集成依赖于 7.4
标记,并且想要改用 latest
标记,则可以串连调用 WithImageTag API:
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache")
.WithImageTag("latest");
// Instead of using the "7.4" tag, the "cache"
// container resource now uses the "latest" tag.
有关更多信息和其他可用的 API,请参阅 ContainerResourceBuilderExtensions。
容器资源生命周期
运行应用主机时,ContainerResource 用于确定要创建和启动的容器映像。 在后台,.NET Aspire 通过委托对符合 OCI 的容器运行时(Docker 或 Podman)的调用,使用定义的容器映像运行容器。 使用以下命令:
首先,使用 docker container create
命令创建容器。 然后,使用 docker container start
命令启动容器。
- docker 容器创建:从指定的映像创建新容器,而无需启动它。
- docker 容器启动:启动一个或多个已停止的容器。
这些命令用于代替 docker run
管理附加的容器网络、卷和端口。 按照此顺序调用这些命令,可以在初始启动时使任何 IP(网络配置)已经准备好。
除了基本资源类型、ProjectResource、ContainerResource和 ExecutableResource之外,.NET.NET Aspire 还提供扩展方法,用于向应用模型添加常见资源。 有关详细信息,请参阅 托管集成。
容器资源生存期
默认情况下,容器资源使用 会话 容器生存期。 这意味着,每次启动应用主机进程时,都会创建并启动容器。 当应用主机停止时,容器将停止并删除。 容器资源可以选择使用 持久性 生命周期,以避免不必要的重启并利用持久化的容器状态。 为此,请链式调用 ContainerResourceBuilderExtensions.WithLifetime API 并传递 ContainerLifetime.Persistent:
var builder = DistributedApplication.CreateBuilder(args);
var ollama = builder.AddContainer("ollama", "ollama/ollama")
.WithLifetime(ContainerLifetime.Persistent);
前面的代码添加名为“ollama”的容器资源,其中包含图像“ollama/ollama”和持久生存期。
连接字符串和终结点引用
通常表示项目资源之间的依赖关系。 请考虑以下示例代码:
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache");
var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice");
builder.AddProject<Projects.AspireApp_Web>("webfrontend")
.WithReference(cache)
.WithReference(apiservice);
项目与项目之间的引用的处理方式不同于具有明确定义的连接字符串的资源。 不再将连接字符串注入到“webfrontend”资源中,而是注入支持服务发现的环境变量。
方法 | 环境变量 |
---|---|
WithReference(cache) |
ConnectionStrings__cache="localhost:62354" |
WithReference(apiservice) |
services__apiservice__http__0="http://localhost:5455" services__apiservice__https__0="https://localhost:7356" |
添加对“apiservice”项目的引用会导致将服务发现环境变量添加到前端。 这是因为通常通过 HTTP/gRPC 进行项目到项目通信。 有关详细信息,请参阅 .NET.NET Aspire 服务发现。
若要从 ContainerResource 或 ExecutableResource获取特定终结点,请使用以下终结点 API 之一:
然后调用 GetEndpoint API 获取用于在 WithReference
方法中引用的端点:
var builder = DistributedApplication.CreateBuilder(args);
var customContainer = builder.AddContainer("myapp", "mycustomcontainer")
.WithHttpEndpoint(port: 9043, name: "endpoint");
var endpoint = customContainer.GetEndpoint("endpoint");
var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
.WithReference(endpoint);
方法 | 环境变量 |
---|---|
WithReference(endpoint) |
services__myapp__endpoint__0=https://localhost:9043 |
port
参数是容器正在侦听的端口。 有关容器端口的详细信息,请参阅 容器端口。 有关服务发现的详细信息,请参阅 .NET.NET Aspire 服务发现。
服务终结点环境变量格式
在上一部分中,WithReference 方法用于表达资源之间的依赖关系。 当服务终结点导致环境变量注入依赖资源时,格式可能并不明显。 本部分提供有关此格式的详细信息。
当一个资源依赖于另一个资源时,应用主机会将环境变量注入依赖资源。 这些环境变量用于配置依赖的资源以连接到它所依赖的资源。 环境变量的格式特定于 .NET.NET Aspire,以与 服务发现兼容的方式表达服务终结点。
服务终结点环境变量名称的前缀为 services__
(双下划线),然后是服务名称、终结点名称,最后是索引。 索引允许为单个服务的多个终结点分配多个标识符,从第一个终结点的 0
开始,每个终结点的标识符递增。
请考虑以下环境变量示例:
services__apiservice__http__0
前面的环境变量表示 apiservice
服务的第一个 HTTP 终结点。 环境变量的值是服务终结点的 URL。 命名终结点可能如下所示:
services__apiservice__myendpoint__0
在前面的示例中,apiservice
服务具有名为 myendpoint
的命名终结点。 环境变量的值是服务终结点的 URL。
引用现有资源
在某些情况下,可能需要引用现有资源,这可能是一种部署到云提供商的资源。 例如,你可能想要引用 Azure 数据库。 在这种情况下,你将依赖 执行上下文 动态确定应用主机是在“运行”模式还是“发布”模式下运行。 如果在本地运行并想要依赖云资源,则可以使用 IsRunMode
属性有条件地添加引用。 可以选择改为在发布模式下创建资源。 某些 托管集成 支持直接提供连接字符串,可用于引用现有资源。
同样,你可能希望将 .NET.NET Aspire 集成到现有解决方案中的用例。 一种常见方法是将 .NET.NET Aspire 应用主机项目添加到现有解决方案。 在应用主机中,通过向应用主机添加项目引用来表达依赖项,生成应用模型。 例如,一个项目可能依赖于另一个项目。 这些依赖项使用 WithReference 方法表示。 有关详细信息,请参阅 ,将 .NET Aspire 添加到现有 .NET 应用。
应用主机生命周期
.NET .NET Aspire 应用主机公开了几个生命周期,可以通过实现 IDistributedApplicationLifecycleHook 接口来对其进行挂钩。 可以使用以下生命周期方法:
次序 | 方法 | 描述 |
---|---|---|
1 | BeforeStartAsync | 在分布式应用程序启动之前执行。 |
2 | AfterEndpointsAllocatedAsync | 在协调器为应用程序模型中的资源分配终端点之后执行。 |
3 | AfterResourcesCreatedAsync | 在编排器创建资源后执行。 |
虽然应用主机提供生命周期挂钩,但你可能想要注册自定义事件。 有关详细信息,请参阅
注册生命周期挂钩
若要注册生命周期挂钩,请实现 IDistributedApplicationLifecycleHook 接口,并使用 AddLifecycleHook API 向应用主机注册挂钩:
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;
var builder = DistributedApplication.CreateBuilder(args);
builder.Services.AddLifecycleHook<LifecycleLogger>();
builder.Build().Run();
internal sealed class LifecycleLogger(ILogger<LifecycleLogger> logger)
: IDistributedApplicationLifecycleHook
{
public Task BeforeStartAsync(
DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
logger.LogInformation("BeforeStartAsync");
return Task.CompletedTask;
}
public Task AfterEndpointsAllocatedAsync(
DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
logger.LogInformation("AfterEndpointsAllocatedAsync");
return Task.CompletedTask;
}
public Task AfterResourcesCreatedAsync(
DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
logger.LogInformation("AfterResourcesCreatedAsync");
return Task.CompletedTask;
}
}
前面的代码:
- 将 IDistributedApplicationLifecycleHook 接口实现为
LifecycleLogger
。 - 使用 AddLifecycleHook API 向应用主机注册生命周期钩子。
- 为每个事件记录一则消息。
运行此应用主机时,将为每个事件执行生命周期钩子。 生成以下输出:
info: LifecycleLogger[0]
BeforeStartAsync
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 9.0.0
info: Aspire.Hosting.DistributedApplication[0]
Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
Application host directory is: ..\AspireApp\AspireApp.AppHost
info: LifecycleLogger[0]
AfterEndpointsAllocatedAsync
info: Aspire.Hosting.DistributedApplication[0]
Now listening on: https://localhost:17043
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17043/login?t=d80f598bc8a64c7ee97328a1cbd55d72
info: LifecycleLogger[0]
AfterResourcesCreatedAsync
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
连接到应用主机生命周期的首选方法是使用事件 API。 有关详细信息,请参阅
执行上下文
IDistributedApplicationBuilder 公开执行上下文(DistributedApplicationExecutionContext),该上下文提供有关应用主机当前执行的信息。 此上下文可用于评估应用主机是作为“运行”模式执行还是作为发布操作的一部分执行。 请考虑以下属性:
-
IsRunMode:如果当前操作正在运行,则返回
true
。 -
IsPublishMode:如果当前操作正在发布,则返回
true
。
如果要根据当前操作有条件地执行代码,此信息非常有用。 请考虑以下示例,该示例演示如何使用 IsRunMode
属性。 在这种情况下,扩展方法用于在本地开发运行中为 RabbitMQ 生成稳定的节点名称。
private static IResourceBuilder<RabbitMQServerResource> RunWithStableNodeName(
this IResourceBuilder<RabbitMQServerResource> builder)
{
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
builder.WithEnvironment(context =>
{
// Set a stable node name so queue storage is consistent between sessions
var nodeName = $"{builder.Resource.Name}@localhost";
context.EnvironmentVariables["RABBITMQ_NODENAME"] = nodeName;
});
}
return builder;
}
执行上下文通常用于有条件地添加指向现有资源的资源或连接字符串。 请考虑以下示例,该示例演示如何根据执行上下文有条件地添加 Redis 或连接字符串:
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.ExecutionContext.IsRunMode
? builder.AddRedis("redis")
: builder.AddConnectionString("redis");
builder.AddProject<Projects.WebApplication>("api")
.WithReference(redis);
builder.Build().Run();
在前面的代码中:
- 如果应用主机以“运行”模式运行,则会添加 Redis 容器资源。
- 如果应用主机以“发布”模式运行,则会添加连接字符串。
在本地运行时,可以轻松反转此逻辑以连接到现有 Redis 资源,并在发布时创建新的 Redis 资源。