In last week's post, I talked about how we build images and the concept of layers that make up an image. With that, I have covered the lifecycle of a container (albeit in reverse, but it seemed logical), from creating the specification for the image, building it, and then running a container based on that image.
Based on just this, you could go and start creating container applications and get a lot of value out of it. You wouldn't even necessarily have to create your own images, as there is an active community of developers building useful images that you can use directly from the Docker Hub. You can find almost anything you need out there, such as databases like MySQL and Mongo, application servers like JBoss and Tomcat, and DevOps tools like Jenkins and Artifactory.
The power of containers doesn't stop there, however. Just as we can combine virtual machines or baremetal servers to build application clusters, you can combine containers to build them as well. You could easily do all of this manually (starting each part of the cluster as a container and configuring it to be aware of the other components), but thanks to some tools that have been developed, you don't really have to do a lot to make it happen. The process of managing and configuring multiple containers is what we call orchestration.
Think of a symphony orchestra (or your army of robots that you are assembling to fight the alien invasion, although in this case I'm choosing the former for a reason). You can take any instrument in the orchestra and make some music with it. The compositions that you would be able to play, however, would be limited. There are different instruments that play different parts, and often you need multiple instances of particular instruments in order to play all the notes. A composer writes a score that captures all the different instruments that are needed and also captures all the notes that they are expected to play.
To demonstrate what orchestration means for containers, I'll use a tool called Docker Compose (you probably see now why I chose a symphony orchestra above). Docker Compose is intended to make it simple to start and connect multiple containers together. You can think of it as a way of specifying all the command line arguments to Docker to start the containers you need. These specifications are written in YAML files. A simple compose file can look like this:
version: 2 services: mysql_svc: image: mysql ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=secret - MYSQL_PASSWORD=changeme - MYSQL_USER=demo - MYSQL_DATABASE=demodb nginx_svc: image: nginx ports: - "8080:80"
Using this file (you can name compose files what you want, but if you name them docker-compose.yml or docker-compose.yaml, the docker-compose command will automatically recognize them), you can start your containers with a simple command:
$ docker-compose up Starting ofmm_nginx_svc_1 ... done Starting ofmm_mysql_svc_1 ... done Attaching to ofmm_nginx_svc_1, ofmm_mysql_svc_1 mysql_svc_1 | [Entrypoint] MySQL Docker Image 8.0.11-1.1.5 mysql_svc_1 | [Entrypoint] Starting MySQL 8.0.11-1.1.5 mysql_svc_1 | 2018-05-18T19:54:50.297972Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.11) starting as process 1 ... mysql_svc_1 | 2018-05-18T19:54:51.345469Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.11' socket: '/var/lib/mysql/mysql.sock' port: 3306 MySQL Community Server - GPL.
Now if you open up another terminal and run curl (or point your favorite browser at localhost:8080), you will see a response from the nginx container:
$ curl localhost:8080
<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ... <p><em>Thank you for using nginx.</em></p> </body> </html>
Similarly, we can shell in to the mysql container and run mysql commands:
$ docker exec -it ofmm_mysql_svc_1 bash $ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 ... Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
By default, the names Docker chooses for the containers created by a compose file are based on the directory the compose file was in (ofmm in this example, short for openshift for mere mortals), the service within the compose file (mysql_svc, nginx_svc) and which instance (this are only one of each, so 1).
Now, the way that I have written my compose file, these two containers are actually independent. They don't rely on one another, and by default, they can't see each other. However, a lot of apps have dependencies between components, such as a web server pulling data from a database. We could put the components together in one container, but it makes your container overly complicated and it wont scale well (adding an additional container would add both a web server and a database, and that likely would be hard to manage).
If you try to run curl from inside the mysql container to contact the nginx container, it will fail (I chose this direction because the mysql image includes curl):
$ curl localhost:8080 curl: (7) Failed to connect to ::1: Cannot assign requested address
The same would be true if you tried to reach mysql from the nginx container. Compose, however, allows us to simply connect one container to another so that they can easily talk. I can add a depends_on key to my compose file, and this will make it so that I can reach the nginx container from my mysql container:
version: "2" services: mysql_svc: image: mysql/mysql-server ... depends_on: - nginx_svc nginx_svc: ...
Now when I start up the containers, you will notice that a Docker Network is created that connects the containers:
$ docker-compose up Creating network "ofmm_default" with the default driver Creating ofmm_nginx_svc_1 ... done Creating ofmm_mysql_svc_1 ... done Attaching to ofmm_nginx_svc_1, ofmm_mysql_svc_1 mysql_svc_1 | [Entrypoint] MySQL Docker Image 8.0.11-1 ...
And when I try to curl the nginx container from the mysql container, it will work this time. Notice, however, that you refer to the container by it's service name, not by the container name (ofmm_nginx_svc_1 in this case), and not by localhost (localhost in the container refers to the mysql container itself):
$ docker exec -it ofmm_mysql_svc_1 bash $ curl nginx_svc
<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
So what's going on here? The reason is pretty simple. I had mentioned briefly that the name of the container includes an instance number. That's because just like in a sympony orchestra, we might need more than one instrument of a given type. The same goes for components of an application cluster. In applications, we often have more than one server or process to handle the load, and we load-balance across those instances. Compose is automatically doing that load balancing for us. If we had 3 instances of nginx running instead of 1, we could still make the same http request to nginx_svc and it would work the same way.
This week I talked about the concept of orchestration for containers. Next week I'll be expanding on that topic by looking at Kubernetes, which allows you to build clusters of servers to manage large volumes of containers using a simple command line interface.