Container Networking using Linux Namespaces — Part2 (Using Docker)

Abhishek Mitra
7 min readJan 4, 2020

--

In this section i will try to explain how docker containers communicate with each other using the same concepts i have explained in my previous article “Container Networking using Namespaces — Part1

Docker revolutionized container deployments by making it simpler for developers to develop and deploy applications at ease with very small footprints and providing an ease to package their software.

Section-1: Docker Networking Types

  1. Docker provides 3 networking types on install
root@machine-1:~# docker network ls
NETWORK ID NAME DRIVER SCOPE
a8ca2c432490 bridge bridge local
b4495ffd6931 host host local
e16ec6bb41de none null local

2. The “none” network type is basically when we create a docker container thats not attached to any network. In which case , the containers will not be able to talk each other or to the host

3. The “host” networking option is used when we create a container and connect it to the host directly. In this case the container has direct access to the Hosts networking stack without making any additional changes.

4. The “bridge” networking option is the most interesting and commonly used option. It is basically the “linux-bridge” option as described in my previous article

## Docker implements the bridge in a similar fashion as we 
## implemented the linux bridge option in previous article
## ip link add docker0 type bridge
root@machine-1:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:50:56:aa:a0:0f brd ff:ff:ff:ff:ff:ff
inet 10.8.10.121/21 brd 10.8.15.255 scope global ens160
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:feaa:a00f/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:99:aa:36:b6 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
root@machine-1:~#
root@machine-1:~#
root@machine-1:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:50:56:aa:a0:0f brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:99:aa:36:b6 brd ff:ff:ff:ff:ff:ff
root@machine-1:~#

Section-2: Communication between Docker Container

  1. For this we will use the “bridge” networking option.
  2. Initially the state of the link “docker0” is DOWN, because there are no containers connected to it using soft-links(patch cables)
  3. We create a “nginx-test” container to demonstrate the container namespace implementation
docker run --name nginx-test -d nginx:alpine

4. To enable “ip netns” command we perform the following hack

## nginx-test is the name of the docker container
pid="$(docker inspect -f '{{.State.Pid}}' "nginx-test")"
mkdir -p /var/run/netns
ln -sf /proc/$pid/ns/net "/var/run/netns/nginx-test"

5. Lets examine the ip address of the link inside docker (we can also do this using docker commands). Here we find one end of the patch cable


root@machine-1:~#
root@machine-1:~# ip netns exec nginx-test ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

6. Similarly when we check the host we see the other end of the patch cable

root@machine-1:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
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
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:50:56:aa:a0:0f brd ff:ff:ff:ff:ff:ff
inet 10.8.10.121/21 brd 10.8.15.255 scope global ens160
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:feaa:a00f/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:99:aa:36:b6 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:99ff:feaa:36b6/64 scope link
valid_lft forever preferred_lft forever
5: veth804d16b@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether e2:5b:71:c4:a3:5c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::e05b:71ff:fec4:a35c/64 scope link
valid_lft forever preferred_lft forever

7. So the 2 ends of the patch cable connecting the container “nginx-test” to the bridge “docker0” is : eth0@if5 === veth804d16b@if4

docker container reconstructed as a namespace

8. Now at this state we have drawn some similarities between our initial article and how docker containers are working . Lets see if we are able to expose the container access to the outside world

9. With current configuration the “nginx” container thats running inside machine1 is connected to the docker0 bridge.

10. The host itself and other containers can access nginx web page on port 80

9 To demonstrate lets create another container as shown below and also figure out the “patch-cable” endpoints for the sake of keeping things aligned with the article

docker run --name nginx-dev -p 8080:80 -d nginx:alpine
Two docker containers connected to docker0
### Accessing nginx-test from nginx-dev
root@machine-1:~# ip netns exec nginx-dev curl http://172.17.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@machine-1:~#
### Accessing nginx-test from machine1 directly
root@machine-1:~# curl http://172.17.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@machine-1:~#

10. However this is not accessible from outside

bash-3.2$ curl http://172.17.0.2:80^C

11. To make this accessible docker documentation suggests to expose the port of the application as a port of the VM . Lets create another container “nginx-prod” with port mapping where port 80 of container is mapped to 8080 on the host machine1

docker run --name nginx-prod -p 8080:80 -d nginx:alpine
nginx-prod created with port-mapping

12. We see the following entry created in the NAT table for PAT and NAT

root@machine-1:~# iptables -t nat  --list -n -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
56 2912 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 2 packets, 104 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 172.17.0.4 172.17.0.4 tcp dpt:80
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
2 104 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.4:80
root@machine-1:~#

13. This is very similar to how we had enabled PAT in the earlier article with namespaces


# PAT for mapping 80 to 8080
iptables –t nat -A PREROUTING -j DNAT --dport 8080 --to destination 80
## Command issues by docker
iptables –t nat -A DOCKER -j DNAT --dport 8080 --to destination 172.17.0.4:80

14. So basically we see the when docker containers are used , the way networking is implemented for containers to talk to each other and to the outside world is basically by manipulating namespaces.

15 The following workflow summarizes the steps needed to establish communication for containers in general. This is the starting point for a Container Network Interface (CNI)

Sequence of steps to enable namespace communication

Conclusion:

I hope the article clearly explains the way docker containers establish communication with other containers and how linux kernel features like namespaces, netfilter are key to enabling container networking.

Docker overlay networking is achieved using CNI drivers which is not discussed here .

--

--