Wednesday, August 6, 2014

Docker talking to docker

The problem



If you are mingling with docker and containers on servers, you'll at some point want to have two containers communicate with each other.

For example if you have an Apache server, Django+Python and a MySQL server and you want to do tests on different configurations/versions of these, you should probably go the container-way: putting each of them on their own container. However since all these services are interchanged in some way (database has to communicate with web server, apache server has to communicate with django), the containers have to talk with each other somehow.


Possible solutions



There are essentially four ways to accomplish container-to-container communication:


  1. Having containers inside containers

  2. Using volumes (shared folders)

  3. Mapping ports (communication over TCP/IP)
  4. Linking containers (communication over.. TCP/IP?)




I will completely omit the first way since it's a brainfuck both conceptually and technically (yes, I am aware of Dind). Been there, done that, and it wasn't pretty.

The second solution is about sharing folders between host and container or between containers by using docker's --volume flag. This is practical when you want to share files (source code, configuration files etc.) on a local machine. If you want containers on different hosts to communicate however this won't suffice.

The two remaining solutions is the meat I will try to break down for you (and minimize your flatulence from the learning curve). I did my research and I will do my best to convey what I came down to. But enough talk, let's start!


Terminal to terminal speaking


I will use netcat to show how all these things work. To remind you, netcat is a Unix tool similar to cat but supposed to work on the network (thus the name net-cat). Practically it's used when you have to copy/paste from host to an other host, chat with someone fast, or just test communication between two hosts like we do here.

You can test it on your home computer. Open two terminals and type in one of them
[code]netcat -l 8000[/code]

and on the other
[code]netcat 127.0.0.1 8000[/code]

Whatever you type on one terminal now, will appear on the other one.



Terminal to container speaking



Let's take this a step further, by containerizing the listening netcat. In a new terminal type:

[code]docker run -i -n -p 9000:2000 ubuntu netcat -l 2000[/code]

that creates a new container with an internal port 2000. The port 9000 is the port on our system. Whatever arrives to that will automatically be forwarded to our container's port 2000. This is somehow similar to port forwarding on home routers. (I explain more on how this actually works later.)

The flag -n is a deprecated flag for networking. In practice without the -n flag, everything will work as before but for some weird reason there will be an initial lag for the first netcat message. So if you don't use it, just wait a bit longer.

text3782

We should now have a container running a "listening" netcat. Let's open a terminal (on the same host) to try and talk with it:
[code]netcat 127.0.0.1 9000[/code]

Or from a different host:
[code]netcat <computer IP> 9000[/code]


Keep in mind that different containers on the same machine can have the exact same internal IP but not the same system one. So you can create a bunch of containers as long as you change the system's IP:

[code]
docker run -d -p 9000:2000 ubuntu netcat -l 2000
docker run -d -p 9001:2000 ubuntu netcat -l 2000
docker run -d -p 9002:2000 ubuntu netcat -l 2000
[/code]

It's all legit baby!


Container to container speaking



We take this a step further by having the listening container as before but instead of opening a terminal to connect to it, we will create a new container on a different host.

text3783

So create a new container on the first host (same command as before)

[code]docker run -i -p 9000:2000 ubuntu netcat -l 2000[/code]

Then we create a new container on the second machine (or same):

[code]docker run -i -t ubuntu bash
root@879ad5d4251a:/#[/code]

On the new prompt that we get we can netcat directly to the first host:
[code]root@879ad5d4251a:/# netcat 10.2.202.156 9000[/code]

This should work as a charm as long as you use your LAN IP.

There are some weird networking behaviours. For example if you netcat by using the IP of the container (eth0 inside container) there is an initial lag but after some seconds the message arrives. The same behaviour occurs if you omit the -n flag but use the LAN IP (which otherwise works). If I start a bash session in the container and listen to 9000 with netcat there, then it works flawlessly. Weird stuff indeed.


A whole network under the city



I omitted quite some information in an effort to jumpstart you. This information is however crucial if you want to use port forwarding and container communication on different hosts. There's a concept that will save yourself some headaches:

There is a whole virtualized network when we use Docker.

Let me prove that to you. I create two containers and then I check the IPs of them and the IPs of the host. I do that by running ifconfig on each terminal.

docker_IPs_internal

Putting it all down sums up to the below picture.

docker_virtual_network

You see, Docker creates a virtual IP for each container we create. Furthermore on our host we have a virtual interface called docker0. That can be considered the router. Now on the same host we are able to communicate with any container if we know their IP.

Now, if a container is listening on a specific port we are able to communicate with it by knocking on its door with a pair of IP address and port number.

Let's use this new knowledge on the previous example with netcat. Let's assume that container 1 is listening on internal port 2000 and system port 9000 (like before):
[code]docker run -i -p 9000:2000 ubuntu netcat -l 2000[/code]

We can connect to it in all these ways:

[code]
netcat 172.17.0.2 2000
netcat 127.0.0.1 9000
netcat localhost 9000
netcat 10.2.202.156 9000
[/code]

The significant information here is that we can directly speak to the container if we know its IP. Pay attention also to that we need to use the correct port in that case.


Container to container speaking via links



So what are those so called links? Nothing special actually. It's just some variables passed from one container to an other. In most cases these variables will hold things like IPs, ports, etc. Thus it's just a step of simplifying things for us - in the end we will probably just use these variables to connect as before.

Let's see how it works. Start by creating a first container that will act as a server:

[code]docker run -i -t -p 8000:8000 --name myserver ubuntu bash[/code]

and then a second container that will act as a client:

[code]docker run -i -t --name myclient --link myserver:myserver ubuntu bash[/code]

Nothing special huh? Well the magic happens if you inside the client (second container) run:

[code]root@3b00d48e549a:/# ping $MYSERVER_PORT_8000_TCP_ADDR[/code]

You will successfully ping the other container. $MYSERVER_PORT_8000_TCP_ADDR is simply a variable set by docker on the newly created container, the one with the --link flag. You can see all variables with the command env. Depending on what flags you passed to the container, you will see appropriate things. In practice, if you don't use the -p flag, you won't see anything interesting (useful).

The --name flag is needed when we use linking as we need an identifier for the container. When we do the linking we need pass the name of the other container, the one that we want to link to and an alias for it. The alias is used merely as a prefix for the variable names. For example if we used --link myserver:dingdong instead of --link myserver:myserver then the variable above would be $DINGDONG_PORT_8000_TCP_ADDR.

That's pretty much all to linking! By using these variables you can get the IPs and ports of other containers on the same host. That's an important annotation. You can only link to a container running on the same hosts. For communicating to containers on distant hosts you'll need to use port mapping as explained earlier.

Useful commands


Show which ports are opened by docker
[code]sudo netstat -tulpn | grep docker[/code]

No comments:

Post a Comment