PAYATHON 2026

API server 可连接,但 Functions 无法连接 Service Bus Emulator

排障赵师傅

结论

这类现象通常是因为 localhost 指向了错误的网络环境,与队列名称、SAS Key 或 Azure Functions isolated worker 本身无关。

连接字符串中的地址是:

Endpoint=sb://localhost:34628

这个地址只有在客户端进程与 Service Bus Emulator 宿主机处于同一网络命名空间时才有效。假设 ASP.NET API 运行在宿主机上,Functions 运行在容器中:

  • API 中的 localhost:34628 指向宿主机,因此可以连接。
  • Functions 中的 localhost:34628 指向 Functions 容器自身。
  • 该容器内没有进程监听这个端口,最终就会出现 actively refused。

所有资源都在本地运行,不等于它们共享同一个 localhost。

先确认 Functions 运行在哪里

在 Aspire Dashboard 中检查 Functions 资源的启动方式,也可以查看对应的进程或容器信息。

如果 Functions 直接作为宿主机进程运行,通常可以继续使用:

Endpoint=sb://localhost:34628

如果 Functions 运行在 Docker 容器中,可以通过宿主机网关访问 Aspire 暴露的端口,也可以使用 Aspire 网络中的资源名称访问 Emulator 的容器端口。

需要从 Functions 的运行环境中验证地址是否可达,不能只确认连接字符串已经配置。例如,进入 Functions 容器后测试端口:

nc -vz localhost 34628

如果测试失败,再检查宿主机网关:

nc -vz host.docker.internal 34628

host.docker.internal 通常适用于 Docker Desktop。在 Linux 环境中能否使用这个名称,取决于 Docker 配置。

方案一:通过宿主机映射端口连接

如果 Service Bus Emulator 的 34628 端口已经发布到宿主机,容器内的 Functions 可以使用:

Endpoint=sb://host.docker.internal:34628;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;

API 和 Functions 不必强制使用相同的连接字符串。两者处于不同网络环境时,即使连接的是同一个服务,使用的主机名也可能不同。

在 Linux Docker 环境中,可能还需要为 Functions 容器添加宿主机网关映射:

extra_hosts:
  - "host.docker.internal:host-gateway"

具体配置取决于 Functions 由 Aspire、Docker Compose 还是其他方式启动。

方案二:使用 Aspire 内部网络地址

如果 Functions 和 Service Bus Emulator 都在 Aspire 管理的容器网络中运行,建议使用 Service Bus 资源的内部 DNS 名称和容器端口。连接字符串大致如下:

Endpoint=sb://servicebus-emulator:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;

其中的 servicebus-emulator 和 5672 只是示例。实际值应以 Aspire Dashboard 中该资源的内部 endpoint、容器名称和 target port 为准,不能直接照搬。

还需要在 AppHost 中让 Functions 显式引用 Service Bus 资源。配置可参考:

var builder = DistributedApplication.CreateBuilder(args);

var serviceBus = builder
    .AddAzureServiceBus("servicebus")
    .RunAsEmulator();

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

builder.AddAzureFunctionsProject<Projects.Functions>("functions")
    .WithReference(serviceBus)
    .WaitFor(serviceBus);

builder.Build().Run();

这些 API 是否可用以及具体签名,取决于项目使用的 Aspire 版本。需要建立资源引用,让 Aspire 为每个消费者生成适合其运行环境的连接信息,避免将 Dashboard 中的宿主机地址手动复制给所有资源。

分别检查 Functions 中的配置名称

手动创建 ServiceBusClient 时,代码读取的是:

Environment.GetEnvironmentVariable("SERVICE_BUS_CONNECTION_STRING")

Service Bus Trigger 的 Connection 参数通常表示配置项名称,并非连接字符串本身。例如:

[Function("ProcessTask")]
public Task Run(
    [ServiceBusTrigger("tasksQueue", Connection = "ServiceBusConnection")]
    ServiceBusReceivedMessage message)
{
    // ...
}

因此,Functions 配置中必须存在名为 ServiceBusConnection 的值:

{
  "Values": {
    "ServiceBusConnection": "Endpoint=sb://host.docker.internal:34628;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"
  }
}

如果 Aspire 注入的配置项是 ConnectionStrings__servicebus,Trigger 使用的却是 ServiceBusConnection,即使两项内容相同,系统也不会自动将它们视为同一个配置项。需要显式映射,或者让代码和 Trigger 统一使用实际注入的名称。

目前手动调用 SendMessageAsync 时也出现了连接拒绝,所以应先解决 endpoint 的网络可达性,再检查 Trigger 配置名。

为发送操作设置超时

诊断时可以设置取消时间,同时确认连接字符串不为空:

string connectionString =
    Environment.GetEnvironmentVariable("SERVICE_BUS_CONNECTION_STRING")
    ?? throw new InvalidOperationException(
        "SERVICE_BUS_CONNECTION_STRING is not configured.");

var options = new ServiceBusClientOptions
{
    TransportType = ServiceBusTransportType.AmqpTcp,
    RetryOptions = new ServiceBusRetryOptions
    {
        TryTimeout = TimeSpan.FromSeconds(10),
        MaxRetries = 1
    }
};

await using var client = new ServiceBusClient(connectionString, options);
ServiceBusSender sender = client.CreateSender("tasksQueue");

var message = new ServiceBusMessage("Health check message");

using var cancellationTokenSource =
    new CancellationTokenSource(TimeSpan.FromSeconds(20));

await sender.SendMessageAsync(
    message,
    cancellationTokenSource.Token);

超时设置只能减少诊断时的等待时间,无法修复错误的主机名或端口。

建议的排查顺序

  1. 确认 Functions 运行在宿主机进程中还是容器中。
  2. 在 Functions 所在环境中测试 localhost:34628 是否可达。
  3. 如果 Functions 位于容器中,测试 host.docker.internal:34628。
  4. 如果两个资源共享 Aspire 容器网络,改用资源的 DNS 名称和实际 target port。
  5. 确认使用的是 Service Bus 的 AMQP endpoint,而非管理端口或其他映射端口。
  6. 网络连通后,核对 Trigger 的 Connection 名称与 Aspire 注入的配置项名称。
  7. 如果只有 localhost 连接失败,分别测试 127.0.0.1 和 ::1,排查 IPv4 与 IPv6 监听地址不一致的问题。

队列不存在、权限不足或连接字符串格式错误时,通常会返回对应的 Service Bus 错误。actively refused 更像是 TCP 层面的连接拒绝,表示客户端已经尝试连接某个地址,但目标端口没有进程监听。当前应先检查 localhost 的实际指向和端口映射,再考虑防火墙或队列定义。

备注:内容仅供参考。