Writing `systemd` Services

DONG Yuxuan @ Jan 24, 2020 Asia/Shanghai

Introduce how to write an autostarted service for Unix with systemd.

Let’s say we have a simple Python script sysdt.py.

#!/usr/bin/env python3
# Print the current time to /tmp/sysdt.log every 10 seconds

from datetime import datetime
from time import sleep

while True:
	with open('/tmp/sysdt.log', 'a') as f:
		print(datetime.now(), file=f)
	sleep(10)

We want to make it a service, meaning run it as a autostarted daemon.

System services are managed by systemd in modern Linux. Every system service has a corresponding unit file servname.service under /etc/systemd/system. Let’s write one for sysdt.py and name it /etc/systemd/systemd/sysdt.service.

[Unit]
Description=sysdt

[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/python3 /home/dongyx/sysdt.py

[Install]
WantedBy=multi-user.target

The service unit file consists of three sections, Unit, Service, and Install. Unit contains some descriptive infomation; Service contains the core infomation about which program to run, how to run, etc.. Install contains the infomation about when to lunch the service.

In our example, the Service section contains three fields. ExecStart is the path and arguments to run. When we run systemctl start sysdt in shell, the content of ExecStart will be executed.

We can also specify the ExecStop to set what should happen if we run systemctl stop sysdt. We don’t specify here thus it uses the default behaviour which sends SIGTERM to the process and all subprocesses of the service.

The Type field specifies how systemd check if the service is running. The most common options are simple, oneshot, and forking.

If we set Type to simple, systemd will think that the service is running if the first process of the service is running. This is suitable for most programs. If you run a long-time running program in a terminal and the terminal blocks util the program is finished. The simple option is what you need.

The oneshot is for short-term running programs. The systemd considers the service is still up after the process exits. For example, you have a very simple program that appends the current time to a file then quits. You want to make it autostarted to record boot times of the system. oneshot is the right option.

The forking option is for legacy daemons that call fork to create subprocesses then terminiate the parent process. For example, if you run httpd -k start, Apache HTTPd will fork subprocesses to run in background and terminate the parent process, thus the terminal dosen’t block. You could check apache2.service and it does use forking.

The Restart option sets whether the service should be restarted when the service process exits, is killed, or a timeout is reached. We set it to always here. As its name implies, systemd will always restart the program if it’s killed.

We have configured how to run the program now. The last thing we need is making it autostarted. We do this in the WantedBy field of the Install section. To explain it, we should understand the concept of targets.

A service is a systemd unit, so does a target. However, unlike a service, a unit represents a group of other units. You can put several srevices into a target and start all the services by starting the target. The WantedBy field sets which targets the service belongs to. Here we set it to multi-user.target which is one of the preset targets of systemd. While the system boots, it has a boot target and systemd will start it. For example, if you boot the system into a graphical environment, the system is booted with graphical.target. The graphical.target requires multi-user.target so multi-user.target will also be executed. If you boot the system into a text environment, the boot target is multi-user.target thus multi-user.taget will be executed but graphical.target will not because multi-user.target dosen’t require it. Besides graphical.target and multi-user.target, there’re other preset targets in Linux. For example, rescue.target is for the system administrator to fix booting issues thus it should lunch as less programs as it can.

That’s why we set WantedBy to multi-user.target. It means starting the service when the system is normally booted (with graphical environment or not, but is not booted into a rescue mode).