TestContainers:容器之间的通信+映射在港口外

发布于 2025-01-21 09:04:43 字数 2886 浏览 0 评论 0原文

我有这样的测试设置:

  1. myService连接到postgtresql
  2. myService endpoint从test Suite中调用了

myService and 和<代码> PostgreSQL 正在使用TestContainers运行。

这是我要实现的网络模式。

起初,我试图通过暴露端口来安排通信。

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION));

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", postgres.getJdbcUrl(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

根据logs myService无法建立与PostgreSQL的连接。

Caused by: java.net.ConnectException: Connection refused

然后,我配置了两种服务以共享同一网络。

static final Network SHARED_NETWORK = Network.newNetwork();

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION))
                 .withNetwork(SHARED_NETWORK)
                 .withNetworkAliases("postgres");

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", "jdbc:postgresql://postgres:5432/" + postgres.getDatabaseName(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withNetwork(SHARED_NETWORK)
                .withNetworkAliases("MyService")
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

现在MyService成功地与PostgreSQL 建立了连接。但是,当我从测试套件中执行myService的http请求时,我会遇到相同的错误。

restTemplate.getForObject("http://" + myService.getHost() + ":" + myService.getMappedPort(8080) +"/api/endpoint", Void.class)
Caused by: java.net.ConnectException: Connection refused

我的问题是如何设置容器网络以使该体系结构起作用?

I have such test setup:

  1. MyService connects to PostgtreSQL
  2. MyService endpoint is being called from test suite

Both MyService and PostgreSQL are being run with Testcontainers.

Here is the network schema I want to achieve.

Network Schema

At first I tried to arrange communication by exposing ports.

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION));

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", postgres.getJdbcUrl(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

According to logs MyService couldn't establish connection to PostgreSQL.

Caused by: java.net.ConnectException: Connection refused

Then I configured both services to share the same network.

static final Network SHARED_NETWORK = Network.newNetwork();

static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>(DockerImageName.parse(POSTGRES_VERSION))
                 .withNetwork(SHARED_NETWORK)
                 .withNetworkAliases("postgres");

static final GenericContainer<?> myService = new GenericContainer<>(DockerImageName.parse(MY_SERVICE_IMAGE))
                .withEnv(
                    Map.of(
                        "SPRING_DATASOURCE_URL", "jdbc:postgresql://postgres:5432/" + postgres.getDatabaseName(),
                        "SPRING_DATASOURCE_USERNAME", postgres.getUsername(),
                        "SPRING_DATASOURCE_PASSWORD", postgres.getPassword()
                    )
                )
                .withExposedPorts(8080)
                .withNetwork(SHARED_NETWORK)
                .withNetworkAliases("MyService")
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("MyService")))

Now MyService has established connection with PostgreSQL successfully. But when I perform HTTP request to MyService from the test suite, I get the same error.

restTemplate.getForObject("http://" + myService.getHost() + ":" + myService.getMappedPort(8080) +"/api/endpoint", Void.class)
Caused by: java.net.ConnectException: Connection refused

My question is how can I setup the containers network to make this architecture work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烟雨扶苏 2025-01-28 09:04:43

您需要指定端口绑定以将端口暴露于“外部世界”。

与您想要的类似的示例:

  Network network = Network.newNetwork();
  GenericContainer mariaDbServer = getMariaDbContainer(network);
  GenericContainer flywayRunner = getFlywayContainer(network);
  ...
  @SuppressWarnings("rawtypes")
  private GenericContainer getMariaDbContainer(Network network) {

    return new GenericContainer<>("mariadb:10.4.21-focal")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "mysqld", "--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4",
            "--collation-server=utf8mb4_unicode_ci").withNetwork(network).withNetworkAliases("somedatabasedb")
        .withNetworkMode(network.getId())
        .withExposedPorts(3306).withCreateContainerCmdModifier(
            cmd -> cmd.withNetworkMode(network.getId()).withHostConfig(
                    new HostConfig()
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(20306), new ExposedPort(3306))))
                .withNetworkMode(network.getId())).withStartupTimeout(Duration.ofMinutes(2L));
  }

  @SuppressWarnings("rawtypes")
  private GenericContainer getFlywayContainer(Network network) {

    return new GenericContainer<>("flyway/flyway:7.15.0-alpine")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "-url=jdbc:mariadb://somedatabasedb -schemas=somedatabase-user=root -password=password -connectRetries=300 migrate")
        .withFileSystemBind(Paths.get(".", "infrastructure/database/schema").toAbsolutePath().toString(),
            "/flyway/sql", BindMode.READ_ONLY).withNetwork(network).waitingFor(
            Wait.forLogMessage(".*Successfully applied.*", 1)
        ).withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
  }

两个容器使用“内部”端口与容器一通信。

容器One将20306(重定向到3306)端口公开到“外界”。

You need to specify port bindings to expose a port to the "outside world".

Example similar to what you want:

  Network network = Network.newNetwork();
  GenericContainer mariaDbServer = getMariaDbContainer(network);
  GenericContainer flywayRunner = getFlywayContainer(network);
  ...
  @SuppressWarnings("rawtypes")
  private GenericContainer getMariaDbContainer(Network network) {

    return new GenericContainer<>("mariadb:10.4.21-focal")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "mysqld", "--default-authentication-plugin=mysql_native_password", "--character-set-server=utf8mb4",
            "--collation-server=utf8mb4_unicode_ci").withNetwork(network).withNetworkAliases("somedatabasedb")
        .withNetworkMode(network.getId())
        .withExposedPorts(3306).withCreateContainerCmdModifier(
            cmd -> cmd.withNetworkMode(network.getId()).withHostConfig(
                    new HostConfig()
                        .withPortBindings(new PortBinding(Ports.Binding.bindPort(20306), new ExposedPort(3306))))
                .withNetworkMode(network.getId())).withStartupTimeout(Duration.ofMinutes(2L));
  }

  @SuppressWarnings("rawtypes")
  private GenericContainer getFlywayContainer(Network network) {

    return new GenericContainer<>("flyway/flyway:7.15.0-alpine")
        .withEnv(Map.of("MYSQL_ROOT_PASSWORD", "password", "MYSQL_DATABASE", "somedatabase"))
        .withCommand(
            "-url=jdbc:mariadb://somedatabasedb -schemas=somedatabase-user=root -password=password -connectRetries=300 migrate")
        .withFileSystemBind(Paths.get(".", "infrastructure/database/schema").toAbsolutePath().toString(),
            "/flyway/sql", BindMode.READ_ONLY).withNetwork(network).waitingFor(
            Wait.forLogMessage(".*Successfully applied.*", 1)
        ).withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));
  }

Container two communicates with container one using "internal" port.

Container one exposes 20306 (that redirects to 3306) port to the "outside world".

酒浓于脸红 2025-01-28 09:04:43

Docker自定义网络具有自己的DNS,您无法使用外部网络的DNS。默认的主机网络共享外部和内部网络的DNS,但有一些缺点。要将数据库容器连接到服务容器,您可以使用- 网络-Alias将Docker转换为IP( https://docs.docker.com/get-started/07_multi_container/ )。例如:

 postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("cars")
            .withUsername("cars")
            .withPassword("cars")
            .withNetwork(network)
            .withNetworkAliases(“database”)
            .withLogConsumer(new Slf4jLogConsumer(log));
    postgres.start(); 

carsModelsService = new GenericContainer<>("cars/car-models-service-core:latest")
            .withExposedPorts(8180)
            .withNetwork(network)
            .withEnv("POSTGRES_HOST", “database”)
            .dependsOn(postgres)
            .withLogConsumer(new Slf4jLogConsumer(log));
    carsModelsService.start();

where:

application.yml

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://${POSTGRES_HOST}:5432/cars
    username: cars
    password: cars

The docker custom network has its own DNS and you cannot use DNS of the outer network. The default host network shares DNS of the outer and inner networks, but has some disadvantages. To connect a database container to a service container you can use --network-alias that docker converts to ip (https://docs.docker.com/get-started/07_multi_container/ ). For example:

 postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("cars")
            .withUsername("cars")
            .withPassword("cars")
            .withNetwork(network)
            .withNetworkAliases(“database”)
            .withLogConsumer(new Slf4jLogConsumer(log));
    postgres.start(); 

carsModelsService = new GenericContainer<>("cars/car-models-service-core:latest")
            .withExposedPorts(8180)
            .withNetwork(network)
            .withEnv("POSTGRES_HOST", “database”)
            .dependsOn(postgres)
            .withLogConsumer(new Slf4jLogConsumer(log));
    carsModelsService.start();

where:

application.yml

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://${POSTGRES_HOST}:5432/cars
    username: cars
    password: cars
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文