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

时间:2019-06-12 13:05:21

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

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

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

我尝试过的事情:
✔️端口在Dockerfile中公开
✔️端口在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的邮递员向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并ping到serviceB(特别是serviceB的:80端口),它将起作用:

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

我也可以使用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跟随重定向,则出现“拒绝连接”(重定向的端口已关闭)

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怎么办? 添加选项以使用容器端口。更多信息

4 个答案:

答案 0 :(得分:0)

从相同的docker网络连接时,还必须指定端口:

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

  

但是当我对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时出现连接拒绝错误的原因   来自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而不是65112或任何其他主机端口上连接到serviceb。容器在容器端口之间相互通信。
  • 确保您正在运行发布的代码,而不是先前版本。在构建和部署映像时,这很容易出错,特别是如果您没有为每个构建更改映像标签。
  • 确保您给serviceb时间启动。当servicea在serviceb之前开始时,我经常看到这些错误。

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

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

$container_id替换为serviceb,以查看对该容器上端口80的所有TCP请求。

答案 3 :(得分:0)

ServiceA的HTTP请求(HTTP 307状态代码)被重定向到https://serviceb:44359/api/bar,成为:44359 HTTPS的主机端口。容器之间无法访问主机端口,容器端口可以访问主机端口。因此,如果我访问serviceA的终端并在重定向curl到URI -v之后发送-L冗长http://serviceb/api/bar的HTTP请求,则会收到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();

            ...

        }
    }