Porting SysVinit Scripts to Systemd

At Belvedere, we recently began the migration to Systemd on some of our production Linux systems. In so doing, BT ported several of our Sysvinit scripts into systemd.service files.

Systemd is a daemon manager for Linux systems, which serves a similar purpose as Sysvinit; however, unlike Sysvinit, Systemd is additionally responsible for managing other aspects of a system such as mounts, devices, file paths, sockets, swapfiles, timers, etc.  It also provides resource management capability for services it manages via its CGroups integration.  Because of Systemd’s many responsibilities, it does a great deal to unify and simplify aspects of Linux systems that sometimes can be complex, such as booting and service management.  Although many are wary of the apparent divorce from the Unix philosophy of “Do one thing and do it well,” it has been adopted by the latest versions of most common Linux distributions.

Moving some of our Sysvinit bash scripts into the *ini* style systemd.service configurations while maintaining the same functionality has been an informative process. For many of the less complex Sysvinit scripts in our environment, it is quite simple.  We outline below the steps that we took to translate the Sysvinit script for our Splunk Forwarder service into a systemd.service configuration file of similar functionality.

Our no-frills Splunk Forwarder Sysvinit script in question reads as follows:

RETVAL=0

. /etc/init.d/functions

splunk_start() {
 echo Starting Splunk...
 "/opt/splunkforwarder/bin/splunk" start --no-prompt --answer-yes
 RETVAL=$?
 [ $RETVAL -eq 0 ] && touch /var/lock/subsys/splunk
}

splunk_stop() {
 echo Stopping Splunk...
 "/opt/splunkforwarder/bin/splunk" stop
 RETVAL=$?
 [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/splunk
}

splunk_restart() {
 echo Restarting Splunk...
 "/opt/splunkforwarder/bin/splunk" restart
 RETVAL=$?
 [ $RETVAL -eq 0 ] && touch /var/lock/subsys/splunk
}

splunk_status() {
 echo Splunk status:
 "/opt/splunkforwarder/bin/splunk" status
 RETVAL=$?
}

case "$1" in

 start)
splunk_start
;;

 stop)
splunk_stop
;;

 restart)
splunk_restart
;;

 status)
splunk_status
;;

 *)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac

exit $RETVAL

We begin our translation efforts with the _UNIT_ section of the systemd.service script.  This section can be pretty sparse as the only information we really need to encode here is a human-readable description of the service (to be used in logs, return statements, etc.), a couple of pieces of information about how this script should be ordered, and a description of its dependencies.  Because our Splunk Forwarder relies on a network connection and relatively little else, this is a very short stanza.

[Unit]
Description = Splunk Universal Forwarder
Wants = network.target
After = network.target

A brief note on these fields: for strict dependencies, it is possible to employ the ‘Requires’ field to ensure that if the required service activates or deactivates, our service will follow suit. ‘Wants’ is simply a less strict enforcement of this dependency as it will attempt to start the service in the specified field if it has not already been started, but the process will not immediately fail as a result. Using ‘Wants’ where possible is recommended usage of Systemd.

The ‘After’ field is used by the system to aid in creating the execution order.  Here it will attempt to start our service sometime after it attempts to start the network.target (networking) service.

As Systemd has a wider purview than just daemon management, we specify that we are configuring a service with the next stanza, as well as include control information from our Sysvinit script.

[Service]
ExecStart=/opt/splunkforwarder/bin/splunk start
ExecStop=/opt/splunkforwarder/bin/splunk stop
ExecReload-/opt/splunkforwarder/bin/splunk restart
StandardOutput=syslog
Type=forking
RemainAfterExit=yes
PidFile=<path/to/pid/file>

First, we specify the method of starting, stopping, and restarting the script via the ‘ExecStart’, ‘ExecStop’, and ’ExecReload’ fields, respectively; this resembles the familiar template functions section of a typical Sysvinit script. Next, we specify that we would like syslog to handle the logging of the service with the ‘StandardOutput’ field.

The ‘Type’ field is somewhat more nuanced but nothing out of the ordinary; it simply specifies that we expect this service to exhibit the traditional unix daemon behavior of calling fork and exit as part of its startup process. Similarly, the ‘RemainAfterExit’ is necessary lest the service be stopped immediately after the exit() call within the ExecStart action.

Finally, the ‘PidFile’ field, though self-explanatory, is worth mentioning in that it is explicitly recommended to have this field for forking services.

Our final stanza, under the _INSTALL_ section name, is not used by Systemd during runtime; rather it describes the behavior of this service when being enabled or disabled.

[INSTALL]
WantedBy=multi-user.target

The ‘WantedBy’ field signifies that our script is started when the specified target is started. In this case, as the target is mult-user.target, this service is set to start, if enabled, when the multi-user run level is initiated (which roughly translates to run level 3 in Sysvinit).

With that, we are finished, and it is only a matter of moving this file to the correct location to allow Systemd to manage our service. Of course, with more complex Sysvinit scripts this process can become a little more involved.  But in many cases it is simply a matter, such as in the above, of explicitly declaring some dependencies/ordering and directly copying the path to the executable.