【问题标题】:Connection refused on API request between containers with docker compose使用 docker compose 的容器之间的 API 请求连接被拒绝
【发布时间】:2019-06-12 13:05:21
【问题描述】:

我正在开发一个多容器 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
...

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

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);
}

如果我通过 http://localhost:51841/api/foo 的 Postman 向 serviceA(主机端口 51841)发出 Http Get 请求,我想获得 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)

PING

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

root@serviceA_id:/app# ping -p 80 serviceb
PATTERN: 0x80
PING serviceb(10.168.0.3) 56(84) bytes of data.
64 bytes from ... (10.168.0.3): icmp_seq=1 ttl=64 time=0.117 ms
64 bytes from ... (10.168.0.3): icmp_seq=2 ttl=64 time=0.101 ms
64 bytes from ... (10.168.0.3): icmp_seq=3 ttl=64 time=0.083 ms
--- serviceb ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2057ms rtt min/avg/max/mdev = 0.083/0.100/0.117/0.016 ms

卷曲

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

root@serviceA_id:/app# curl -v 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: Mon, 17 Jun 2019 07:11:31 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

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

如果我让 curl 跟随重定向,则会出现 Connection Refused(重定向端口已关闭)

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

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

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

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

【问题讨论】:

  • +1m 非常感谢,我几天来一直在调整我的 docker-compose 试图克服这个连接被拒绝的错误!
  • 我收到了 Connection Refused 错误并回答了您的问题。查看了您的清单并意识到我没有暴露这个新端口,谢谢!
  • 与问题无关 - 但是 - 请不要在异步方法调用中使用 .Result ,使用 await - 这是不好的做法,你会为各种问题做好准备;-)跨度>

标签: c# asp.net docker .net-core docker-compose


【解决方案1】:

ServiceA 的 HTTP 请求被重定向(HTTP 307 状态代码)到 https://serviceb:44359/api/bar:44359 HTTPS 的主机端口。容器之间无法访问主机端口,容器端口可以。因此,如果我访问 serviceA 的终端并发送带有 curl 详细 -v 的 HTTP 请求,然后将 -L 重定向到 URI http://serviceb/api/bar,我会收到 Connection Refused 错误:

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

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

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

HttpsPolicyBuilderExtensions.UseHttpsRedirection(IApplicationBuilder) method的默认配置默认重定向到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();

            ...

        }
    }

【讨论】:

  • 伙计!你是天使。
【解决方案2】:

但当我对 serviceB 的 API REST 执行 ping 操作时却没有:

root@serviceA_id:/app# ping -p 80 serviceb/api/bar
PATTERN: 0x80
ping: serviceb/api/bar: Temporary failure in name resolution

这可能是做 API 时出现 Connection Refused 错误的原因 来自 ASP.NET 的请求

ping 不是这样工作的。 Ping 使用 ICMP,而不是 TCP,也不是在 TCP 之上工作的 HTTP。您 ping 主机,而不是 TCP 端口或 HTTP API。所以以上预计会失败。

我还可以使用 CURL 与 API REST 端点建立连接,但是 收到的内容长度为 0

root@serviceA_id:/app# curl -v http://serviceb/api/bar
*   Trying 10.168.0.3...
* TCP_NODELAY set
* Connected to serviceb (10.168.0.3) port 80 (#0)

这表明您已正确配置 docker 容器进行通信,网络配置中无需调试任何其他内容,并且您知道您的 serviceb 正在侦听。

唯一需要调试的是:

  • 确保 servicea 在端口 80 上连接到 serviceb,而不是 65112 或任何其他主机端口。容器通过容器端口相互通信。
  • 确保您运行的是发布的代码,而不是以前的版本。这在构建和部署镜像时很容易出错,尤其是在您没有为每个构建更改镜像标签的情况下。
  • 确保给 serviceb 启动时间。当 servicea 先于 serviceb 启动时,我经常看到这些错误。

如果仍有问题,可以使用 tcpdump 等工具开始调试。例如

docker run -it --rm --net container:$container_id \
  nicolaka/netshoot tcpdump -i any port 80

用 serviceb 的 id 替换 $container_id 以查看对该容器上端口 80 的任何 TCP 请求。

【讨论】:

  • 谢谢!最后,我使用 curl,这样我可以获得更轻的输出,并专注于跟踪 HTTP 请求的演变,这有助于我找到解决方案,所以是的,我试图连接到正确的端口,但我被重定向到了错误的端口港口。您的第一个建议是正确的
  • “容器在容器端口上相互通信”——这是我的错误。
【解决方案3】:

从同一个 docker 网络连接时,您还必须指定端口:

var result = client.GetAsync("http://serviceb:80/api/actioname").Result;

【讨论】:

  • 我试过但同样的错误。就我而言,当您访问 :80(使用 http 方案)或 :443(使用 https)时,不需要在 URI 中指定端口
  • @JorgeJGonzález 这是真的。您能否也尝试删除networks 指令?因为它应该默认工作。
  • @JorgeJGonzález 也请尝试在服务 B 中使用不同的端口(不要在同一网络中重复 80 和 443)。
  • 我已经删除了网络指令及其使用,我已经将两个服务的公开/声明端口从 80 更改为 80 和 81,但我仍然收到 ConnectionRefused 错误
【解决方案4】:
var result = client.GetAsync("http://serviceb:65112/api/actioname").Result;

您已经在 docker-compose 中为服务 B 公开了端口 65112,因此请使用相同的端口

【讨论】:

  • 我已经尝试过了,我仍然得到同样的错误,但我认为我必须使用容器端口而不是主机端口来进行容器通信
猜你喜欢
  • 2017-05-09
  • 2021-02-21
  • 2020-07-08
  • 1970-01-01
  • 1970-01-01
  • 2018-10-23
  • 2019-10-20
  • 2022-01-26
  • 2022-08-05
相关资源
最近更新 更多