Отказано в соединении по запросу API между контейнерами с docker compose - PullRequest
2 голосов
/ 12 июня 2019

Я разрабатываю мультиконтейнерное приложение Docker и хочу, чтобы контейнер отправлял HTTP-запрос к API другого контейнера с помощью Docker Compose. Я получаю сообщение об ошибке "Отказано в соединении".

Оба контейнера работают на ASP.NET Core 2.2. Я использую IDE Visual Studio 2017. Я тестирую вызовы API с почтальоном.

Вещи, которые я пробовал:
Orts Порты выставляются в Dockerfiles
Orts Порты объявлены в конфигурации Docker Compose
✔️ В Docker Compose объявлена ​​сеть и к ней подключены контейнеры
RI URI запроса HTTP использует имя службы, а не localhost или локальный IP-адрес
RI URI запроса Http использует порт контейнера (80), а не порт хоста
✔️ Брандмауэр отключен
✔️ Пользовательская сеть с пользовательской подсетью
✔️ Пользовательский IPv4-адрес для услуг
✔️ Docker Compose понижен до v2.4 (для указания шлюза в пользовательских сетях)
✔️ Удалить и воссоздать сервисный проект
✔️ Перешли с базового использования 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 Get на serviceA (с хост-портом 51841) с почтальоном на http://localhost:51841/api/foo Я бы хотел получить ответ от действия Bar в serviceB.

Но я получаю Соединение отказано

Необработанные детали исключения:

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

Если я получаю доступ к bash serviceA и выполняю команду ping на serviceB (и, в частности, serviceB: 80 port), он работает:

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 с помощью CURL, но полученная длина содержимого равна 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 (перенаправленный порт закрыт) * ​​1043 *

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 голосов
/ 19 июня 2019

HTTP-запросы ServiceA перенаправлялись (код состояния HTTP 307) на https://serviceb:44359/api/bar, являющийся :44359 портом хоста для HTTPS. Порты хоста не доступны между контейнерами, порты контейнера делают. Поэтому, если я получаю доступ к терминалу serviceA и отправляю HTTP-запрос с curl подробным -v после перенаправления -L на URI http://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

Почему меня перенаправили на порт хоста?

В 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();

            ...

        }
    }
0 голосов
/ 12 июня 2019
var result = client.GetAsync("http://serviceb:65112/api/actioname").Result;

вы указали порт 65112 для службы B в docker-compose, поэтому используйте тот же порт

0 голосов
/ 17 июня 2019

но это не так, когда я выполняю команду ping на API REST службыB:

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 использует ICMP, а не TCP и не HTTP, который работает поверх TCP. Вы пропингуете хост, а не порт TCP или HTTP API. Таким образом, вышеупомянутое, как ожидается, потерпит неудачу.

Я также могу установить соединение с конечной точкой API REST с помощью CURL, но Контент-длина получена 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 для связи, вам больше нечего отлаживать в конфигурации ваших сетей, и вы знаете, что ваш сервисный сервер прослушивает.

Единственное, что осталось отладить:

  • Убедитесь, что servicea подключается к serviceb через порт 80, а не 65112 или любой другой порт хоста. Контейнеры взаимодействуют между собой через порты контейнеров.
  • Убедитесь, что вы используете опубликованный код, а не предыдущую версию. Это легко ошибиться при создании и развертывании изображений, особенно если вы не меняете тег изображения для каждой сборки.
  • Убедитесь, что вы даете serviceb время для запуска. Я часто видел эти ошибки, когда servicea запускается до serviceb.

Если у вас все еще есть проблемы, вы можете начать отладку с помощью таких инструментов, как tcpdump. Э.Г.

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

Замените $container_id идентификатором serviceb, чтобы увидеть все TCP-запросы к порту 80 в этом контейнере.

0 голосов
/ 12 июня 2019

Вы также должны указать порт при подключении из той же сети Docker:

var result = client.GetAsync("http://serviceb:80/api/actioname").Result;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...