In this blog post, I'll talk about a way to use Salt to automate the build and configuration of Docker containers. I will not consider the deployment of Docker containers with Salt as this subject is already covered elsewhere (here for instance). The emphasis here is really on building (or configuring) a container for future deployment.
Motivation
Salt is a remote execution framework that can be used for configuration management. It's already widely used at Logilab to manage our infrastructure as well as on a semi-daily basis during our application development activities.
Docker is a tool that helps automating the deployment of applications within Linux containers. It essentially provides a convenient abstraction and a set of utilities for system level virtualization on Linux. Amongst other things, Docker provides container build helpers around the concept of dockerfile.
So, the first question is why would you use Salt to build Docker containers when you already have this dockerfile building tool. My first motivation is to encompass the limitations of the available declarations one could insert in a Dockerfile. First limitation: you can only execute instructions in a sequential manner using a Dockerfile, there's is no possibility of declaring dependencies between instructions or even of making an instruction conditional (apart from using the underlying shell conditional machinery of course). Then, you have only limited possibilities of specializing a Dockerfile. Finally, it's no so easy to apply a configuration step-by-step, for instance during the development of said configuration.
That's enough for an introduction to lay down the underlying motivation of this post. Let's move on to more practical things!
A Dockerfile for the base image
Before jumping into the usage of Salt for the configuration of a Docker image, the first thing you need to do is to build a Docker container into a proper Salt minion.
Assuming we're building on top of some a base image of Debian flavour
subsequently referred to as <debian>
(I won't tell you where it comes from,
since you ought to build your own base image -- or find some friend you trust
to provide you with one!), the following Dockerfile
can be used to
initialize a working image which will serve as the starting point for
further configuration with Salt:
FROM <debian>
RUN apt-get update
RUN apt-get install -y salt-minion
Then, run docker build . docker_salt/debian_salt_minion
and you're done.
Plugin the minion container with the Salt master
The next thing to do with our fresh Debian+salt-minion image is to turn it
into a container running salt-minion
, waiting for the Salt master to
instruct it.
docker run --add-host=salt:10.1.1.1 --hostname docker_minion \
--name minion_container \
docker_salt/debian/salt_minion salt-minion
Here:
--hostname
is used to specify the network name of the container, for easier query by the Salt master,10.1.1.1
is usually the IP address of the host, which in our example will serve as the Salt master,--name
is just used for easier book-keeping.
Finally,
salt-key -a docker_minion
will register the new minion's key into the master's keyring.
If all went well, the following command should succeed:
salt 'docker_minion' test.ping
Configuring the container with a Salt formula
salt 'docker_minion' state.sls some_formula
salt 'docker_minion' state.highstate
Final steps: save the configured image and build a runnable image
(Optional step, cleanup salt-minion
installation.)
Make a snapshot image of your configured container.
docker stop minion_container
docker commit -m 'Install something with Salt' \
minion_container me/something
Try out your new image:
docker run -p 8080:80 me/something <entry point>
where <entry point>
will be the main program driving the service provided by
the container (typically defined through the Salt formula).
Make a fully configured image for you service:
FROM me/something
[...anything else you need, such as EXPOSE, etc...]
CMD <entry point>