Managing docker services with this one easy trick
October 20, 2013
I have been having a lot of internal debate about the idea of running more than one service in a docker container. A Docker container is built to run a single process in the foreground and to live for only as long as that process is running. This is great in a utopian world where servers are immutable and sysadmins drink tiki drinks on the beach, however it doesn’t always translate well to the real world.
Examples where you might want to be able to run multiple servers span from the simple use case of running sshd
as well as your application to running a web app such as wordpress
where you might want both apache
and mysql
running in the same container.
Wrapping your applications in a supervisor daemon such as runit
seems like a perfect fit for this. All you need to do is install runit
as part of your dockerfile
and then create appropriate service directories for the apps you want to run in the container. I was doing some testing of this when I realized a quirk of runit
which I could exploit for evil.
To start or stop a service with runit
is simply a matter of creating or deleting a symlink in a service directory, so in theory if you could expose that directory to the server hosting the container you could exploit that to start and stop services from outside of the container. Docker
volume mapping allows exactly this!
Below you will find examples of running three services (logstash,elasticsearch,kibana) that make up the logstash
suite.
Start by cloning the demo git repository and run demo.sh
$ git clone https://github.com/paulczar/docker-runit-demo.git
$ cd docker-runit-demo
$ ./demo.sh
demo.sh script
Step 1: Build the container
The script uses the below Dockerfile
to build the base container that we’ll be running.
# Installs runit for service management
#
# Author: Paul Czarkowski
# Date: 10/20/2013
FROM paulczar/jre7
MAINTAINER Paul Czarkowski "paul@paulcz.net"
RUN apt-get update
RUN apt-get -y install curl wget git nginx
RUN apt-get -y install runit || echo
CMD ["/usr/sbin/runsvdir-start"]
Step 2: Install the applications
This will take a few minutes the first time as it needs to download logstash
, kibana
, and elasticsearch
and stage them in a local ./opt
directory.
Step 3: Start the Docker container
Starts the Docker
container with the following command:
docker run -d -p 8080:80 -p 5014:514 -p 9200:9200 \
-v $BASE/opt:/opt \
-v $BASE/sv:/etc/sv \
-v $BASE/init:/etc/init \
-v $BASE/service:/etc/service \
demo/runit
The container should be up and running
$ docker ps
ID IMAGE COMMAND CREATED STATUS PORTS
eb495ad92ba0 demo/runit:latest /usr/sbin/runsvdir-s 4 seconds ago Up 3 seconds 5014->514, 8080->80, 9200->9200
However there aren’t any services running!
$ curl localhost:8080
curl: (56) Recv failure: Connection reset by peer
$ curl localhost:9200
curl: (56) Recv failure: Connection reset by peer
We can start the services with the following commands
$ cd service
$ ln -s ../sv/elasticsearch
$ ln -s ../sv/logstash
$ ln -s ../sv/kibana
cd ..
We can now see the services are running, test the ports and send some data to logstash.
$ curl localhost:8080
<!DOCTYPE html><!--[if IE 8]><html class="no-js lt-ie9" lang="en"><![endif]--><!--[if gt IE 8]><!--><html class="no-js" lang="en">
...
curl localhost:9200
{
"ok" : true,
"status" : 200,
...
$tail -100 /var/log/syslog | nc localhost 5014
Stop a service ?
$ rm service/elasticsearch
$ rm service/logstash
$ rm service/kibana
Bonus Round: Logs!
The beautify of doing this is that we’re actually logging the application output to a mounted volume. This means we now have access to their logs from the host machine.
$ tail opt/logstash/logs/current
$ tail opt/elasticsearch-0.90.5/logs/current
$ tail opt/kibana/logs/access.log
Cleanup
Unfortunately any files created inside the docker instance are owned by root ( an artifact of docker daemon running as root ). If you’re in The following script will clean out any such files after you’ve stopped the docker container.
It will delete any files/dirs inside your current directory that are owned by root. Obviously it can be very dangerous to run … so be careful where you run it from!
$ sudo find . -uid 0 -exec rm -rfv {} \;