gdraheim / docker-systemctl-replacement

docker systemctl replacement - allows to deploy to systemd-controlled containers without starting an actual systemd daemon (e.g. centos7, ubuntu16)
European Union Public License 1.2
1.4k stars 401 forks source link

Style Check Type Check Unit Tests Code Coverage PyPI version

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 seamlessly.

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. Effectively 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="https://github.com/gdraheim/docker-systemctl-replacement/raw/master/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 replacement 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 separate 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 definitely 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.