Docker is a lightweight container technology that has gathered enormous interest in recent years. It neatly bundles various Linux kernel features and services, such as namespaces, cgroups, SELinux, and AppArmor profiles, over union filesystems such as AUFS and BTRFS in order to make modular images. These images provide a highly configurable virtualized environment for applications and follow a write once, run anywhere workflow. An application can be composed of a single process running in a Dcker container or it could be made up of multiple processes running in their own containers and being replicated as the load increases. Therefore, there is a need for powerful networking elements that can support various complex use cases.
In this chapter, you will learn about the essential components of Docker networking and how to build and run simple container examples.
This chapter covers the following topics:
Networking and Docker
Docker OVS networking
Unix domain networks
Linking Docker containers
What's new in Docker networking
Docker is getting a lot of traction in the industry because of its performance-savvy and universal replicability architecture, while providing the following four cornerstones of modern application development:
Furthermore, wide-scale adoption of Thoughtworks's microservices architecture, or LOSA (Lots of Small Applications), is further bringing potential to Docker technology. As a result, big companies such as Google, VMware, and Microsoft have already ported Docker to their infrastructure, and the momentum is continued by the launch of myriad Docker start-ups, namely Tutum, Flocker, Giantswarm, and so on.
Since Docker containers replicate their behavior anywhere, be it your development machine, a bare metal server, virtual machine, or data center, application designers can focus their attention on development, while operational semantics are left with DevOps. This makes team workflow modular, efficient, and productive. Docker is not to be confused with a virtual machine (VM), even though they are both virtualization technologies. While Docker shares an OS with providing a sufficient level of isolation and security to applications running in containers, it later completely abstracts away the OS and gives strong isolation and security guarantees. However, Docker's resource footprint is minuscule in comparison to a VM and hence preferred for economy and performance. However, it still cannot completely replace VMs and is therefore complementary to VM technology. The following diagram shows the architecture of VMs and Docker:
Each Docker container has its own network stack, and this is due to the Linux kernel NET namespace, where a new NET namespace for each container is instantiated and cannot be seen from outside the container or from other containers.
Docker networking is powered by the following network components and services.
Various networking components can be used to work with Docker, providing new ways to access and use Docker-based services. As a result, we see a lot of libraries that follow a different approach to networking. Some of the prominent ones are Docker Compose, Weave, Kubernetes, Pipework, libnetwork, and so on. The following figure depicts the root ideas of Docker networking:
docker0 bridge is the heart of default networking. When the Docker service is started, a Linux bridge is created on the host machine. The interfaces on the containers talk to the bridge, and the bridge proxies to the external world. Multiple containers on the same host can talk to each other through the Linux bridge.
docker0 can be configured via the
--net flag and has, in general, four modes:
If we create two containers called Container1 and Container2, both of them are assigned an IP address from a private IP address space and also connected to the docker0 bridge, as shown in the following figure:
Both the preceding containers will be able to ping each other as well as reach the external world.
For external access, their port will be mapped to a host port.
As mentioned in the previous section, containers use network namespaces. When the first container is created, a new network namespace is created for the container. A vEthernet link is created between the container and the Linux bridge. Traffic sent from
eth0 of the container reaches the bridge through the vEthernet interface and gets switched thereafter. The following code can be used to show a list of Linux bridges:
# show linux bridges $ sudo brctl show
The output will be similar to the one shown as follows, with a bridge name and the
veth interfaces on the containers it is mapped to:
bridge name bridge id STP enabled interfaces docker0 8000.56847afe9799 no veth44cb727 veth98c3700
How does the container connect to the external world? The
iptables nat table on the host is used to masquerade all external connections, as shown here:
$ sudo iptables -t nat -L –n ... Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16 ...
How to reach containers from the outside world? The port mapping is again done using the
iptables nat option on the host machine.
Open vSwitch is a powerful network abstraction. The following figure shows how OVS interacts with the VMs, Hypervisor, and the Physical Switch. Every VM has a vNIC associated with it. Every vNIC is connected through a VIF (also called a virtual interface) with the Virtual Switch:
OVS uses tunnelling mechanisms such as GRE, VXLAN, or STT to create virtual overlays instead of using physical networking topologies and Ethernet components. The following figure shows how OVS can be configured for the containers to communicate between multiple hosts using GRE tunnels:
$ docker run --name c1 –v /var/run/foo:/var/run/foo –d –I –t base /bin/bash $ docker run --name c2 –v /var/run/foo:/var/run/foo –d –I –t base /bin/bash
c2 can communicate over the following Unix socket address:
struct sockaddr_un address; address.sun_family = AF_UNIX; snprintf(address.sun_path, UNIX_PATH_MAX, "/var/run/foo/bar" );
bind(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un)); listen(socket_fd, 5); while((connection_fd = accept(socket_fd, (struct sockaddr *) &address, &address_length)) > -1) nbytes = read(connection_fd, buffer, 256);
connect(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un)); write(socket_fd, buffer, nbytes);
In this section, we introduce the concept of linking two containers. Docker creates a tunnel between the containers, which doesn't need to expose any ports externally on the container. It uses environment variables as one of the mechanisms for passing information from the parent container to the child container.
In addition to the environment variable
env, Docker also adds a host entry for the source container to the
/etc/hosts file. The following is an example of the host file:
$ docker run -t -i --name c2 --rm --link c1:c1alias training/webapp /bin/bash [email protected]<container_id>:/opt/webapp# cat /etc/hosts 172.17.0.1 aed84ee21bde ... 172.17.0.2 c1alaias 6e5cdeb2d300 c1
There are two entries:
The first is an entry for the container
c2that uses the Docker container ID as a host name
The second entry,
172.17.0.2 c1alaias 6e5cdeb2d300 c1, uses the
linkalias to reference the IP address of the
The following figure shows two containers Container 1 and Container 2 connected using veth pairs to the
docker0 bridge with
--icc=true. This means these two containers can access each other through the bridge:
Links provide service discovery for Docker. They allow containers to discover and securely communicate with each other by using the flag
-link name:alias. Inter-container communication can be disabled with the daemon flag
-icc=false. With this flag set to
false, Container 1 cannot access Container 2 unless explicitly allowed via a link. This is a huge advantage for securing your containers. When two containers are linked together, Docker creates a parent-child relationship between them, as shown in the following figure:
# start the database $ sudo docker run -dp 3306:3306 --name todomvcdb \ -v /data/mysql:/var/lib/mysql cpswan/todomvc.mysql # start the app server $ sudo docker run -dp 4567:4567 --name todomvcapp \ --link todomvcdb:db cpswan/todomvc.sinatra
On the inside, it looks like this:
$ dburl = ''mysql://root:[email protected]'' + \ ENV[''DB_PORT_3306_TCP_ADDR''] + ''/todomvc'' $ DataMapper.setup(:default, dburl)
Docker networking is at a very nascent stage, and there are many interesting contributions from the developer community, such as Pipework, Weave, Clocker, and Kubernetes. Each of them reflects a different aspect of Docker networking. We will learn about them in later chapters. Docker, Inc. has also established a new project where networking will be standardized. It is called libnetwork.
libnetwork implements the container network model (CNM), which formalizes the steps required to provide networking for containers while providing an abstraction that can be used to support multiple network drivers. The CNM is built on three main components—sandbox, endpoint, and network.
A sandbox contains the configuration of a container's network stack. This includes management of the container's interfaces, routing table, and DNS settings. An implementation of a sandbox could be a Linux network namespace, a FreeBSD jail, or other similar concept. A sandbox may contain many endpoints from multiple networks.
An endpoint connects a sandbox to a network. An implementation of an endpoint could be a veth pair, an Open vSwitch internal port, or something similar. An endpoint can belong to only one network but may only belong to one sandbox.
All containers on the same network can communicate freely with each other
Multiple networks are the way to segment traffic between containers and should be supported by all drivers
Multiple endpoints per container are the way to join a container to multiple networks
An endpoint is added to a network sandbox to provide it with network connectivity
We will discuss the details of how CNM is implemented in Chapter 6, Next Generation Networking Stack for Docker: libnetwork.
In this chapter, we learned about the essential components of Docker networking, which have evolved from coupling simple Docker abstractions and powerful network components such as Linux bridges and Open vSwitch.
We learned how Docker containers can be created with various modes. In the default mode, port mapping helps through the use of iptables NAT rules, allowing traffic arriving at the host to reach containers. Later in the chapter, we covered the basic linking of containers. We also talked about the next generation of Docker networking, which is called libnetwork.