Couple of years ago, I developed web a service. Service contains embedded web server, and is using MongoDB as its data source. All back-end code is C++ based. Everything works perfect.
Some time later
Two years past. My service was used internally by my co-workers. But new functionality has to be added. So I look at the old code, trying to estimate work needed. In the mean time, new MongoDB driver shows up. Driver is using C++11 capabilities, and is much much better, comparing to the old one.
Machine, on which my service is running, is pretty old Debian distribution. I could update to new one, but unfortunately new Debian does not contain C++11 libraries for MongoDB.
My laptop works on newest Ubuntu distribution – same story.
There are more problems , „old” mongo driver is using Boost. Old version of Boost. I probably could recompile driver with newest Boost libraries. Probably.
To rewrite or not to rewrite
So. I had two choices:
- Install old Debian on virtual machine and add required functionalities using VirtualBox. Sinking deeper and deeper into technological dept.
- Rewrite service for newest MongoDB driver.
I decided to try second option. What I don’t like, is another ssh-based session of tweaking server machine. And reinstalls every major libraries update.
While searching through Google, I found mention of Docker. It could be solution for my problems. So I started research.
I love it.
Not a virtual machine
Docker is often described as light-weight virtual machine. I think description is not precise, and misleading. Docker (among other things) separates process (processes). Process is running in sandbox, any interactions between host, or another docker’s hosted processes has to be defined during first run command.
Image is a snapshot of file system. It contains complete collection of files to run concrete process inside. For example. Image for MongoDb database contains very thin, but complete Linux distribution, plus all MongoDb files required to run database server. Image is read-only template, you can’t run image. Docker transforms image into Container.
One of cool features of Docker its layered structure. Let say, I want to build image containing MongoDb database server. First I have to choose base Linux distribution. Ubuntu 14.04 for example.
My image will consists two layers:
- First – base Ubuntu layer
- Second – MongoDb server layer. This layer will contain only CHANGED files by MongoDb installation process. So layer will be quite small
Now. I can create another image. Lets say a PostgreSQL server. As base image I’ll again use Ubuntu 14.04. Base Ubuntu image will be reused by both MongoDb and PostgreSQL images. Pretty neat, right?
Image is a read only template. Container is writable instance of image. Image to Container is like class to object. Docker can run many containers based on single image.
Container can be transformed back into image. So created image can be base for another container. And so on.
Container has it’s own file system which is (surprise, surprise) a layer over image’s file system.
Container can be created from image using ‚docker run’ command.
Single process. Usually
Docker image contains command, which will be called after container start. Usually command starts single process. But nothing stands in the way to run script which starts multiple processes.
When command returns – docker will stop the container.
By default container files:
/etc/resolv.conf /etc/hosts /etc/hostname
are overlayed by relevant files of host system. Don’t be surprised when changes make in those files disappears.
Container can be transformed into image using ‚docker commit’ command. But usually images are created by Dockerfile.
Dockerfile is a recipe, contains rules and commands executed on base image leading to destination image.
Images are build using
command. Created image is placed inside local docker storage.
Commands are processed in the same order as they are placed in Dockerfile.
This line informs docker that images is based on latest release of Ubuntu. If image is not found locally, Docker will search and download from Docker Hub.
RUN apt-get -y update
Runs command on image.
Informs docker that image expose TCP port. Host can redirect port into another one. The way host threat exposed ports is defined by ‚docker run’ command
Copies local files into image.
COPY ./file_layer /
file_layer ├── usr │ ├── bin │ └── local └── var └── lib
Inside file_layer directory I can emulate root directory. Files are going to be copied inside image. Eventually replacing image’s original ones.
Command sets working directory for image initial command.
Entry command for Container.
Usually command starts Docker-hosted process, MongoDB for example. Before running command, docker sets working directory (WORKDDIR).
Back to story of my web service.
I’ve updated kernel on my server. Installed Docker.
Also installed Docker on my development laptop.
Now I can create and test images on my development machine. When ready, image will be moved to production machine.
Because Debian nor Ubuntu contains C++ drivers for MongoDb. I’m using COPY command to copy them from my development laptop into image.
Coping libraries into file structure in not enough. Libraries cache has to be rebuild using ldconfig command:
My services is using LDAP authentication. So proper packages has to be installed:
RUN apt-get -y libldap-2.4.2
So I have my image created. No I need to transport it into destination machine. There are couple ways to do it.
- Image can be saved as tar archive using docker export command. Tar archive can be imported into Docker using docker load command.
- Image can be pushed into Docker Hub.
- Image can be pushed into private repository
I like this trick:
docker save <image> | bzip2 | pv | ssh user@host 'bunzip2 | docker load'
Image can be transformed into container, using docker run command.
docker run <image name>
Docker run command is very important. Defines behavior of the container. Single image can be run many times, each time transformed into different container. For example, using Docker there can be 10 instances of MongoDB running concurrently on single host machine. But each instance requires different hosts resources. TCP port number, for example, has to be different.
This fine-tuning of image is done by run command. I suggest you to consult docker documentation.
My service was deployed and now it’s running. I have build automated system for build and deployment of images. Linux distribution on my server is meaningless. I can use any Linux distribution inside my images.