Customising systemd unit files

systemd is now one of the most common init systems used on Linux. It's also become the default init system for a range of Linux distributions including CentOS, Debian, and Amazon Linux 2.

One of the great things about systemd is unit files can easily be modified to control how a service is launched. This post is going to look at customising unit file configuration, while still playing nice with the system package manager.

Adding a CPU quota to Apache

On CentOS, the unit file for the httpd service is /usr/lib/systemd/system/httpd.service, and has contents similar to the following:

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target

For the example in this post, I'm going to show a few different ways to add the following configuration:

[Service]
CPUQuota=40%

This configuration will limit Apache's maximum CPU usage to 40% of a core. For more info, have a look at the systemd documentation on resource control.

Adding the new config directly

It's tempting to modify /usr/lib/systemd/system/httpd.service directly:

@@ -5,6 +5,7 @@
 Documentation=man:apachectl(8)

 [Service]
+CPUQuota=40%
 Type=notify
 EnvironmentFile=/etc/sysconfig/httpd
 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND

While this will work initially there is a big drawback, the file is not marked as a config file:

$ rpm --query --configfiles httpd |grep httpd.service
$

As a result, if httpd is updated the configuration change will be overridden!

Copying the unit file

To avoid the problem above, custom configuration should be placed in /etc/systemd/system/, this configuration will the be read instead of the configuration in /usr/lib/systemd/system/. Therefore you can just copy and modify the configuration file:

cp -p /usr/lib/systemd/system/httpd.service /etc/systemd/system/
sed -i '/^\[Service\]/aCPUQuota=40%' /etc/systemd/system/httpd.service
systemctl daemon-reload

This will work fine, however there is a slight drawback. If a later version of the httpd package modifies the unit file, systemd will ignore the changes until you manually copy them over to /etc/systemd/system/httpd.service.

Using .include

Instead of just copying the file, a better approach is to include the system default file using .include, and then add any custom configuration. This can be done by adding configuration similar to the following to /etc/systemd/system/httpd.service:

.include  /usr/lib/systemd/system/httpd.service

[Service]
CPUQuota=40%

Once the config has been updated, don't forget to reload the unit file:

systemctl daemon-reload

systemctl show can then be used to check the new configuration option has been picked up correctly:

$ systemctl show httpd.service --property=CPUQuotaPerSecUSec
CPUQuotaPerSecUSec=400ms