docker systemctl replacement
This script may be used to overwrite "/usr/bin/systemctl".
It will execute the systemctl commands without SystemD!
This is used to test deployment of services with a docker container as the target host. Just as on a real machine you can use "systemctl start" and "systemctl enable" and other commands to bring up services for further configuration and testing. Information from "systemctl show" allows deployment automation tools to work seemlessly.
This script can also be run as docker-init of a docker container (i.e. the main "CMD" on PID 1) where it will automatically bring up all enabled services in the "multi-user.target" and where it will reap all zombies from background processes in the container. When running a "docker stop" on such a container it will also bring down all configured services correctly before exit.
## docker exec lamp-stack-container systemctl list-units --state=running
httpd.service loaded active running The Apache HTTP Server
mariadb.service loaded active running MariaDB database server
## docker exec lamp-stack-container pstree -ap
systemctl,1 /usr/bin/systemctl
|-httpd,7 -DFOREGROUND
| |-httpd,9 -DFOREGROUND
| |-httpd,10 -DFOREGROUND
`-mysqld_safe,44 /usr/bin/mysqld_safe --basedir=/usr
`-mysqld,187 --basedir=/usr --datadir=/var/lib/mysql
|-{mysqld},191
|-{mysqld},192
Problems with SystemD in Docker
The background for this script is the inability to run a SystemD daemon easily inside a docker container. There have been multiple workarounds with varying complexity and actual functionality. (The systemd-nsspawn tool is supposed to help with running systemd in a container but only rkt with CoreOs is using it so far).
Most people have come to take the easy path and to create a
startup shell script for the docker container that will
bring up the service processes one by one. Essentially one would
read the documentation or the SystemD *.service
scripts of the
application to see how that would be done. By using this
replacement script a programmer can skip that step.
Service Manager
The systemctl-replacement script does cover the functionality
of a service manager where commands like systemctl start xx
are executed. This is achieved by parsing the *.service
files that are installed by the standard application packages
(rpm, deb) in the container. These service unit descriptors
define the actual commands to start/stop a service in their
ExecStart/ExecStop settings.
When installing systemctl.py as /usr/bin/systemctl in a container then it provides enough functionality that deployment scripts for virtual machines continue to work unchanged when trying to start/stop, enable/disable or mask/unmask a service in a container.
This is also true for deployment tools like Ansible. As of version 2.0 and later Ansible is able to connect to docker containers directly without the help of a ssh-daemon in the container. Just make your inventory look like
[frontend]
my_frontend_1 ansible_connection=docker
Based on that ansible_connection
one can enable the
systemctl-replacement to intercept subsequent calls
to "service:"
steps. Effectivly Ansible scripts that
shall be run on real virtual machines can be tested
with docker containers. However in newer centos/ubuntu
images you need to check for python first.
- copy: src="files/docker/systemctl.py" dest="/usr/bin/systemctl"
- package: name="python"
- file: name="/run/systemd/system/" state="directory"
- service: name="dbus.service" state="stopped"
See SERVICE-MANAGER for more details.
Problems with PID 1 in Docker
The command listed as "CMD" in the docker image properties or being given as docker-run argument will become the PID-1 of a new container. Actually it takes the place that would traditionally be used by the /sbin/init process which has a special functionality in unix'ish operating systems.
The docker-stop command will send a SIGTERM to PID-1 in the container - but NOT to any other process. If the CMD is the actual application (exec java -jar whatever) then this works fine as it will also clean up its subprocesses. In many other cases it is not sufficient leaving zombie processes around.
Zombie processes may also occur when a master process does
not do a wait
for its children or the children were
explicitly "disown"ed to run as a daemon themselves. The
systemctl replacment script can help here as it implements
the "zombie reaper" functionality that the standard unix
init daemon would provide. Otherwise the zombie PIDs would
continue to live forever (as long as the container is
running) filling also the process table of the docker host
as the init daemon of the host does not reap them.
Init Daemon
Another function of the init daemon is to startup the default system services. What has been known as runlevel in SystemV is now "multi-user.target" or "graphical.target" in a SystemD environment.
Let's assume that a system has been configured with some "systemctl enable xx" services. When a virtual machine starts then these services are started as well. The systemctl-replacement script does provide this functionality for a docker container, thereby implementing "systemctl default" for usage in inside a container.
The "systemctl halt" command is also implemented allowing to stop all services that are found as "is-enabled" services that have been run upon container start. It does execute all the "systemctl stop xx" commands to bring down the enabled services correctly.
This is most useful when the systemctl replacement script has been run as the entrypoint of a container - so when a "docker stop" sends a SIGTERM to the container's PID-1 then all the services are shut down before exiting the container. This can be permanently achieved by registering the systemctl replacement script as the CMD attribute of an image, perhaps by a "docker commit" like this:
docker commit -c "CMD ['/usr/bin/systemctl']" \
-m "<comment>" <container> <new-image>
After all it allows to use a docker container to be more like a virtual machine with multiple services running at the same time in the same context.
See INIT-DAEMON for more details.
Testsuite and Examples
There is an extensive testsuite in the project that allows for a high line coverage of the tool. All the major functionality of the systemctl replacement script is being tested so that its usage in continuous development pipeline will no break on updates of the script. If the systemctl.py script has some important changes in the implementation details it will be marked with an update of the major version.
Please run the testsuite.py
or make check
upon providing
a patch. It takes a couple of minutes because it may download
a number of packages during provisioning - but with the help of the
docker-mirror-packages-repo
scripting this can be reduced a lot (it even runs without internet connection).
Some real world examples have been cut out into a seperate project. This includes dockerfile and ansible based tests to provide common applications like webservers, databases and even a Jenkins application. You may want to have a look at gdraheim/docker-systemctl-images list.
See TESTSUITE for more details.
Development
Although this script has been developed for quite a while, it does only implement a limited number of commands. It does not cover all commands of "systemctl" and it will not cover all the functionality of SystemD. The implementation tries to align with SystemD's systemctl commands as close as possible as quite some third party tools are interpreting the output of it. However the implemented software ARCHITECTURE is very different.
The systemctl replacement script has a long HISTORY now with over a thousand commits on github (mostly for the testsuite). It has also garnered some additional functionality like the USERMODE which is specifically targeted at running docker containers. See the RELEASENOTES for the latest achievements. The choice of the EUPL-LICENSE is intentionally permissive to allow you to copy the script to your project.
Sadly the functionality of SystemD's systemctl is badly
documented so that much of the current implementation is
done by trial and fixing the errors. Some BUGS
are actually in other tools and need to be circumvented. As
most programmers tend to write very simple *.service
files
it works in a surprising number of cases however. But definitly
not all. So if there is a problem, use the
github issue tracker
to make me aware of it. In general it is not needed to emulate
every feature as EXTRA-CONFIGS can help.
And I take patches. ;)
The author
Guido Draheim is working as a freelance consultant for multiple big companies in Germany. This script is related to the current surge of DevOps topics which often use docker as a lightweight replacement for cloud containers or even virtual machines. It makes it easier to test deployments in the standard build pipelines of development teams.