The Importance of SystemD in Linux

I just wanted to preface this by saying it makes me sad that the divisions that SystemD has caused run so deep. I frequently see accusations that SystemD is malware that powers a botnet and that it has destroyed Linux simply by not conforming to a software design suggestion; the Unix philosophy. As with any software solution, there are major faults and major benefits, but in my opinion, the benefits of SystemD in a modern OS world significantly outweigh the faults.

SystemD fills all the requirements of a modern init system and it is currently the init system of choice for all of the major Linux distros. Prior to SystemD, solutions such as Sys V init, Upstart, and OpenRC existed to start services at boot or even manage restarting services if they crashed. At the time SystemD was adopted, none of the init solutions used a unified format for service configs, none of them made service-specific logging accessible by comparison to SystemD, and none of them made dependency based service starting and management as accessible and easy as SystemD. For these reasons, and more, SystemD is the go-to init system for all major Linux distros.

What SystemD does

"systemd is a suite of software that provides fundamental building blocks for a Linux operating system" - Wikipedia

SystemD provides the tools necessary to boot a Linux operating system, start and run services, manage service dependencies (services that have to start after other services), run services at scheduled times, manage service logging, and manage services for users using SystemD Slices (cgroups). SystemD does a lot more than that believe me, but I am only so familiar with the massive tool set that is SystemD. SystemD also provides a lot of nice features such as drop-in service reloading that allows system administrators to write systemd unit files, test them, and enable them all without rebooting, or a major headache!

SystemD Directories

Unfortunately, SystemD's directory structure can be configured differently across Linux distributions. The directory specification I give is the one that I believe is the most used, so therefore should apply to the most cases. A useful command to see where you should store SystemD system unit files is pkg-config systemd --variable=systemdsystemunitdir. The directories that are important for writing and creating services and timers are as follows:

  • /etc/systemd/system Storing services in this directory override the services in /lib/systemd/system. If you wanted to modify the way a service runs without editing the original service, this directory is where you would write the new service. As such this is where you will want to store your services.
  • /lib/systemd/system This is the default installation directory for system services. You can store your service files in this directory or you can store them in /etc/systemd/system.
A neat trick is to replace system in the directories above with user. Services, etc. added to those directories can be started and enabled by users via systemctl --user commands. See the man page for more info :D

Anatomy of a SystemD Unit File: An Example

The best way to explain what goes into a SystemD unit file is to just show an example. SystemD units can get very complex, and explaining every facet is outside of the scope of this post.

Several system's I have set up haven't had a factory built in way to flash IPtables rules into memory. The way I solve this problem is to write 4 utility bash scripts and a unit file. These files; flash the rules into memory, flush the rules and re-flash, flush the rules, save the rules, and a SystemD unit file to pull all of those scripts into a logical service. Because I'm nice; here are those files 😉.

/sbin/flash-iptables

#!/usr/bin/env bash

function checkRet {
	if [[ $1 -ne 0 ]]; then
		exit 1
	fi
}

/sbin/iptables-restore </etc/iptables/iptables.conf
checkRet $?
/sbin/ip6tables-restore </etc/iptables/ip6tables.conf
checkRet $?

exit 0

/sbin/flush-iptables

#!/usr/bin/env bash

function checkRet {
	if [[ $1 -ne 0 ]]; then
		exit 1
	fi
}

/sbin/iptables -F
checkRet $?
/sbin/ip6tables -F
checkRet $?

exit 0

/sbin/restore-iptables

#!/usr/bin/env bash

function checkRet {
	if [[ $1 -ne 0 ]]; then
		exit 1
	fi
}

/sbin/flush-iptables
checkRet $?
/sbin/flash-iptables
checkRet $?

exit 0

/sbin/save-iptables

# File: /sbin/save-iptables
#!/usr/bin/env bash

function checkRet {
	if [[ $1 -ne 0 ]]; then
		exit 1
	fi
}

/sbin/iptables-save >/etc/iptables/iptables.conf
checkRet $?
/sbin/ip6tables-save >/etc/iptables/ip6tables.conf
checkRet $?

exit 0

/etc/systemd/system/iptables.service

[Unit]
Description=Kernel packet firewall
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/sbin/flash-iptables
ExecReload=/sbin/restore-iptables 
ExecStop=/sbin/flush-iptables
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Wow that's a lot of code for a blog post. ;]

Since this is a SystemD blog post I'll be diving into the SystemD service file. The three segment headers you see here are [Unit], [Service], and [Install]. These headers are required for a SystemD unit. [Install] is optional but if you want to systemctl enable the unit the unit needs the [Install] segment. These segments dictate what the unit is and when it should run, what it runs and how it runs, and how SystemD implements when the unit runs. whew.

  • The Before directive tells systemD to run the unit before the target unit. This directive does not imply a dependency relationship between this unit, and the one it's supposed to run Before. To establish the relationship, we have
  • The Wants directive. This directive tells SystemD that the next unit to be run should be the argument to Wants.
  • Type signifies to SystemD the type of service the unit file will run. In our case it's oneshot, which only means that the service will exit once with no children. Hence oneshot.
  • The only other potentially confusing directive here is RemainAfterExit. This tells SystemD that when the unit exits, it isn't stopped, or not running, rather the effect of the service has been achieved until ExecStop is called. In our case, RemainAfterExit allows us to tell SystemD that the effect of the service is still present until ExecStop is called on the unit by SystemD.
  • The WantedBy directive tells SystemD how to enable the unit if it is enabled.

SystemD allows for some pretty crazy service logic. We used some pretty simple directives such as Before and Wants but there are even more complicated ones that I didn't go into for the sake of brevity.

Anatomy of a SystemD Timer; Another Example

SystemD timers are really cool. They are one of my favorite parts of SystemD. They are simple to the point of making cron pointless to install on systems because they can perform all of cron's functions and more! SystemD timers can also include some of the complex logic of SystemD units!

The example we are about to explore simply pings a host a preset amount every hour.

/etc/systemd/system/phone.service

[Unit]
Description=Ping a box!

[Service]
Type=oneshot
ExecStart=/bin/ping -c 3 1.1.1.1

/etc/systemd/system/phone.timer

[Unit]
Description=Ping hourly

[Timer]
OnCalendar=hourly

[Install]
WantedBy=timers.target

This is a pretty simple example. The only thing new here compared to the last example is the [Timer] segment. The [Timer] segment specifies when the phone unit gets run. One requirement for SystemD timers is that the timer file must have the same name as the unit file. This simplifies the syntax of timers and sets a naming convention for timers and units.

Recap!

WOW what a braindump! The true power of SystemD is in its units and its timers. SystemD units allow for more granular control of how a service is run and SystemD timers give us granular control over when the unit is run. All of this is configured within SystemD. One standardized config format, no dealing with upstart + init, and cron each with their own caveats and formats. If you have any questions please feel free to reach out to me on Twitter!