testcontainers / testcontainers-java

Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
https://testcontainers.org
MIT License
7.99k stars 1.64k forks source link

getContainerIpAddress always returns localhost #452

Closed fred777 closed 5 years ago

fred777 commented 7 years ago

I'm trying to use testcontainers (v1.4.2) for integration testing between a Spring Boot service packed into a GenericContainer and a MysqlContainer.

Unfortunately, getContainerIpAddress will always return "localhost".

Hence, ((MySQLContainer) mysql).getJdbcUrl() will just return garbage: jdbc:mysql://localhost:32821/test

My system: Archlinux x64, Docker version 17.07.0-ce But same error on colleague's Mac.

The following part of GenericContainer.java doesn't make sense to me

    @Override
    public String getContainerIpAddress() {
        return DockerClientFactory.instance().dockerHostIpAddress();
    }

-> isn't that just returning the mere ip address of the host running docker service?

What I expected here to see is a call returning the IP address of the virtual network interface related to the desired container.... please correct me if I'm wrong...

LiamMaru commented 7 years ago

I encountered exactly the same problem and, unfortunately, couldn't find an acceptable workaround. If there is a solution to this then I'd love to get my hands on it.

kiview commented 7 years ago

In Groovy it would be possible to access the container's IP address like this:

genericContainer.containerInfo.networkSettings.networks.entrySet().first().value.ipAddress

So it's possible in Java as well, using a bit more verbose code, since the information is available in the object.

@fred777 The general idea would be use the mapped port of the MySQLContainer, which you can already see in the JdbcUrl using the port 32821.

@rnorth I wonder what's the reason in not exposing the container's ip in the public API?

bsideup commented 7 years ago

@fred777 getContainerIpAddress returns an IP address you can use to access your container from your tests, not from other containers. For inter-containers communication I recommend you to use Networks (since TestContainers version 1.4+ )

fred777 commented 7 years ago

As I tried to explain, getContainerIpAddress just returns the string "localhost". Nada IP.

bsideup commented 7 years ago

localhost is an expected behavior here. Just do not pass this as host to the running containers because for them localhost will be something different.

getJdbcUrl returns JDBC URL for the host.

kiview commented 6 years ago

What about adding a API method returning the real host IP address? I think it's needed in some test configurations.

arcuri82 commented 6 years ago

I do have to agree with @fred777. I was confused as well when I saw getting localhost when querying a method that was supposed to return an IP address. If returning localhost is expected behavior, then might be best to change then name of that method. Btw, great work with TestContainers!!! ;)

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe this is a mistake, please reply to this comment to keep it open. If there isn't one already, a PR to fix or at least reproduce the problem in a test case will always help us get back on track to tackle this.

stale[bot] commented 5 years ago

This issue has been automatically closed due to inactivity. We apologise if this is still an active problem for you, and would ask you to re-open the issue if this is the case.

slonopotamus commented 5 years ago

The point of this issue is that localhost is NOT and IP address. It is a hostname.

tristantarrant commented 5 years ago

Not good that this has been closed. I'd rather have container.getContainerAddress() return the actual address on the docker network and have a getMappedAddress() (as a counterpart to getMappedPort()) for the forwarded network mappings.

bsideup commented 5 years ago

@tristantarrant what would be you use case for this?

If you need this value on your host, you may have a problem because Testcontainers supports running the tests with various Docker setups, including the remote ones (even in some Cloud)

If you need it for a communication between the containers, there is a support for networks and network aliases that should be used instead.

The issue was kept closed because it was mostly about the confusion about "getIp returns host" as stated here: https://github.com/testcontainers/testcontainers-java/issues/452#issuecomment-481223442

Also, it got automatically closed due to inactivity 😅

tristantarrant commented 5 years ago

In my use case I have a protocol which sends clients the addresses of all members in the server cluster. While the protocol itself does have a way to remap these addresses, I also want to test its behaviour when the addresses are not remapped.

bsideup commented 5 years ago

@tristantarrant such NAT-unfriendly protocols are covered differently in Testcontainers, take a look at KafkaContainer or CouchbaseContainer.

Also, see Kevin's answer: https://github.com/testcontainers/testcontainers-java/issues/452#issuecomment-331184470

We do expose ContainerInfo object with the information you need, but we don't want to have it as a supported API (getting the internal IP) because it does not work in many cases

tristantarrant commented 5 years ago

Yeah, I'm using that approach.

perlun commented 5 years ago

A recommended read which might be helpful to others googling this issue when trying to connect to their containers: https://www.testcontainers.org/features/networking/

steghio commented 5 years ago

Hello,

I am using testcontainers 1.12.1 and following https://www.testcontainers.org/features/networking/

Getting the container IP address

String ipAddress = container.getContainerIpAddress();

I am using Docker 1.13.1

I am also getting localhost as output from that method.

I would ask to at least modify the documentation to make this caveat very clear and mention workarounds.

I would also expose a method to retrieve the host IP address of the container. In some scenarios when using Zookeeper, it is necessary to publish that and in a distributed environment there is no way of knowing where will the container be started beforehand.

By the way, using Docker Toolbox, this issue does not appear and the actual IP address is returned instead

Radomiej commented 4 years ago

I struggle with this problem a lot in last time(I setup GitLab CI) and I found a one decent workaround:

String ipAddress = container.getContainerInfo().getNetworkSettings().getGateway();

for example:

            String ipAddress = mariaDB.getContainerInfo().getNetworkSettings().getGateway();
            TestPropertyValues.of(
                    "spring.datasource.url=" + mariaDB.getJdbcUrl().replace("localhost", ipAddress),
                    "spring.datasource.username=" + mariaDB.getUsername(),
                    "spring.datasource.password=" + mariaDB.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());

you can also try with --network="host" in your container or similar configuration on host side, but this above should be enought

burmecia commented 2 years ago

The above code returns gateway ip address, not the container's ip address. To get container's ip address:

String ipAddress = container.getContainerInfo().getNetworkSettings().getIpAddress();

Make sure your container network is using Docker's default bridge network.

slonopotamus commented 2 years ago

What getContainerIpAddress returns neither belongs to container nor is an ip address (and instead is a hostname). Possibly this function should just be deprecated and given a different name that doesn't confuse people.

bsideup commented 2 years ago

There is already getHost as a better named method, and you are right, we should deprecate getContainerIpAddress 👍

burmecia commented 2 years ago

But there are some scenario we do need container ip address, not host name. For example:

  1. Container A is a server runs on default bridge network
  2. Container B is a client runs on default bridge network too
  3. We are not allowed to use --link option

Now if B wants to access A, B must know A's ip address because as per Docker's doc:

Containers on the default bridge network can only access each other by IP addresses

bsideup commented 2 years ago

Please see https://www.testcontainers.org/features/networking/ and especially the "Advanced networking" section

burmecia commented 2 years ago

Thanks I already read that doc, but the thing is in my case above, we cannot change Container A and B network config because it is deeply built in the CI/CD workflow and change it will break other parts. And I don't think provide getContainerIpAddress is that hard as it is just one line code, but it can provide both convenience and completeness of your code.

bsideup commented 2 years ago

Sorry, I am not sure I understand your setup.

If you need two containers talking to each other - you should be using networks and network aliases (so that you don't need to know the dynamic ip). Also, we cannot add a method that will query container.getContainerInfo().getNetworkSettings().getIpAddress() because it won't work in many environments.

burmecia commented 2 years ago

I mean in my case we cannot change the containers' network config or add network aliases or do something like that, because it is an already-built CI/CD pipeline. My point is why a simple getContainerIpAddress cannot be implemented. I know if container uses user-defined bridge network, container.getContainerInfo().getNetworkSettings().getIpAddress() will be empty, but we can still loop through container.getNetworkSettings().getNetworks() to find a valid ip address, just like this function did: https://github.com/testcontainers/testcontainers-java/blob/0654ef6a9186eab85df3accd9ea52a37c68853b0/core/src/main/java/org/testcontainers/containers/GenericContainer.java#L885

kiview commented 2 years ago

I mean in my case we cannot change the containers' network config or add network aliases or do something like that, because it is an already-built CI/CD pipeline.

This is hard to understand from our side. In which way is this built into the CI pipeline?

Since you provide an explanation how to get the IP address for your environment, I suggest you use it for your very specific use case. However, since Testcontainers also supports environments where this call chain would explicitly not work, we don't want to expose this as an API in Testcontainers itself.

rnorth commented 2 years ago

I agree - to be honest, I think that your use case is so different from typical users’ that: (a) there would not be much value to our users in providing this API as part of the library (b) if we did add it, it would create significant confusion among users

I think that as @kiview suggests it is better if you continue to use your own code for this use case.

burmecia commented 2 years ago

Thank you all for your replies. I can understand if you think it is not beneficial to add this function. I just comment here for someone else who has same ip address requirement as me.

To get container's ip address:

  1. use container.getContainerInfo().getNetworkSettings().getIpAddress()
  2. if above is not work, search through values of container.getContainerInfo().getNetworkSettings().getNetworks() to find one

To answer @kiview's question, I will describe my scenario below.

I am working on a JDBC-based destination connector for https://github.com/airbytehq/airbyte. They built an automated integration test pipeline using Testcontainers. For each test case, it will create 2 containers:

  1. Container for the destination database such like ClickHouse, Postgres, MySQL etc.

    This one uses Testcontainers and provides JDBC url like jdbc:clickhouse://localhost:8123/default. It uses default bridge network option and expose port 8123.

  2. Container for the connector we developed using JDBC interfaces

    It uses --network host option so it can access the 1st container's db through JDBC url jdbc:clickhouse://localhost:8123/default

This pipeline is already built for many connectors and it runs on both local laptop and CI/CD environment. Thus we, as a connector developer, cannot change its config. It works fine on Linux platform, but on Mac the 2nd container cannot access the 1st. The reason is because --network host is not supported on Mac which means host name localhost cannot be used. I tried use host.docker.internal instead of localhost, but the JDBC driver will take DNS lookup first for that which still fails.

So my solution is do a dirty hack to disable 2nd container use --network host, then both containers are now in same default bridge network. But localhost still doesn't work because default bridge network can only use ip address. Then I have to use ip address in JDBC url like jdbc:clickhouse://172.17.0.2:8123/default to access 1st container.

Since this hack is so dirty I cannot publish it and only used for my local development and brings lots of inconvenience. So my final solution is totally run the integration test pipeline on Linux VM, thus all the problems solved and I don't need ip address anymore.

I am writing here just in case if I need it in the future or for someone else has similar situation like me.

Thanks again.

bsideup commented 2 years ago

@burmecia even after what you described I think you could do it much easier if you follow the best practices.

This one uses Testcontainers and provides JDBC url like jdbc:clickhouse://localhost:8123/default. It uses default bridge network option and expose port 8123.

Actually, the URL will look more like jdbc:clickhouse://localhost:31234/default, where 31234 is how 8123 (inside the container) is exposed to the tests (random port).

Container for the connector we developed using JDBC interfaces It uses --network host option so it can access the 1st container's db through JDBC url jdbc:clickhouse://localhost:8123/default

If you remove --network host and use withNetwork(network).withNetworkAlias("clickhouse") on the ClickHouse container and withNetwork(network) in your second JDBC container, you will be able to use jdbc:clickhouse://clickhouse:8123/default as the URL - no need to know any IPs.

That's how it should be done (instead of using --network host), and it will work on Mac/Windows, on CI, on CI where docker-in-docker is used, and any other environment where you will be running your tests.

I hope this helps.

burmecia commented 2 years ago

Thank you for your suggestions @bsideup, I also think that's an ideal solution. If Airbyte team @sherifnada could implement that will be great. The current situation is that we have no control of the underlying container's network setup, that's why I have to work out some dirty hacks to get ip address.

sherifnada commented 2 years ago

@burmecia apologies - I don't have context on this issue. Do you mind creating an issue in https://github.com/airbytehq/airbyte to address the need? Would be happy to discuss there

kiview commented 2 years ago

@sherifnada thanks for getting in touch, feel free to reach out to us (or pull me into the issue in your repo) if you have any questions regarding patterns for using Testcontainers in your test suite.