Skip to content

Bug: Running inside container when mounting /var/run/docker. sock in container does not work #563

@claussa

Description

@claussa

Describe the bug

My test does not work:

tests_integration/utils/custom_elasticsearch_container.py:84: in start
    super().start()
../env/lib/python3.10/site-packages/testcontainers/core/container.py:87: in start
    Reaper.get_instance()
../env/lib/python3.10/site-packages/testcontainers/core/container.py:188: in get_instance
    Reaper._instance = Reaper._create_instance()
../env/lib/python3.10/site-packages/testcontainers/core/container.py:242: in _create_instance
    raise last_connection_exception
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
cls = <class 'testcontainers.core.container.Reaper'>
    @classmethod
    def _create_instance(cls) -> "Reaper":
        logger.debug(f"Creating new Reaper for session: {SESSION_ID}")
    
        Reaper._container = (
            DockerContainer(c.ryuk_image)
            .with_name(f"testcontainers-ryuk-{SESSION_ID}")
            .with_exposed_ports(8080)
            .with_volume_mapping(c.ryuk_docker_socket, "/var/run/docker.sock", "rw")
            .with_kwargs(privileged=c.ryuk_privileged, auto_remove=True)
            .with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout)
            .start()
        )
        wait_for_logs(Reaper._container, r".* Started!")
    
        container_host = Reaper._container.get_container_host_ip()
        container_port = int(Reaper._container.get_exposed_port(8080))
    
        last_connection_exception: Optional[Exception] = None
        for _ in range(50):
            try:
                Reaper._socket = socket()
>               Reaper._socket.connect((container_host, container_port))
E               ConnectionRefusedError: [Errno 111] Connection refused
../env/lib/python3.10/site-packages/testcontainers/core/container.py:228: ConnectionRefusedError

To Reproduce

Run your test on linux in a container while mounting /var/run/docker. sock without specifying DOCKER_HOST.

Runtime environment

Docker image: python:3.10
Testcontainers version: 4.4.0

Source of the bug

Reaper._container.get_container_host_ip() does not return the correct value.

In docker_client.py:

    def host(self) -> str:
        """
        Get the hostname or ip address of the docker host.
        """
        # https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644
        # if os env TC_HOST is set, use it
        host = os.environ.get("TC_HOST")
        if not host:
            host = os.environ.get("TESTCONTAINERS_HOST_OVERRIDE")
        if host:
            return host
        try:
            url = urllib.parse.urlparse(self.client.api.base_url)

        except ValueError:
            return None
        if "http" in url.scheme or "tcp" in url.scheme:
            return url.hostname
        if inside_container() and ("unix" in url.scheme or "npipe" in url.scheme):
            ip_address = default_gateway_ip()
            if ip_address:
                return ip_address
        return "localhost"

This method return localhost because the client api base_url is http+docker://localhost/v1.41/containers/create
So the first condition is true, and the method return localhost.

It should test if we are inside a container and if true, get the default_gateway_ip().

Furthermore this method so throw an error if the subprocess return an error. For now, it just return None if the command ip is not installed.

def default_gateway_ip() -> str:
    """
    Returns gateway IP address of the host that testcontainer process is
    running on

    https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
    """
    cmd = ["sh", "-c", "ip route|awk '/default/ { print $3 }'"]
    try:
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        ip_address = process.communicate()[0]
        if ip_address and process.returncode == 0:
            return ip_address.decode("utf-8").strip().strip("\n")
    except subprocess.SubprocessError:
        return None

Last weird comportement in get_exposed_port:

    def get_exposed_port(self, port: int) -> str:
        mapped_port = self.get_docker_client().port(self._container.id, port)
        if inside_container():
            gateway_ip = self.get_docker_client().gateway_ip(self._container.id)
            host = self.get_docker_client().host()

            if gateway_ip == host:
                return port
        return mapped_port

I don't understand the condition if we are inside a container, if the host is the same as the gateway ip we should return the mapped_port.
In this case, we are in a container, so using the gateway_ip as host we should use the mapped_port to connect to the mapped port on the host.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions