开发 Aspire 的优点之一是,它使你能够在本地开发、测试和调试云原生应用。 内部循环联网是Aspire 的一个关键功能,它允许您的应用在开发环境中相互通信。 本文介绍如何 Aspire 使用代理、终结点、终结点配置和启动配置文件处理各种网络方案。
内部循环中的网络
内部循环是在将应用部署到目标环境之前在本地开发和测试应用的过程。 Aspire 提供了多种工具和功能来简化和增强内部循环中的网络体验,例如:
- 启动配置文件:启动配置文件是指定如何在本地运行应用的配置文件。 可以使用启动配置文件(如 launchSettings.json 文件)来定义应用的终结点、环境变量和启动设置。
- Kestrel 配置:Kestrel 配置使您可以指定 Kestrel Web 服务器侦听的端点。 可以在应用设置中配置 Kestrel 终结点,并 Aspire 自动使用这些设置来创建终结点。
- 终结点/终结点配置:终结点是应用与它依赖的服务之间的连接,例如数据库、消息队列或 API。 终结点提供服务名称、主机端口、方案和环境变量等信息。 您可以通过启动配置文件隐式添加终结点,或者通过调用 WithEndpoint显式添加终结点到您的应用程序。
- 代理: Aspire 为应用程序中每个添加的服务绑定自动启动代理,并分配一个端口供代理侦听。 然后,代理将请求转发到应用监听的端口,该端口可能与代理所使用的端口不同。 这样,便可以避免端口冲突,并使用一致且可预测的 URL 访问应用和服务。
终结点的工作原理
服务 Aspire 绑定涉及两个集成:表示应用所需的外部资源的 服务 (例如数据库、消息队列或 API),以及建立应用和服务之间的连接的 绑定 并提供必要的信息。
Aspire支持两种服务绑定类型:隐式、基于指定启动配置文件自动创建,用于定义不同环境中的应用行为,以及使用手动创建的WithEndpoint启动配置文件。
在创建绑定(无论是隐式还是显式)时, Aspire 在指定的端口上启动轻型反向代理,处理从应用到服务的请求的路由和负载均衡。 代理是一个 Aspire 实现详细信息,不需要任何配置或管理的顾虑。
为了帮助可视化终结点的工作原理,请考虑 Aspire 入门模板内部循环网络图:
如何管理容器网络
添加一个或多个容器资源时,Aspire 创建一个专用的容器桥接网络,以便在容器之间实现服务自动发现。 此桥接网络是一个虚拟网络,它允许容器相互通信,并通过 DNS 名称为容器间服务发现提供 DNS 服务器。
网络的生存期取决于容器资源:
- 如果所有容器都有会话生存期,则网络也是基于会话的,并在 AppHost 进程结束时清理。
- 如果任何容器具有持久性生存期,则网络是永久性的,并在 AppHost 进程终止后继续运行。 Aspire 在后续运行中重复使用此网络,即使 AppHost 未运行,也允许永久性容器保持通信。
有关容器生存期的详细信息,请参阅 容器资源生存期。
下面是容器网络的命名约定:
-
会话网络:
aspire-session-network-<unique-id>-<app-host-name> -
持久网络:
aspire-persistent-network-<project-hash>-<app-host-name>
每个 AppHost 实例获取自己的网络资源。 唯一的区别是网络的生命周期和名称。服务发现的工作方式对两者都相同。
容器使用其资源名称在网络上注册自己。
Aspire 将此名称用于容器之间的服务发现。 例如,pgadmin 容器可以使用 postgres 连接到名为 postgres:5432 的数据库资源。
Note
主机服务(如项目或其他可执行文件)不使用容器网络。 它们依赖于公开的容器端口来发现服务并与容器通信。 有关服务发现的详细信息,请参阅 服务发现概述。
启动配置
调用 AddProject时,AppHost 会查找 属性/launchSettings.json 以确定默认的终结点集。 AppHost 使用以下规则选择特定的启动配置文件:
- 调用
launchProfileName时传递的显式AddProject参数。 -
DOTNET_LAUNCH_PROFILE环境变量。 有关详细信息,请参阅 .NET 环境变量。 - launchSettings.json中定义的第一个启动配置文件。
请考虑以下 launchSettings.json 文件:
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7239;http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
对于本文的其余部分,请设想你已使用 IDistributedApplicationBuilder API 创建了 builder,并将其分配给一个名为 CreateBuilder() 的变量。
var builder = DistributedApplication.CreateBuilder(args);
若要指定 http 和 https 启动配置文件,请在 applicationUrl 文件中配置两者 值。 这些 URL 用于为此项目创建终结点。 这相当于:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066)
.WithHttpsEndpoint(port: 7239);
Important
如果没有 launchSettings.json(或启动配置文件),则默认情况下没有绑定。
有关详细信息,请参阅 Aspire 和启动配置文件。
Kestrel 配置的终结点
Aspire 支持 Kestrel 终端配置。 例如,考虑某个定义了具有 HTTPS 方案和端口 5271 的 Kestrel 终结点的项目的 appsettings.json 文件:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5271"
}
}
}
}
上述配置指定 Https 终结点。
Url 属性设置为 https://*:5271,这意味着终结点在端口 5271 上侦听所有接口。 有关详细信息,请参阅 Kestrel Web 服务器的ASP.NET Core终结点配置。
配置 Kestrel 终结点后,项目应从 applicationUrl 文件中删除任何配置的 。
Note
如果 applicationUrl 存在于 launchSettings.json 文件中,并且已配置 Kestrel 终结点,则 AppHost 将引发异常。
在添加项目资源时,有一个重载允许你指定应使用 Kestrel 终结点,而不是 launchSettings.json 文件:
builder.AddProject<Projects.Networking_ApiService>(
name: "apiservice",
configure: static project =>
{
project.ExcludeLaunchProfile = true;
project.ExcludeKestrelEndpoints = false;
})
.WithHttpsEndpoint();
有关详细信息,请参阅 AddProject。
端口和代理
定义服务绑定时,主机端口 始终 提供给位于服务前面的代理。 这允许服务的单个或多个副本的行为类似。 此外,所有使用 WithReference API 的资源依赖项都依赖于环境变量中的代理终结点。
请考虑调用 AddProject、WithHttpEndpoint,然后 WithReplicas的以下方法链:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066)
.WithReplicas(2);
上述代码将生成以下网络关系图:
上图描述了以下内容:
- Web 浏览器作为应用的入口点。
- 主机端口 5066。
- 前端代理位于 Web 浏览器和前端服务副本之间,监听网络端口 5066。
-
frontend_0前端服务副本在随机分配的端口 65001 上进行侦听。 -
frontend_1前端服务副本在随机分配的端口 65002 上侦听。
如果没有调用 WithReplicas,则只有一个前端服务。 代理仍侦听端口 5066,但前端服务侦听随机端口:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066);
定义了两个端口:
- 主机端口 5066。
- 底层服务将绑定到的随机代理端口。
上图描述了以下内容:
- Web 浏览器作为应用的入口点。
- 主机端口 5066。
- 前端代理位于 Web 浏览器和前端服务之间,侦听端口 5066。
- 侦听 65001 随机端口的前端服务。
基础服务通过 ASPNETCORE_URLS 为项目资源提供此端口。 通过在服务绑定上指定一个环境变量来访问此端口的其他资源。
builder.AddNpmApp("frontend", "../NodeFrontend", "watch")
.WithHttpEndpoint(port: 5067, env: "PORT");
前面的代码使随机端口在 PORT 环境变量中可用。 应用使用此端口侦听来自代理的传入连接。 请考虑下图:
上图描述了以下内容:
- Web 浏览器作为应用的入口点。
- 一个主机端口 5067。
- 前端代理位于 Web 浏览器和前端服务之间,侦听端口 5067。
- 侦听环境 65001 的前端服务。
Tip
若要避免终结点被代理,将 IsProxied 属性设置为 false,然后调用 WithEndpoint 扩展方法。 有关详细信息,请参阅 终结点扩展:的其他注意事项。
省略主机端口
省略主机端口时, Aspire 为主机和服务端口生成随机端口。 如果想要避免端口冲突,并且不关心主机或服务端口,这非常有用。 请考虑以下代码:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint();
在此方案中,主机和服务端口都是随机的,如下图所示:
上图描述了以下内容:
- Web 浏览器作为应用的入口点。
- 随机主机端口 65000。
- 前端代理位于 Web 浏览器和前端服务之间,侦听端口 65000。
- 侦听随机端口 65001 的前端服务。
容器端口
添加容器资源时, Aspire 会自动向容器分配随机端口。 若要指定容器端口,请使用所需的端口配置容器资源:
builder.AddContainer("frontend", "mcr.microsoft.com/dotnet/samples", "aspnetapp")
.WithHttpEndpoint(port: 8000, targetPort: 8080);
前面的代码:
- 从
frontend映像创建名为mcr.microsoft.com/dotnet/samples:aspnetapp的容器资源。 - 通过将主机绑定到端口 8000 并将其映射到容器的端口 8080 来公开
http终结点。
请考虑下图:
端点扩展方法
实现 IResourceWithEndpoints 接口的任何资源都可以使用 WithEndpoint 扩展方法。 此扩展有多个重载,允许您指定协议、容器端口、主机端口、环境变量名称以及端点是否被代理。
还有一种重载方法,允许您指定委托来配置终结点。 如果需要根据环境或其他因素配置终结点,这非常有用。 请考虑以下代码:
builder.AddProject<Projects.Networking_ApiService>("apiService")
.WithEndpoint(
endpointName: "admin",
callback: static endpoint =>
{
endpoint.Port = 17003;
endpoint.UriScheme = "http";
endpoint.Transport = "http";
});
前述代码提供用于配置终结点的回调委托。 终结点命名为 admin,配置为使用 http 方案和传输,以及 17003 主机端口。 使用者按名称引用此终结点,请考虑以下 AddHttpClient 调用:
builder.Services.AddHttpClient<WeatherApiClient>(
client => client.BaseAddress = new Uri("http://_admin.apiservice"));
Uri 是通过在 admin 终结点名称前加上 _ 哨兵来构建的。 这是一种约定,指示 admin 段是属于 apiservice 服务的终结点名称。 有关详细信息,请参阅 Aspire 服务发现。
其他注意事项
调用 WithEndpoint 扩展方法时,callback 重载会暴露原始 EndpointAnnotation,这允许使用者自定义端点的许多方面。
AllocatedEndpoint 属性允许获取或设置服务的终结点。
IsExternal 和 IsProxied 属性决定了终结点的管理和公开方式:IsExternal 决定是否应公开访问终结点,而 IsProxied 确保 DCP 对其进行管理,从而允许内部端口差异和复制。
Tip
如果您要托管一个运行自身代理的外部可执行文件,并且由于 DCP 已经绑定了端口而遇到端口绑定问题,请尝试将 IsProxied 属性设置为 false。 这可以防止 DCP 管理代理,从而允许可执行文件成功绑定端口。
Name 属性标识服务,而 Port 和 TargetPort 属性分别指定所需的端口和侦听端口。
对于网络通信,Protocol 属性支持 TCP 和 UDP,将来可能会有更多可能性,Transport 属性指示传输协议(HTTP、HTTP2、HTTP3)。 最后,如果服务有 URI 地址,则 UriScheme 属性提供用于构建服务 URI 的 URI 协议。
有关详细信息,请参阅 EndpointAnnotation 属性的可用属性。
端点过滤
所有 Aspire 项目资源端点都遵循一组默认规则。 某些终结点在运行时包含在 ASPNETCORE_URLS 中,某些终结点作为 HTTP/HTTPS_PORTS发布,还有一些配置是从 Kestrel 配置中解析的。 无论默认行为如何,都可以使用 WithEndpointsInEnvironment 扩展方法筛选环境变量中包含的终结点:
builder.AddProject<Projects.Networking_ApiService>("apiservice")
.WithHttpsEndpoint() // Adds a default "https" endpoint
.WithHttpsEndpoint(port: 19227, name: "admin")
.WithEndpointsInEnvironment(
filter: static endpoint =>
{
return endpoint.Name is not "admin";
});
上述代码在端口 19227 上添加默认 HTTPS 终结点以及 admin 终结点。 但是,admin 端点被从环境变量中排除。 如果想要公开终结点供内部使用,这非常有用。