使用 docker compose 的容器之间的 API 请求连接被拒绝

Jor*_*lez 10 c# asp.net docker .net-core docker-compose

我正在开发一个多容器 Docker 应用程序,我想要一个容器使用 Docker Compose 向其他容器的 API 发出 HTTP 请求。我收到连接被拒绝错误。

两个容器都运行 ASP.NET Core 2.2。我使用的 IDE 是 Visual Studio 2017。我正在使用 Postman 测试 API 调用。

我尝试过的事情:
?? 端口在 Dockerfiles 中公开
?? 端口是在 Docker Compose 配置中声明的
??网络在 Docker Compose 中声明并且容器连接到它
??Http 请求 URI 使用服务名称而不是localhost或本地 IP 地址
?? Http 请求 URI 使用容器端口 (80) 而不是主机端口
?? 防火墙被禁用了
??具有自定义子网的自定义网络
?? 服务的自定义 IPv4 地址
?? Docker Compose 降级到 v2.4(在自定义网络上指定网关)
??删除并重新创建 serviceB 项目
?? 从 HttpClient 的基本用法切换到类型化客户端(自定义服务)
?? 从 GetAsync(Uri) 切换到 GetAsync(Uri,HttpCompletionOption,CancellationToken)

Dockerfiles

FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
...
Run Code Online (Sandbox Code Playgroud)

Docker Compose 配置:

version: '3.4'

services:
  servicea:
    ...
    ports:
      - "51841:80"
      - "44364:443"
    networks:
      - local

  serviceb:
      ...
    ports:
      - "65112:80"
      - "44359:443"
    networks:
      - local

networks:
  local:
    driver: bridge
Run Code Online (Sandbox Code Playgroud)

serviceA 控制器动作:

[Route("[action]")]
[HttpGet]
public IActionResult foo()
{
   HttpClient client = _clientFactory.CreateClient();

   var result = client.GetAsync("http://serviceb:80/api/bar").Result;
   var response = result.Content.ReadAsStringAsync().Result;

   return new OkObjectResult(response);
}
Run Code Online (Sandbox Code Playgroud)

如果我使用 Postman 对 serviceA(主机端口为 51841)发出 Http Get 请求,http://localhost:51841/api/foo我想获得 serviceB 的 Bar 操作的响应。

但我的连接被拒绝

原始异常详细信息:

System.Net.Http.HttpRequestException: Connection refused ---> System.Net.Sockets.SocketException: Connection refused
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
Run Code Online (Sandbox Code Playgroud)

如果我访问 serviceA 的 bash 并对 serviceB(特别是 serviceB 的 :80 端口)执行 ping 操作,则它可以工作:

version: '3.4'

services:
  servicea:
    ...
    ports:
      - "51841:80"
      - "44364:443"
    networks:
      - local

  serviceb:
      ...
    ports:
      - "65112:80"
      - "44359:443"
    networks:
      - local

networks:
  local:
    driver: bridge
Run Code Online (Sandbox Code Playgroud)

卷曲

我还可以使用 CURL 与 API REST 端点建立连接,但收到的 Content-Length 为 0

[Route("[action]")]
[HttpGet]
public IActionResult foo()
{
   HttpClient client = _clientFactory.CreateClient();

   var result = client.GetAsync("http://serviceb:80/api/bar").Result;
   var response = result.Content.ReadAsStringAsync().Result;

   return new OkObjectResult(response);
}
Run Code Online (Sandbox Code Playgroud)

所以我们可以看到 serviceB 告诉 serviceA 它的请求将被重定向到https://serviceb:44359/api/bar/但所需的连接端口是 80(容器端口),而不是 44359(主机端口)

如果我让 curl 跟随重定向,则会出现连接被拒绝(重定向的端口已关闭)

System.Net.Http.HttpRequestException: Connection refused ---> System.Net.Sockets.SocketException: Connection refused
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
Run Code Online (Sandbox Code Playgroud)

为什么我被重定向到主机端口?

Startup.cs我的服务中使用app.UseHttpsRedirection();所以删除该行解决了问题

如果我仍然需要使用 HTTPS 怎么办? 添加选项以使用容器端口。更多信息在回答

Jor*_*lez 8

ServiceA的HTTP请求都被重定向(HTTP 307状态码)https://serviceb:44359/api/bar:44359用于HTTPS主机端口。容器之间无法访问主机端口,容器端口可以。因此,如果我访问 serviceA 的终端并在重定向到 URI 后发送带有curl详细信息的 HTTP 请求,则会收到连接拒绝错误:-v-Lhttp://serviceb/api/bar

root@serviceA_id:/app# curl -v -L http://serviceb/api/bar
*   Trying 10.168.0.3...
* TCP_NODELAY set
* Connected to serviceb (10.168.0.3) port 80 (#0)
> GET /api/bar HTTP/1.1
> Host: serviceb
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 307 Temporary Redirect
< Date: Wed, 19 Jun 2019 08:48:33 GMT
< Server: Kestrel
< Content-Length: 0
< Location: https://serviceb:44359/api/bar
<
* Curl_http_done: called premature == 0
* Connection #0 to host serviceb left intact
* Issue another request to this URL: 'https://serviceb:44359/api/bar'
*   Trying 10.168.0.3...
* TCP_NODELAY set
* connect to 10.168.0.3 port 44359 failed: Connection refused
* Failed to connect to serviceb port 44359: Connection refused
* Closing connection 1
curl: (7) Failed to connect to serviceb port 44359: Connection refused
Run Code Online (Sandbox Code Playgroud)

为什么我被重定向到主机端口?

Startup.cs我的服务使用中app.UseHttpsRedirection();,该行导致了问题。

HttpsPolicyBuilderExtensions.UseHttpsRedirection(IApplicationBuilder) 方法的默认配置默认重定向到HTTPS主机端口。如果要使用不同的端口进行重定向,则需要添加该选项,Startup.cs如下所示:

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {

            ...

            services.AddHttpsRedirection(options =>
            {
                options.HttpsPort = 443;
            });

            ...

        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {

            ...

            app.UseHttpsRedirection();

            ...

        }
    }
Run Code Online (Sandbox Code Playgroud)