Докер: Взаимодействие между двумя .NET АПИ

Вопрос или проблема

Я пытаюсь установить связь между двумя API .BET внутри контейнера Docker. Я загружаю оба API и базу данных через docker-compose, могу получить доступ к обоим API по отдельности, но когда я пытаюсь отправить данные из одного API в другой через маршрут от “API A” к “API B”, я получаю ошибку ниже.

2024-11-10 09:19:45 info: Microsoft.EntityFrameworkCore.Migrations[20405]
2024-11-10 09:19:45       Миграции не были применены. База данных уже обновлена.
2024-11-10 09:19:45 info: Microsoft.Hosting.Lifetime[14]
2024-11-10 09:19:45       Теперь слушает на: http://[::]:4000
2024-11-10 09:19:45 info: Microsoft.Hosting.Lifetime[14]
2024-11-10 09:19:45       Теперь слушает на: https://[::]:4001
2024-11-10 09:19:45 info: Microsoft.Hosting.Lifetime[0]
2024-11-10 09:19:45       Приложение запущено. Нажмите Ctrl+C для завершения работы.
2024-11-10 09:19:45 info: Microsoft.Hosting.Lifetime[0]
2024-11-10 09:19:45       Среда размещения: Разработка
2024-11-10 09:19:45 info: Microsoft.Hosting.Lifetime[0]
2024-11-10 09:19:45       Путь корневого контента: /app
2024-11-10 09:20:06 info: System.Net.Http.HttpClient.IItemRepository.LogicalHandler[100]
2024-11-10 09:20:06       Начало обработки HTTP-запроса POST https://itemservice_api/api/Item
2024-11-10 09:20:06 info: System.Net.Http.HttpClient.IItemRepository.ClientHandler[100]
2024-11-10 09:20:06       Отправка HTTP-запроса POST https://itemservice_api/api/Item
2024-11-10 09:20:06 fail: Restaurante_Api.Middlewares.ErrorHandlingMiddleware[0]
2024-11-10 09:20:06       Внутренняя ошибка сервера ErrorMessage { Code = 01.00, Message = Внутренняя ошибка сервера. }
2024-11-10 09:20:06       System.Net.Http.HttpRequestException: Соединение отказано (itemservice_api:443)
2024-11-10 09:20:06        ---> System.Net.Sockets.SocketException (111): Соединение отказано
2024-11-10 09:20:06          at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
2024-11-10 09:20:06          at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          --- Конец трассировки стека внутреннего исключения ---
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
2024-11-10 09:20:06          at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
2024-11-10 09:20:06          at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendCoreAsync>g__Core|5_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
2024-11-10 09:20:06          at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendCoreAsync>g__Core|5_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
2024-11-10 09:20:06          at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
2024-11-10 09:20:06          at Restaurante_Repositories.Repositories.ItemRepository.Create(CreateItemRequest item) in /src/Restaurante_Repositories/Repositories/ItemRepository.cs:line 25
2024-11-10 09:20:06          at Restaurante_UseCases.UseCases.CreateItemUseCase.Execute(CreateItemRequest request) in /src/Restaurante_UseCases/UseCases/CreateItemUseCase.cs:line 20
2024-11-10 09:20:06          at Restaurante_Api.Controllers.ItemController.Create(CreateItemRequest request) in /src/Restaurante_Api/Controllers/ItemController.cs:line 21
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
2024-11-10 09:20:06          at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
2024-11-10 09:20:06          at Restaurante_Api.Middlewares.ErrorHandlingMiddleware.Invoke(HttpContext context, ILogger`1 logger) in /src/Restaurante_Api/Middlewares/ErrorHandlingMiddleware.cs:line 16

Я собрал docker-compose следующим образом

services:
  restaurante_api:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=4000
      - ASPNETCORE_HTTPS_PORTS=4001
    container_name: restaurante_api
    hostname: restaurante_api
    image: ${DOCKER_REGISTRY-}restauranteapi
    build:
      context: .
      dockerfile: Restaurante_Api/Dockerfile
    ports:
      - "4000:4000"
      - "4001:4001"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
    networks:
      restaurante-bridge:
    depends_on:
      itemservice_api:
        condition: service_started
      sqlserver:
        condition: service_healthy

  sqlserver:
    image: mcr.microsoft.com/mssql/server:2022-preview-ubuntu-22.04
    container_name: sqlserver   
    hostname: sqlserver
    ports:
      - "1433:1433"
    environment:
      - SA_PASSWORD=leoQueres123
      - ACCEPT_EULA= Y       
    volumes:
      - ${APPDATA}/sqlserverrestaurante/data:/var/opt/mssql/data
      - ${APPDATA}/sqlserverrestaurante/log:/var/opt/mssql/log   
    networks:
      - restaurante-bridge
    healthcheck:
      test: /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "leoQueres123" -Q "SELECT 1" -b -o /dev/null
      interval: 10s
      timeout: 3s
      retries: 10
      start_period: 10s 
  
  itemservice_api: 
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=5000
      - ASPNETCORE_HTTPS_PORTS=5001
    container_name: itemservice_api
    hostname: itemservice_api
    image: ${DOCKER_REGISTRY-}itemserviceapi
    build:
      context: .
      dockerfile: ItemService_Api/Dockerfile
    ports:
      - "5000:5000"
      - "5001:5001"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
    networks:
      restaurante-bridge:
    depends_on:
      sqlserver:
        condition: service_healthy


networks:
  restaurante-bridge:    
    driver: bridge
    external: true

docker-compose:
restaurante-bridge:
driver: bridge`

Dockerfile каждого приложения

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 5000
EXPOSE 5001

# Эта фаза используется для компиляции проекта сервиса
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ItemService_Api/ItemService_Api.csproj", "ItemService_Api/"]
COPY ["ItemService_Borders/ItemService_Borders.csproj", "ItemService_Borders/"]
COPY ["ItemService_Repositories/ItemService_Repositories.csproj", "ItemService_Repositories/"]
COPY ["ItemService_UseCases/ItemService_UseCases.csproj", "ItemService_UseCases/"]
RUN dotnet restore "./ItemService_Api/ItemService_Api.csproj"
COPY . .
WORKDIR "/src/ItemService_Api"
RUN dotnet tool install --global dotnet-ef 
ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet build "./ItemService_Api.csproj" -c $BUILD_CONFIGURATION -o /app/build

# Эта фаза используется для публикации проекта сервиса, который будет скопирован в финальную фазу
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ItemService_Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# Эта фаза используется в производстве или когда выполняется в VS в обычном режиме (стандартный режим, когда не используется конфигурация отладки)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT dotnet ef database update --environment Development --project src/ItemService_Repositories
ENTRYPOINT ["dotnet", "ItemService_Api.dll"]
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 4000
EXPOSE 4001

# Эта фаза используется для компиляции проекта сервиса
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Restaurante_Api/Restaurante_Api.csproj", "Restaurante_Api/"]
COPY ["Restaurante_Borders/Restaurante_Borders.csproj", "Restaurante_Borders/"]
COPY ["Restaurante_Repositories/Restaurante_Repositories.csproj", "Restaurante_Repositories/"]
COPY ["Restaurante_UseCases/Restaurante_UseCases.csproj", "Restaurante_UseCases/"]
RUN dotnet restore "./Restaurante_Api/Restaurante_Api.csproj"
COPY . .
WORKDIR "/src/Restaurante_Api"
RUN dotnet tool install --global dotnet-ef 
ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet build "./Restaurante_Api.csproj" -c $BUILD_CONFIGURATION -o /app/build

# Эта фаза используется для публикации проекта сервиса, который будет скопирован в финальную фазу
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Restaurante_Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# Эта фаза используется в производстве или когда выполняется в VS в обычном режиме (стандартный режим, когда не используется конфигурация отладки)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT dotnet ef database update --environment Development --project src/Restaurante_Repositories
ENTRYPOINT ["dotnet", "Restaurante_Api.dll"]

Может кто-нибудь помочь мне понять эту проблему? Я сделал все, что мог, и не могу это решить.

Ответ или решение

Для успешного взаимодействия между двумя .NET API в контейнерах Docker, необходимо учесть несколько ключевых аспектов, касающихся настройки сети, конфигурации и обработки ошибок. Ваша проблема, судя по предоставленным логам и конфигурации, заключается в невозможности API A установить соединение с API B, что вызывает ошибку "Connection refused".

Анализ проблемы

Ошибка HttpRequestException: Connection refused (itemservice_api:443) указывает на то, что API A не может подключиться к API B по указанному адресу и порту. Это может быть вызвано несколькими факторами:

  1. Сетевые настройки: Убедитесь, что оба контейнера находятся в одной сети restaurante-bridge. Судя по вашему docker-compose.yml, они действительно находятся в одной сети, но стоит перепроверить.

  2. Протоколы и порты: В ваших API вы указали, что они работают на HTTPS, однако в запросе вы пытаетесь подключиться к itemservice_api:443, что является стандартным портом для HTTPS. Проверьте, что API B слушает на этом порту, и корректно настроен для обработки HTTPS-запросов.

  3. Работа с сертификатами: Если ваше приложение использует HTTPS, убедитесь, что сертификат для HTTPS корректно настроен внутри контейнера. Можно временно отключить HTTPS в процессе отладки, чтобы проверить, воспроизводится ли проблема.

  4. Задержки при запуске: Контейнеры могут не быть готовы к моменту, когда вы пытаетесь отправить запрос. Убедитесь, что API B полностью инициализирован и слушает на порту, прежде чем API A попытается установить соединение.

Рекомендации по решению

  1. Проверка конфигурации сети:
    Убедитесь, что вы можете пинговать контейнеры друг с другом. Это можно сделать с помощью команды:

    docker exec -it <container_name> ping itemservice_api
  2. Проверка доступности API B:
    Используйте команду curl из контейнера API A, чтобы проверить, доступен ли API B:

    curl -v https://itemservice_api:5001/api/Item

    Замените порт на 5001 для HTTPS, основываясь на вашей конфигурации.

  3. Настройка логирования:
    Добавьте более подробное логирование в API B, чтобы понимать, принимает ли он запросы и что делает с ними.

  4. Изменение порта:
    Если вы продолжаете сталкиваться с проблемами подключения, вы можете временно изменить API B на использование HTTP с открытым портом, чтобы исключить проблемы с сертификатом.

  5. Задержка на запуске:
    Добавьте зависимость depends_on в вашем docker-compose.yml, убедившись, что она настроена и на уровень готовности Service B:

    - itemservice_api:
       condition: service_healthy

Заключение

Убедитесь, что все компоненты вашей системы настроены корректно и следуйте приведенным выше рекомендациям для диагностики. Если вам потребуется дополнительная помощь, предоставьте больше информации о конфигурации ответов сервиса и среду выполнения приложения. Подходите к проблеме системно и на уровне сети, это поможет избежать большинства распространенных ошибок при работе с контейнеризацией.

Оцените материал
Добавить комментарий

Капча загружается...