Подключите приложение Spring-Boot в тестовом контейнере к базе данных Postgres

в настоящее время я тестирую MyService, который зависит от AccountService. Для этого я использую @Testcontainers

Я столкнулся с проблемой, что AccountService не может подключиться к базе данных на моем Mac, и я не могу понять, почему, поскольку база данных доступна, и я могу подключиться к ней вручную. Я попытался подключить MyService (не в контейнере, из IntelliJ) к БД, и он отлично работает.

Я предполагаю, что проблема с конфигурацией контейнера докера для AccountService из-за этой ошибки: (см. трассировку стека ниже)

Connection refused: localhost/127.0.0.1:52801

где вы, надеюсь, можете помочь мне решить эту проблему.

Мой тестовый код

    @Testcontainers
    @SpringBootTest
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    @AutoConfigureWebTestClient
    @EnableAutoConfiguration
    class MyServiceIntegrationTest {
    
        private static final String ACCOUNT_SERVICE = "account-service:0.0.1";
        private static final int ACCOUNT_SERVICE_INTERNAL_PORT = 8099;
        private static final String ACCOUNT_SERVICE_DATABASE_NAME = "account-db";
        private static final String DATABASE_USERNAME = "admin";
        private static final String DATABASE_PASSWORD = "password";
    
        @Container
        public static PostgreSQLContainer<?> accountServiceDb = new PostgreSQLContainer<>("postgres:latest")
                .withDatabaseName(ACCOUNT_SERVICE_DATABASE_NAME)
                .withUsername(DATABASE_USERNAME)
                .withPassword(DATABASE_PASSWORD);
        static GenericContainer<?> accountServiceContainer;
  
        @BeforeAll
        static void setup() {
        try (final Network network = Network.newNetwork()) {
            final var accountServiceEnv = Map.of(
                    "R2DBC_URL", "r2dbc:postgresql://"
                            + accountServiceDb.getHost() + ":"
                            + accountServiceDb.getFirstMappedPort()
                            + "/"
                            + accountServiceDb.getDatabaseName(),
                    "DB_USERNAME", DATABASE_USERNAME,
                    "DB_PASSWORD", DATABASE_PASSWORD
            );
    
            accountServiceContainer = new GenericContainer<>(DockerImageName.parse(ACCOUNT_SERVICE))
                    .withNetwork(network)
                    .withNetworkAliases(ACCOUNT_SERVICE)
                    .withEnv(accountServiceEnv)
                    .withExposedPorts(ACCOUNT_SERVICE_INTERNAL_PORT);
               
            accountServiceDb.withNetwork(network);
            accountServiceContainer.start();
        }
    }
        
        @Test
        void intTest(){
            // todo
        }
    }

Трассировка стека из AccountService

2023-02-22 09:16:27 2023-02-22 09:16:27.503  INFO 1 --- [           main] c.c.b.Account.AccountServiceApplication  : Starting AccountServiceApplication using Java 17.0.5 on 358da35fc9e0 with PID 1 (/service.jar started by root in /)
2023-02-22 09:16:27 2023-02-22 09:16:27.517  INFO 1 --- [           main] c.c.b.Account.AccountServiceApplication  : No active profile set, falling back to 1 default profile: "default"
2023-02-22 09:16:30 2023-02-22 09:16:30.833  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2023-02-22 09:16:30 2023-02-22 09:16:30.885  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 33 ms. Found 0 R2DBC repository interfaces.
2023-02-22 09:16:31 2023-02-22 09:16:31.567  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2023-02-22 09:16:32 2023-02-22 09:16:32.436  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 868 ms. Found 2 R2DBC repository interfaces.
2023-02-22 09:16:38 2023-02-22 09:16:38.398 ERROR 1 --- [tor-tcp-epoll-1] c.c.b.s.services.AccountBackendService   : Error by DB creation
2023-02-22 09:16:38 
2023-02-22 09:16:38 io.r2dbc.postgresql.PostgresqlConnectionFactory$PostgresConnectionException: Cannot connect to localhost/<unresolved>:52801
2023-02-22 09:16:38     at io.r2dbc.postgresql.PostgresqlConnectionFactory.cannotConnect(PostgresqlConnectionFactory.java:218) ~[r2dbc-postgresql-0.8.13.RELEASE.jar!/:0.8.13.RELEASE]
2023-02-22 09:16:38     at reactor.core.publisher.Mono.lambda$onErrorMap$31(Mono.java:3811) ~[reactor-core-3.4.26.jar!/:3.4.26]
2023-02-22 09:16:38     at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:94) ~[reactor-core-3.4.26.jar!/:3.4.26]
2023-02-22 09:16:38     at 
SHORTENED
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
2023-02-22 09:16:38 Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: localhost/127.0.0.1:52801
2023-02-22 09:16:38 Caused by: java.net.ConnectException: finishConnect(..) failed: Connection refused
2023-02-22 09:16:38     at io.netty.channel.unix.Errors.newConnectException0(Errors.java:155) ~[netty-transport-native-unix-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.unix.Errors.handleConnectErrno(Errors.java:128) ~[netty-transport-native-unix-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.unix.Socket.finishConnect(Socket.java:359) ~[netty-transport-native-unix-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.doFinishConnect(AbstractEpollChannel.java:710) ~[netty-transport-classes-epoll-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.finishConnect(AbstractEpollChannel.java:687) ~[netty-transport-classes-epoll-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:567) ~[netty-transport-classes-epoll-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:489) ~[netty-transport-classes-epoll-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:397) ~[netty-transport-classes-epoll-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.87.Final.jar!/:4.1.87.Final]
2023-02-22 09:16:38     at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
2023-02-22 09:16:38 
2023-02-22 09:16:38 2023-02-22 09:16:38.416 ERROR 1 --- [tor-tcp-epoll-1] reactor.core.publisher.Operators         : Operator called default onErrorDropped

Dockerfile службы AccountService

FROM secret/copenjdk17:17

EXPOSE 8080
EXPOSE 9090
# JMX port:
EXPOSE 9998
# Java Debug port:
EXPOSE 5004

COPY /build/libs/account-service-0.0.1.jar service.jar

STOPSIGNAL SIGINT

Заранее большое спасибо!

Не очень хорошо знаком с Testcontainers, но как работает контейнер postgres? Привязаны ли порты к хосту? Например, docker run -p 52801:$port .. или у вас установлен postgres на хосте?

Ardit Meti 22.02.2023 09:34

Вы пытаетесь соединить два контейнера друг с другом, и это не работает? В этом случае вам следует использовать сети.

Michał Krzywański 22.02.2023 09:39

Также вы имеете в виду обслуживание счета и службу хранения. Эти службы одинаковы или это просто ошибка копирования и вставки?

Michał Krzywański 22.02.2023 09:40

ошибка копирования, да. Хорошо, я рассмотрю ваше предложение сети.

Anna Klein 22.02.2023 10:20

Можете ли вы использовать базу данных в памяти, такую ​​​​как H2, или фиктивную реализацию промежуточного сервиса вместо тестовых контейнеров здесь? Эта настройка выглядит почти так, как будто вы пытаетесь использовать Testcontainers для запуска всего стека приложений, и вам может быть лучше запустить приложение в обычном режиме с помощью Docker Compose или аналогичного, а затем запустить отдельный интеграционный тест.

David Maze 22.02.2023 12:07

@MichałKrzywański Я добавил сеть в оба контейнера. К сожалению, я получаю ту же ошибку

Anna Klein 22.02.2023 12:09

Вы также изменили хост базы данных в своей службе, чтобы он стал alias контейнера базы данных в этой сети?

Michał Krzywański 22.02.2023 12:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
7
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем коде есть несколько неправильных вещей:

  • нужно подключить оба контейнера к сети
  • к контейнерам можно получить доступ через эту сеть по сетевому псевдониму, который является DNS-именем в этой сети, поэтому вы должны использовать псевдоним контейнера postgres в качестве хоста при подключении из службы к этой базе данных.
  • вы должны получить доступ к postgres db через порт 5432, а не через сопоставленный порт на хосте, потому что они обмениваются данными по сети.

Вы должны использовать сопоставленный порт, если хотите подключиться из localhost Проверьте приведенный ниже код с комментариями, которые должны указать вам правильное направление:

@Testcontainers
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@AutoConfigureWebTestClient
@EnableAutoConfiguration
class MyServiceIntegrationTest {

    private static final String ACCOUNT_SERVICE = "account-service:0.0.1";
    private static final int ACCOUNT_SERVICE_INTERNAL_PORT = 8099;
    private static final String ACCOUNT_SERVICE_DATABASE_NAME = "account-db";
    private static final String DATABASE_USERNAME = "admin";
    private static final String DATABASE_PASSWORD = "password";
    //create network
    private static final Network network = Network.newNetwork();

    private static final String ACCOUNT_SERVICE_DB = "account-service-db";
    
    @Container
    public static PostgreSQLContainer<?> accountServiceDb = new PostgreSQLContainer<>("postgres:latest")
            .withDatabaseName(ACCOUNT_SERVICE_DATABASE_NAME)
            .withUsername(DATABASE_USERNAME)
            .withPassword(DATABASE_PASSWORD)
            //connect postgres db to network
            .withNetwork(network)
            //give the postgres DNS name
            .withNetworkAliases(ACCOUNT_SERVICE_DB);
    static GenericContainer<?> accountServiceContainer;

    @BeforeAll
    static void setup() {
        final Network network = Network.newNetwork();
        final var accountServiceEnv = Map.of(
                "R2DBC_URL", "r2dbc:postgresql://"
                        //use db container DNS alias as host over common network
                        + ACCOUNT_SERVICE_DB + ":"
                        //here we use default postgres exposed port and not host port
                        + "5432"
                        + "/"
                        + accountServiceDb.getDatabaseName(),
                "DB_USERNAME", DATABASE_USERNAME,
                "DB_PASSWORD", DATABASE_PASSWORD
        );

        accountServiceContainer = new GenericContainer<>(DockerImageName.parse(ACCOUNT_SERVICE))
                .withNetwork(network)
                .withNetworkAliases(ACCOUNT_SERVICE)
                .withEnv(accountServiceEnv)
                .withExposedPorts(ACCOUNT_SERVICE_INTERNAL_PORT);

        accountServiceDb.withNetwork(network);
        accountServiceContainer.start();
    }

    @Test
    void intTest() {
        // todo
    }

Другие вопросы по теме