Hello,
I am unable to easily access a test-containers-launched container IP.
Executing:
GenericContainer#getContainerIpAddress() returns "localhost".GenericContainer#getTestHostIpAddress() throws 'java.lang.UnsupportedOperationException' exception which make sense given that I am not running the code in docker-machine (I'm running Ubuntu).GenericContainer#getIpAddress() also returns "localhost" and being a deprecated method would be a last resort if it worked as expected.To obtain a container's actual IP I have to execute the following:
container.getContainerInfo().getNetworkSettings().getNetworks().entrySet().iterator().next().getValue().getIpAddress()
While this workaround above works for me it does not look clean. Is there an alternative way of obtaining a container's IP that I am unaware of?
For context, the container is launched using similar code to as shown below:
new GenericContainer(
new ImageFromDockerfile()
.withFileFromPath("docker-entrypoint.sh", Paths.get(RESOURCES_FOLDER + "docker-entrypoint.sh"))
.withDockerfileFromBuilder(builder -> builder.from(REGISTRY + getDockerImage())
.entryPoint("bash", "/opt/docker-entrypoint.sh")
.cmd("/opt/bin/startup.sh")
.build()))
.withExposedPorts(1199, 1099, 8080)
.waitingFor(Wait.forLogMessage(".*Startup Completed!.*", 1));
Inspecting the containers network information in the terminal I get the following:
"NetworkSettings": {
"Bridge": "",
"SandboxID": "48198b74e9bf808688ace5e13413c9acd2626d2be59e4e0330208e8629dfd92f",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"1099/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "32826"
}
],
"1199/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "32825"
}
],
"8080/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "32824"
}
]
},
"SandboxKey": "/var/run/docker/netns/48198b74e9bf",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "b6004ef4e781adbf46d21555cc849a6e2436be249a6046023c2f97106e05b237",
"Gateway": "172.50.50.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.50.50.4",
"IPPrefixLen": 24,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:32:32:04",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "afd1b30e1bc53cf8c1c336d1de6ab13fed1a132a532629a7cf361a69022f8ac1",
"EndpointID": "b6004ef4e781adbf46d21555cc849a6e2436be249a6046023c2f97106e05b237",
"Gateway": "172.50.50.1",
"IPAddress": "172.50.50.4",
"IPPrefixLen": 24,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:32:32:04",
"DriverOpts": null
}
}
}
Thank you for the amazing project regardless :D
Just keen to understand - do you actually _need_ the IP address? As the container has ports exposed these should be accessible via localhost and getMappedPort(...) for the port. Or is there a difference in your environment that means localhost is incorrect?
(As an aside, yes, we know that getContainerIpAddress is a very bad name for the method when it can feasibly return a hostname. That will change in a major version bump some time 馃槵.)
The exposed ports command in the example is probably just causing more confusion than actually helping... Apologies for that.
Just keen to understand - do you actually need the IP address?
Yes, the docker image in question is a Java-based program which I'm connecting to via Java RMI.
As the container has ports exposed these should be accessible via localhost and getMappedPort(...) for the port.
Mapped ports yes, but RMI works by talking to a registry in a known port (the exposed port) and then further calls being redirected to another port (which is part of a range, defined by configuration) for the actual remote method invocation.
Now, it would defeat the purpose of mapped ports if I had to do a 1:1 mapping of all the ports in the range of ports available for object communication. Essentially this would mean that I could only run 1 container at a time and lose the flexibility that container ports give me. I admit that my example is incomplete and further information was necessary to understand the reason behind this issue.
Or is there a difference in your environment that means localhost is incorrect?
Essentially what happens is I must communicate with the container on a known port inside the container which will return 1 port out of a range (of I believe 10 possible ports) with which to perform the actual remote invocations. This returned port value is the port to be used inside the container, hence why I need to know its IP address.
Is my explanation clear?
P.S: Thank you for the aside :pray: do change the name as soon as possible it is very misleading :rofl:
You can start a proxy first and use the assigned port to hint JMX on which port it is running.
If you're doing it for debugging, see this article:
https://bsideup.github.io/posts/debugging_containers/
If your test actually tests JMX, consider using SocatContainer in front of your target container
The obtained container internal IP is not accessible on some systems like Mac OS X or with remote Docker (obviously), this is why there is no getter for it
Thank you for the blog post @bsideup, I'm not using the JMX ports for debugging, you are absolutely right that in other systems this approach would not work.
Your comment has been very insightful nonetheless. I shall look at SocatContainers.
Thank you!
As a side note, for us the workaround is to use Java itself to provide such value:
````
public String getAddress(){
try {
InetAddress host = InetAddress.getLocalHost();
LOGGER.debug("host is: {}", host);
LOGGER.debug("hostname is: {}", host.getCanonicalHostName());
return host.getCanonicalHostName();
} catch (UnknownHostException e){
LOGGER.error("Unable to find HOST hostname", e);
throw new RuntimeException("Unable to find HOST hostname", e);
}
}
````
Which is not ideal
I have more reliable way to get the IP address - currently I wrote some tests using TestContainers and own network. Then getNetworks() returns unordered set of two networks. Your code worked as russian rulete, because sometimes I retrieved the "bridge" network, sometimes the container's network I gave him in constructor, but this gives stable result (I have little doubts about the 127001, but at least it prevents npe):
/**
* @return IP address usable in container network
*/
public String getVirtualNetworkIpAddress() {
final Collection<ContainerNetwork> networks = getContainerInfo().getNetworkSettings().getNetworks().values();
LOG.trace("networks: {}", networks);
return networks.stream().filter(n -> n.getNetworkID().equals(getNetwork().getId())).findAny()
.map(ContainerNetwork::getIpAddress).orElse("127.0.0.1");
}
I faced the same issue: when using an explicit network for my containers, I'm unable to get the container IP in this network and wrote the same code as https://github.com/testcontainers/testcontainers-java/issues/1719#issuecomment-547658059 .
I didn't test on something else than Linux (no docker for {Mac,Windows}) but our testsuite works on my dev workstation and on our CI regardless of whether we use an explicit Network or not.
What about changing the implementation of getContainerIpAddress() for this one?
Is there any case where the result is not ok?
I guess it should return a list of IPs because chances are that some addresses are not reachable for a remote docker daemon.
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.
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.
Most helpful comment
You can start a proxy first and use the assigned port to hint JMX on which port it is running.
If you're doing it for debugging, see this article:
https://bsideup.github.io/posts/debugging_containers/
If your test actually tests JMX, consider using SocatContainer in front of your target container
The obtained container internal IP is not accessible on some systems like Mac OS X or with remote Docker (obviously), this is why there is no getter for it