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 lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES277451c15ec1 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 esSo 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 lsNETWORK ID NAME DRIVER SCOPEc2c695315b3a bridge bridge locala875bec5d6fd host host localead0e804a67b none null localThe 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 bashroot@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# exitThis 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
eshostname stands for172.17.0.2or 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-net0815b2a3bb7a6608e850d05553cc0bda98187c4528d94621438f31d97a6fea3c
$ docker network lsNETWORK ID NAME DRIVER SCOPEc2c695315b3a bridge bridge local0815b2a3bb7a foodtrucks-net bridge locala875bec5d6fd host host localead0e804a67b none null localThe 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 eses
$ docker container rm eses
$ 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.213d6415f73c8d88bddb1f236f584b63dbaf2c3051f09863a3f1ba219edba3673
$ 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 bashroot@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# lsapp.py node_modules package.json requirements.txt static templates webpack.config.jsroot@53af252b771a:/opt/flask-app# python3 app.pyIndex 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# exitWohoo! 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-web852fc74de2954bb72471b858dce64d764181dca0cf7693fed201d76da33df794
$ docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES852fc74de295 prakhar1989/foodtrucks-web "python3 ./app.py" About a minute ago Up About a minute 0.0.0.0:5000->5000/tcp foodtrucks-web13d6415f73c8 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:5000HTTP/1.0 200 OKContent-Type: text/html; charset=utf-8Content-Length: 3697Server: Werkzeug/0.11.2 Python/2.7.6Date: Sun, 10 Jan 2016 23:58:53 GMTHead 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 containerdocker build -t prakhar1989/foodtrucks-web .
# create the networkdocker network create foodtrucks-net
# start the ES containerdocker 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 containerdocker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-webNow 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.shAnd 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!