Skip to content

[Bug]: Two custom containers can't connect on docker using testcontainers #8731

Description

@dbraun1991

Module

Core

Testcontainers version

1.19.8

Using the latest Testcontainers version?

Yes

Host OS

MacOS

Host Arch

ARM

Docker version

Client:
 Cloud integration: v1.0.35+desktop.13
 Version:           26.1.1
 API version:       1.45
 Go version:        go1.21.9
 Git commit:        4cf5afa
 Built:             Tue Apr 30 11:44:56 2024
 OS/Arch:           darwin/arm64
 Context:           desktop-linux

Server: Docker Desktop 4.30.0 (149282)
 Engine:
  Version:          26.1.1
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.9
  Git commit:       ac2de55
  Built:            Tue Apr 30 11:48:04 2024
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.6.31
  GitCommit:        e377cd56a71523140ca6ae87e30244719194a521
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

What happened?

=== First ===
Java code from the testcontainers library behaves differently.

  • MacOS on "MacBook Pro from 2023" with "Apple M2 Pro Silicon" fails
  • Gitlab Pipeline with linux runner executes successfully

=== Second ===
The bookingEndpoint gets an IP that is not sufficient for connecting.
Neither of the following ...

=== Third ===
Executing the JARs without Testcontainers locally -> works as a charm, just as in gitlab pipeline

`
// Static definitions which are lifted before all tests

// Define the NATS & MySql
@ClassRule
public static GenericContainer<?> natsContainer =
        new GenericContainer<>(
                DockerImageName.parse("nats:alpine3.19"))
                .withExposedPorts(4222) // Default MQTT port
                .withNetwork(network)
                .withNetworkAliases("natsmb")
                .waitingFor(Wait.forListeningPort());

@ClassRule
public static MySQLContainer<?> mysqlContainer =
        new MySQLContainer<>("mysql:8.2")
                .withDatabaseName("testdb")
                .withUsername("test")
                .withPassword("test")
                // Adjust the service network
                .withNetwork(network)
                .withNetworkAliases("mysqldb");

// Define the container for the AuthEndpoint using the built image
@ClassRule
public static GenericContainer<?> authEndpoint =
        new GenericContainer<>(authEndpointDockerImage)
                .withExposedPorts(authEndpointPort)
                // Adjust the service network
                .withNetwork(network)
                .withNetworkAliases("authEndpoint")

                // Adjust the 'infrastructure'-connections
                .withEnv("DB_URL", "jdbc:mysql://mysqldb:3306/testdb")
                .withEnv("DB_USERNAME", "test")
                .withEnv("DB_PASSWORD", "test")
                .withEnv("NATS_URL", "nats://natsmb:4222")
                .withEnv("INSTANCE_INDICATOR", "AUTH")
                .withEnv("INITIAL_DELAY", "130000")
                .withEnv("FIXED_INTERVAL", "12000000")

                // Add a wait strategy for the AuthEndpoint, ensuring the mysqlContainer is up first
                .dependsOn(mysqlContainer, mysqlContainer)
                // AuthEndpoint is ready => actuator is open for connection:
                .waitingFor(Wait.forHttp("/actuator").forStatusCode(200));

// Define the container for BookingEndpoint using the built image
@ClassRule
public static GenericContainer<?> bookingEndpoint =
        new GenericContainer<>(bookingEndpointDockerImage)
                .withExposedPorts(bookingEndpointPort)
                // Adjust the service network
                .withNetwork(network)
                .withNetworkAliases("bookingEndpoint")

                // Adjust the 'infrastructure'-connections
                .withEnv("DB_URL", "jdbc:mysql://mysqldb:3306/testdb")
                .withEnv("DB_USERNAME", "test")
                .withEnv("DB_PASSWORD", "test")
                .withEnv("NATS_URL", "nats://natsmb:4222")
                .withEnv("INSTANCE_INDICATOR", "BOOKING")
                .withEnv("AUTH_URL", "")
                .withEnv("INITIAL_DELAY", "1000")
                .withEnv("FIXED_INTERVAL", "5000")

                // Add a wait strategy for the BookingEndpoint, ensuring the AuthEndpoint is up first
                .dependsOn(authEndpoint)
                // BookingEndpoint is ready => actuator is open for connection:
                .waitingFor(Wait.forHttp("/actuator").forStatusCode(200));


static {
    OffsetDateTime start = OffsetDateTime.now();
    logger.info(String.format("Setup start at: %s", start));

    logger.info("==============================================");
    logger.info("=====     Starting the message broker    =====");
    logger.info("==============================================");
    // Start the message broker
    natsContainer.start();
    // === Set system properties ===
    // --> MessageQueue
    String natsURL = String.format("nats://%s:%s",
            natsContainer.getHost(),
            natsContainer.getMappedPort(4222));
    System.setProperty("NATS_URL", natsURL);

    // Start the database
    logger.info("==============================================");
    logger.info("=====        Starting the database       =====");
    logger.info("==============================================");
    mysqlContainer.start();
    // === Set system properties ===
    // --> Database
    System.setProperty("DB_URL", mysqlContainer.getJdbcUrl());
    System.setProperty("DB_USERNAME", mysqlContainer.getUsername());
    System.setProperty("DB_PASSWORD", mysqlContainer.getPassword());

    // Start the AuthEndpoint
    logger.info("==============================================");
    logger.info("=====     Starting the Auth-Endpoint     =====");
    logger.info("==============================================");
    authEndpoint.start();
    authEndpointBaseURL =
            "http://" + getHostIp(authEndpoint) + ":" + authEndpoint.getMappedPort(authEndpointPort);
    // authEndpointBaseURL2 = "http://authEndpoint:" + authEndpoint.getMappedPort(authEndpointPort);

    bookingEndpoint.withEnv("AUTH_URL", authEndpointBaseURL);
    logger.info("AuthUrl given to BookingEndpoint definition via ENV:   " + authEndpointBaseURL);

    bookingEndpoint.start();

    // === Set general system properties ===
    System.setProperty("INSTANCE_INDICATOR", "VALIDATOR");

    Duration duration = Duration.between(start, OffsetDateTime.now());
    logger.info(String.format("Setup took %s seconds", duration.getSeconds()));

    // Known startup duration is ~30 seconds locally and ~60 seconds in pipeline.
    // It shall be known if Environment startup-time drastically changes.
    // assertTrue(duration.getSeconds() < 90);
}

private static String getHostIp(GenericContainer<?> authEndpoint) {

    if (authEndpoint.getHost().equals("localhost")) {
        try {
            // Get IP from Host-Machine 
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
    return authEndpoint.getHost();
}

`

Relevant log output

Custom Log Output:

Additional Information

https://stackoverflow.com/questions/78352477/testcontainers-container-gethost-is-behaving-differently-locally-vs-gitlab-pi

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions