Saturday, August 18, 2018

Docker host and bridged networking. Running library/httpd on different ports

Docker provides different networking options. When using the Docker host networking, you don't have the option to create port mappings. When using images like library/httpd:2.4, you don't have the option to update the port on which it runs; it runs by default on port 80. Suppose you want to use the host networking feature and want to run library/httpd:2.4 on different ports, how would you do this?

In this blog I'll explain 2 mechanisms by which you can expose library/httpd on different ports using host networking and how you can do the same using bridged networking. I'll describe several features of the different solutions and consequences for connectivity / host lookup options. At the end of the post I'll give some tips on how to test connectivity between containers.



Using Docker host networking

Image per port

You could create your own Dockerfile and use an ARG (argument) such as below:

FROM httpd:2.4
MAINTAINER Maarten Smeets <maarten.smeets@amis.nl>
LABEL nl.amis.smeetsm.httpd.name="Apache Httpd" nl.amis.smeetsm.httpd.version="2.4"

#COPY ./www/ /usr/local/apache2/htdocs/
ARG PORT
RUN sed -ri "s/^Listen 80/Listen $PORT/g" /usr/local/apache2/conf/httpd.conf
ENTRYPOINT ["httpd-foreground"]

You can build this like:

docker build --build-arg PORT=84 -t smeetsm/httpd:2.4 .

And run it like:

docker run -dit --network host --name my-running-app-01 smeetsm/httpd:2.4

This allows you to build a container for running on a specific port. Drawback of this is that you build the image specifically for running on a single port. If you want containers running on multiple ports, you'd need multiple images.

docker build --build-arg PORT=84 -t smeetsm/httpdport84:2.4 .
docker build --build-arg PORT=85 -t smeetsm/httpdport85:2.4 .
docker build --build-arg PORT=86 -t smeetsm/httpdport86:2.4 .

And run them like

docker run -dit --network host --name my-running-app-01 smeetsm/httpdport84:2.4
docker run -dit --network host --name my-running-app-02 smeetsm/httpdport85:2.4
docker run -dit --network host --name my-running-app-03 smeetsm/httpdport86:2.4

You cannot run the same image on multiple ports. Thus you have to create an image per port and this might not be what you want. Also the containers you create this way are not easy to scale.

Single image running on different ports

A much cleaner solution would be to use the same base image supplied by Apache and supply the port with the run command. You can do this like:

docker run -dit --network host --name my-running-app-01 library/httpd:2.4 /bin/bash -c "sed -ri 's/^Listen 80/Listen 84/g' /usr/local/apache2/conf/httpd.conf && httpd-foreground"

docker run -dit --network host --name my-running-app-01 library/httpd:2.4 /bin/bash -c "sed -ri 's/^Listen 80/Listen 85/g' /usr/local/apache2/conf/httpd.conf && httpd-foreground"

docker run -dit --network host --name my-running-app-01 library/httpd:2.4 /bin/bash -c "sed -ri 's/^Listen 80/Listen 86/g' /usr/local/apache2/conf/httpd.conf && httpd-foreground"

Docker run allows you to specify a single command to be run and you can supply parameters for this command. If you want to run multiple commands after each other, you can use bash with the -c parameter.

The above commands start 3 containers on the specified ports (84,85,86) using the host network driver. This does have the limitation that the containers cannot communicate with each without going over the host interface. They all share the Docker host hostname since they use the Docker host network interface directly. Interesting to see is that they can use their own hostname to directly access different ports on the host.

For example, if I run my-running-app-01 on port 84 using hostname ubuntu-vm and I'm running my-running-app-02 using the same hostname (since the same network interface is used) running on port 85, I can access my-running-app-02 from my-running-app-01 by accessing ubuntu-vm or localhost(!) port 85. my-running-app-01 does not know if my-running-app-02 is running inside a Docker container or is directly hosted on the Docker host.

Bridged networking

Using bridged networking, which is often the default when using Docker, you can create named bridged networks. Hosts on these named bridged networks can find each other by their container name (automatic DNS).

Also bridged networks provide a layer between the host network and the container network. The containers can access other network resources by going through a NAT interface. You have to explicitly map ports if you want to access them from the host or the outside world. Using a bridged network, the software within the container can run on the same port and only the outside port can differ. You thus don't need to update configuration files when you want to run on different ports. In this case you use the same image for the creation of the different containers.

The below example uses the default bridge network. Containers can access each other by IP

docker run -dit --name my-running-app-01 -p 84:80 library/httpd:2.4
docker run -dit --name my-running-app-02 -p 85:80 library/httpd:2.4
docker run -dit --name my-running-app-03 -p 86:80 library/httpd:2.4

The below example uses a named bridge network. The containers can access each other by name.

docker network create --driver bridge my-net
docker run -dit --name my-running-app-01 -p 84:80 --network my-net library/httpd:2.4
docker run -dit --name my-running-app-02 -p 85:80 --network my-net library/httpd:2.4
docker run -dit --name my-running-app-03 -p 86:80 --network my-net library/httpd:2.4

Notes

In order to test network connectivity I used the following:

Networks and containers

In order to find out which networks were used:

docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
3457e6f0a394        bridge              bridge              local
43e8356475ab        host                host                local
bffb13042787        my-net              bridge              local
fc4390096330        none                null                local

In order to find out which container was connected to which network and which IP it used I diid:

docker network inspect my-net

        "Containers": {
            "3398bb1f84504d1d5cb85a107420059dce3b617a91aef6663f526e0f7cd610b0": {
                "Name": "my-running-app-02",
                "EndpointID": "7f8191b81db6718b6f4c8091344e35a1b9641bb591025a6d5aa12699b631fbaf",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "810f87402961d79538238d07a9fb70774621b5f6363878d83884fafc89e382ed": {
                "Name": "my-running-app-01",
                "EndpointID": "5a6c99d83d4d43fec8cb7b6812f1628620f39dd13abf4caa4e5bacbf36f2707a",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        }

The above is just a part of the output but does indicate containers and IP's

Check connectivity from within a container

Enter the container: docker exec -it my-running-app-01 /bin/bash

Since the container is Debian based, I can use Apt to install packages.

apt-get update && apt-get install -y telnet

Try to connect to a specific container from within a specific container

telnet my-running-app-02 80

If I got a response like:

telnet my-running-app-02 80
Trying 172.18.0.3...
Connected to my-running-app-02.
Escape character is '^]'.
^C

A connection could be established.

If I got a response like

telnet my-running-app-02 81
Trying 172.18.0.3...
telnet: Unable to connect to remote host: Connection refused

I could not establish the connection. The above does indicate resolving my-running-app-02 to an IP worked.

If the host also couldn't be resolved, the exception would be like:

telnet whatever 80
telnet: could not resolve whatever/80: Name or service not known

No comments:

Post a Comment