Managing Container Networks
When Docker is executed, it creates a virtual bridge called "docker0", so it can manage internal communication between the container and the host as well as between containers.
Let's learn about some parameters of the "Docker container run" command that will help us with the network in which the containers will communicate.
-
--dns -- Specifies the DNS server.
-
--hostname -- Specifies a hostname.
-
--link -- Creates a link between containers, without the need to know each other's IP.
-
--net -- Allows you to configure the network mode you will use with the container. We have four options, but the most well-known and used is "--net=host", which allows the container to use the host's network to communicate and not create a network stack for the container.
-
--expose -- Exposes the container port only.
-
--publish -- Exposes both the container and host ports.
-
--default-gateway -- Determines the default route.
-
--mac-address -- Determines a MAC address.
When the container is started, the network goes through several steps until its complete initialization:
-
A pair of virtual interfaces is created.
-
An interface with a unique name is created, such as "veth1234", and then linked with Docker's bridge, "docker0".
-
With this, the "eth0" interface is made available inside the container, in a unique network namespace.
-
The MAC address of the container's virtual interface is configured.
-
An IP is allocated to the container's "eth0". This IP must belong to the range of the "docker0" bridge.
With this, the container already has a network interface and is ready to communicate with other containers or with the host. :D
10.1. Can I make the container port respond on the host port?​
Yes, this is possible and widely used.
Let's learn a bit more about this through an example using our "linuxtips/apache" image.
The first thing we need to know is the port through which Apache2 communicates. This is easy, right? If it has the default port settings of a web server, the container's Apache2 will be responding on port 80/TCP, correct?
Now let's make port 8080 of our host respond through port 80 of our container, that is, whenever someone hits port 8080 of our host, the request will be forwarded to port 80 of the container. Simple, right?
To accomplish this forwarding, we need to use the "-p" parameter of the "docker container run" command, as we will do in the following example:
root@linuxtips:~# # docker container run -ti -p 8080:80 linuxtips/apache:1.0 /bin/bash
root@4a0645de6d94:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 1 18:18 ? 00:00:00 /bin/bash
root 6 1 0 18:18 ? 00:00:00 ps -ef
root@4a0645de6d94:/# /etc/init.d/apache2 start
[....] Starting web server: apache2AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message
. ok
root@4a0645de6d94:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:18 ? 00:00:00 /bin/bash
root 30 1 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 33 30 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
www-data 34 30 0 18:19 ? 00:00:00 /usr/sbin/apache2 -k start
root 109 1 0 18:19 ? 00:00:00 ps -ef
root@4a0645de6d94:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
74: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:3/64 scope link
valid_lft forever preferred_lft forever
root@4a0645de6d94:/#
Notice that we passed the "-p" parameter as follows:
- -p 8080:80 -- Where "8080" is the host port and "80" is the container port.
With this, we are saying that every request that arrives on port 8080 of my host should be forwarded to port 80 of the container.
Inside the container, we started Apache2 and verified the container's IP, correct?
Now let's exit the container with the "Ctrl + p + q" shortcut. :)
From the host, let's perform a "curl" to the container's IP on port 80, then to port 8080 of the host and then analyze the outputs:
root@linuxtips:~# curl <IPCONTAINER>:80
If everything went well up to here, you will see the code of the Apache2 welcome page.
The same occurs when we execute "curl" again, but hitting the host's IP. See:
root@linuxtips:~# curl <IPHOST>:8080
Very easy, it's tear-jerkingly simple! \o/
10.2. And how does it do this? Magic?​
No, it's not magic! Actually, the command just uses a very old Linux kernel module called netfilter, which provides the iptables tool, which we all have used extensively.
Let's take a look at the iptables rules related to our container. First the filter table:
root@linuxtips:~# iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-ISOLATION all -- 0.0.0.0/0 0.0.0.0/0
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:5000
ACCEPT tcp -- 0.0.0.0/0 172.17.0.3 tcp dpt:80
Chain DOCKER-ISOLATION (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
root@linuxtips:~#
Now the NAT table:
root@linuxtips:~# iptables -L -n -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:5000
MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 to:172.17.0.2:5000
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.3:80
root@linuxtips:~#
As we can notice, we have NAT rules configured that allow DNAT from port 8080 of the host to port 80 of the container. See below:
MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.3:80
All of this done "automagically" by Docker, without the need to configure multiple iptables rules. <3