web-dev-qa-db-ja.com

docker composeを使用したコンテナー間のAPIリクエストで接続が拒否されました

マルチコンテナの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のバーアクションの応答を取得できます。

しかし、接続が拒否されました

生の例外の詳細:

_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
_

[〜#〜]カール[〜#〜]

APIで接続を確立することもできますREST CURLのエンドポイントですが、受け取った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/_にリダイレクトされることを伝えているのがわかりますが、目的の接続ポートは44359(ホストポート)ではなく80(コンテナポート)です。

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

しかし、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

これが、ASP.NETからAPIリクエストを行う際の接続拒否エラーの原因である可能性があります

それはpingの動作方法ではありません。 PingはICMPを使用します。TCPではなく、TCPの上で動作するHTTPではありません。 TCPポートまたはHTTP APIではなく、ホストにpingします。したがって、上記は失敗すると予想されます。

APIで接続を確立することもできますREST CURLのエンドポイントですが、受け取った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)

これは、Dockerコンテナーが通信用に正しく構成されていること、ネットワークの構成でデバッグするものは何もないこと、およびservicebがリッスンしていることを示しています。

デバッグに残された唯一のものは次のとおりです。

  • Serviceaが65112またはその他のホストポートではなく、ポート80でservicebに接続していることを確認します。コンテナーは、コンテナーポートで相互に通信します。
  • 以前のバージョンではなく、投稿されたコードを実行していることを確認してください。特にビルドごとにイメージタグを変更しない場合は、イメージをビルドしてデプロイするときにこれを間違えやすいです。
  • Servicebに開始する時間を与えてください。これらのエラーは、serviceaがservicebの前に開始されるときによく見られます。

それでも問題が解決しない場合は、tcpdumpなどのツールを使用してデバッグを開始できます。例えば。

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

$container_id servicebのIDを使用して、そのコンテナのポート80へのTCPリクエストを表示します。

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

docker-composeでサービスBのポート65112を公開しているため、同じポートを使用します

0
Prashant Sharma