Testcontainers:容器间通信+映射外部端口

Testcontainers: communication between containers + mapped outside port

我有这样的测试设置:

  1. MyService 连接到 PostgtreSQL
  2. MyService 正在从测试套件调用端点

MyServicePostgreSQL 都在 运行 中使用 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")))

根据日志 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

我的问题是如何设置容器网络以使该架构正常工作?

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

与您想要的相似的示例:

  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));
  }

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

容器一将 20306(重定向到 3306)端口暴露给“外部世界”。