Docker Network
Before we talk about the features Docker provides especially to deal with such scenarios, let’s see if we can figure out a way to get around the problem. Hopefully, this should give you an appreciation for the specific feature that we are going to study.
Okay, so let’s run docker container ls
(which is same as docker ps
) and see what we have.
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
277451c15ec1 docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/usr/local/bin/dock…" 17 minutes ago Up 17 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp es
So we have one ES container running on 0.0.0.0:9200
port which we can directly access. If we can tell our Flask app to connect to this URL, it should be able to connect and talk to ES, right? Let’s dig into our Python code and see how the connection details are defined.
es = Elasticsearch(host='es')
To make this work, we need to tell the Flask container that the ES container is running on 0.0.0.0
host (the port by default is 9200
) and that should make it work, right? Unfortunately, that is not correct since the IP 0.0.0.0
is the IP to access ES container from the host machine i.e. from my Mac. Another container will not be able to access this on the same IP address. Okay if not that IP, then which IP address should the ES container be accessible by? I’m glad you asked this question.
Now is a good time to start our exploration of networking in Docker. When docker is installed, it creates three networks automatically.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c2c695315b3a bridge bridge local
a875bec5d6fd host host local
ead0e804a67b none null local
The bridge network is the network in which containers are run by default. So that means that when I ran the ES container, it was running in this bridge network. To validate this, let’s inspect the network.
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "c2c695315b3aaf8fc30530bb3c6b8f6692cedd5cc7579663f0550dfdd21c9a26",
"Created": "2018-07-28T20:32:39.405687265Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"277451c15ec183dd939e80298ea4bcf55050328a39b04124b387d668e3ed3943": {
"Name": "es",
"EndpointID": "5c417a2fc6b13d8ec97b76bbd54aaf3ee2d48f328c3f7279ee335174fbb4d6bb",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
You can see that our container 277451c15ec1
is listed under the Containers
section in the output. What we also see is the IP address this container has been allotted - 172.17.0.2
. Is this the IP address that we’re looking for? Let’s find out by running our flask container and trying to access this IP.
$ docker run -it --rm prakhar1989/foodtrucks-web bash
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
{
"name" : "Jane Foster",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.1.1",
"build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
"build_timestamp" : "2015-12-15T13:05:55Z",
"build_snapshot" : false,
"lucene_version" : "5.3.1"
},
"tagline" : "You Know, for Search"
}
root@35180ccc206a:/opt/flask-app# exit
This should be fairly straightforward to you by now. We start the container in the interactive mode with the bash
process. The --rm
is a convenient flag for running one off commands since the container gets cleaned up when its work is done. We try a curl
but we need to install it first. Once we do that, we see that we can indeed talk to ES on 172.17.0.2:9200
. Awesome!
Although we have figured out a way to make the containers talk to each other, there are still two problems with this approach -
-
How do we tell the Flask container that
es
hostname stands for172.17.0.2
or some other IP since the IP can change? -
Since the bridge network is shared by every container by default, this method is not secure. How do we isolate our network?
The good news that Docker has a great answer to our questions. It allows us to define our own networks while keeping them isolated using the docker network
command.
Let’s first go ahead and create our own network.
$ docker network create foodtrucks-net
0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c2c695315b3a bridge bridge local
0815b2a3bb7a foodtrucks-net bridge local
a875bec5d6fd host host local
ead0e804a67b none null local
The network create
command creates a new bridge network, which is what we need at the moment. In terms of Docker, a bridge network uses a software bridge which allows containers connected to the same bridge network to communicate, while providing isolation from containers which are not connected to that bridge network. The Docker bridge driver automatically installs rules in the host machine so that containers on different bridge networks cannot communicate directly with each other. There are other kinds of networks that you can create, and you are encouraged to read about them in the official docs.
Now that we have a network, we can launch our containers inside this network using the --net
flag. Let’s do that - but first, in order to launch a new container with the same name, we will stop and remove our ES container that is running in the bridge (default) network.
$ docker container stop es
es
$ docker container rm es
es
$ docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673
$ docker network inspect foodtrucks-net
[
{
"Name": "foodtrucks-net",
"Id": "0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c",
"Created": "2018-07-30T00:01:29.1500984Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"13d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673": {
"Name": "es",
"EndpointID": "29ba2d33f9713e57eb6b38db41d656e4ee2c53e4a2f7cf636bdca0ec59cd3aa7",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
As you can see, our es
container is now running inside the foodtrucks-net
bridge network. Now let’s inspect what happens when we launch in our foodtrucks-net
network.
$ docker run -it --rm --net foodtrucks-net prakhar1989/foodtrucks-web bash
root@9d2722cf282c:/opt/flask-app# curl es:9200
{
"name" : "wWALl9M",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "BA36XuOiRPaghPNBLBHleQ",
"version" : {
"number" : "6.3.2",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "053779d",
"build_date" : "2018-07-20T05:20:23.451332Z",
"build_snapshot" : false,
"lucene_version" : "7.3.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py node_modules package.json requirements.txt static templates webpack.config.js
root@53af252b771a:/opt/flask-app# python3 app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded: 733
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
root@53af252b771a:/opt/flask-app# exit
Wohoo! That works! On user-defined networks like foodtrucks-net, containers can not only communicate by IP address, but can also resolve a container name to an IP address. This capability is called automatic service discovery. Great! Let’s launch our Flask container for real now -
$ docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web
852fc74de2954bb72471b858dce64d764181dca0cf7693fed201d76da33df794
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
852fc74de295 prakhar1989/foodtrucks-web "python3 ./app.py" About a minute ago Up About a minute 0.0.0.0:5000->5000/tcp foodtrucks-web
13d6415f73c8 docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/usr/local/bin/dock…" 17 minutes ago Up 17 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp es
$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT
Head over to http://0.0.0.0:5000 and see your glorious app live! Although that might have seemed like a lot of work, we actually just typed 4 commands to go from zero to running. I’ve collated the commands in a bash script.
#!/bin/bash
# build the flask container
docker build -t prakhar1989/foodtrucks-web .
# create the network
docker network create foodtrucks-net
# start the ES container
docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
# start the flask app container
docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web
Now imagine you are distributing your app to a friend, or running on a server that has docker installed. You can get a whole app running with just one command!
$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ ./setup-docker.sh
And that’s it! If you ask me, I find this to be an extremely awesome, and a powerful way of sharing and running your applications!