Я разрабатываю мультиконтейнерное приложение 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?
Добавить опцию, чтобы использовать контейнерный порт. Подробнее в ответе