Dockerising apt-cacher-ng
Docker's own example how to dockerise apt-cacher-ng is spectacularly simplified to avoid demonstrating Docker's pitfalls and challenges.
As per our evaluation of Docker one of the first services we dockerised was apt-cacher-ng, partially because official example seemed so straightforward. In this article we describe some deployment challenges that are not covered by Docker's apt-cacher-ng dockerising instructions.
First challenge is to figure out data storage model.
Docker containers are volatile therefore persistent data should be stored either in special data container or on the host machine. We have chosen the latter because we could use shared network folder for data and therefore move container between servers. Storing data on host requires to deal with synchronisation between host's and container's user id for ownership of data.
Docker have no native support for shared volume's uid configuration.
It is not hard to choose user (g)id on host machine and update apt-cacher-ng
user's (g)id in container but it is an effort without clear instructions
regarding the best way to do so. We ended up running container with parameters
-v ${HOSTDATA}/cache:/var/cache/apt-cacher-ng -v ${HOSTDATA}/log:/var/log/apt-cacher-ng
to place apt-cacher-ng data and logs to location on host.
Second challenge is to make sure that container runs cron.
Apt-cacher-ng needs periodic maintenance of its data and Debian package installs corresponding daily cron job.
Probably we could create a separate container specifically for cron job but that would be crazy because of added complexity.
Running cron inside container can be useful for services that use logrotate to manage their logs.
So we need container that runs at least two daemons: apt-cacher-ng and cron.
Unfortunately Docker is well suited to run only one process so we need
workaround to run dockerised container/mini-VM with multiple daemons.
Basically we need Docker friendly init system that can propagate signals
and terminate all children processes when container is stopping.
There is a project
to do exactly that but unfortunately it is based on Ubuntu and uses 3rd party
software therefore we have decided to try our own solution using only software
from Debian repositories.
Neither Systemd nor SysV init systems work in Docker so we tried runit which
worked fairly well except that we had to re-write daemons' init scripts from
scratch -- at this point we appreciated package maintainers work which makes
it so easy to deploy services by installing OS packages.
Instead of runit we could use supervisor which uses slightly more memory.
Third challenge is to configure container to use static IP.
There are number of reasons for that. Primarily we wanted to minimise changes to our infrastructure.
Since we have dedicated IP for apt-cacher-ng it does not matter where we start VM which claims this IP.
We need no DNS updates or load balancers.
But the real problem with Dockerised apt-cacher-ng is that it needs to know
its publicly accessible IP address. Not only it is advertised on web page but
also apt-cacher-ng announces its services through Avahi for autodiscovery.
If package "squid-deb-proxy-client" is installed Apt will automatically find
and use apt-cacher-ng services in local network provided that "avahi-daemon"
runs on the same host with apt-cacher-ng. Avahi-daemon depends on dbus
and they are all should be running on the machine with public IP.
Docker do not support assignment of static IPs.
When container is started Docker chooses random IP for container and overriding
that requires a bit of hacking. First thing we tried is to map IP using docker run
argument as follows: --net=bridge -p 192.168.0.249:3142:3142
.
It almost worked but not quite as daemons have to know their public IPs to work properly.
After a while we figured out that we can configure docker daemon to start with
DOCKER_OPTS="--bridge=br0 --ip-masq=false --iptables=false"
to minimise
Docker's interference with network configuration (yeah, we had to set-up
network bridge on host). Docker still assigns random IP to container on start
but we could override this by the following configuration of /etc/network/interfaces
inside container:
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
pre-up ip addr flush dev eth0
address 192.168.0.249
netmask 255.255.255.0
gateway 192.168.0.1
pre-up ip addr flush dev eth0
is necessary to dismiss Docker-assigned IP.
To make it work container entry script should begin with /etc/init.d/networking start
and container should be started with --cap-add=NET_ADMIN --net=bridge
.
Finally from container entry script we had to re-create Docker-generated /etc/hosts
file to remove references to Docker-assigned IP.
This configuration avoids collisions with existing LAN IPs (see docker bug 11199)
and uses static IP assigned on container's build time.
Such network configuration can be trivially modified to use DHCP instead of static IP.
Resulting container runs four daemons (apt-cacher-ng, cron, dbus and avahi-daemon) and claims its own IP. All daemons except cron are running without root privileges.
Probably it would be easier to set up networking if we were using rkt container runtime but the latter is not available in Debian yet.
If you need rkt in Debian then please consider sponsoring our work.
Conclusion
Docker introduces multiple problems that did not exist before Docker.
Docker adds significant complexity to application deployment. Where deploying applications with packages takes minutes, dokerisation takes hours or even days of effort and experimentation.
Docker is young and there is no established industry best practice how to manage dockerised applications.
runit "run" scripts
cron:
exec cron -f -l
apt-cacher-ng:
mkdir /var/run/apt-cacher-ng/
chown -c apt-cacher-ng /var/run/apt-cacher-ng/
exec chpst -u apt-cacher-ng /usr/sbin/apt-cacher-ng SocketPath=/var/run/apt-cacher-ng/socket -c /etc/apt-cacher-ng ForeGround=1
avahi-daemon:
sv start dbus || exit 1
exec /usr/sbin/avahi-daemon --no-rlimits
We had to add "--no-rlimits" because for unknown reason avahi-daemon fails to start on some (seemingly identically configured) machines as follows:
chroot.c: fork() failed: Resource temporarily unavailable
failed to start chroot() helper daemon.
This is not a rlimit-nproc < 3
problem.
dbus:
[ -d "/var/run/dbus" ] || mkdir /var/run/dbus >>/dev/null
rm -fv /var/run/dbus/* >>/dev/null
chown -R messagebus:messagebus /var/run/dbus
dbus-uuidgen --ensure
exec dbus-daemon --system --nofork
See also
Other apt-cacher-ng dockerising instructions: