Systemd: Ruby Daemon Supervision

Since Ubuntu has migrated from upstart to systemd in order to unify basic Linux service behaviors across all distributions we now have to deal with systemd as a default service manager / supervisor. In this article we will write a systemd supervisor service for a simple Ruby script as an example.

Systemd services location:

/lib/systemd/system/*.service

Logs location:

/var/log/syslog

It's even more convinient to fetch logs with a special journalctl utility which reads a systemd journal:

$ journalctl --since "2017-02-25 00:00:00"

Let's create a simple Ruby script which writes numbers in a logfile infinitely:

$ cd /home/username/
$ cat mydaemon.rb
$stdout.reopen("/home/username/mydaemon.log", "a")
$stdout.sync = true
loop.with_index do |_, i|
  puts i
  sleep(3)
end

And start it as a daemon:

$ ruby mydaemon.rb &
[1] *pid*

Checking logs to make sure the daemon is running as expected:

$ tail -f mydaemon.log
0
1
2
3
^C

And stop it:

$ kill *pid*

Now add a systemd service which will start this daemon automatically and restart it whenever it was stopped or crashed.

$ cat /lib/systemd/system/mydaemon.service
[Unit]
Description=Simple supervisor

[Service]
User=username
Group=username
WorkingDirectory=/home/username
Restart=always
ExecStart=/usr/bin/ruby mydaemon.rb

In contrary to upstart systemd does not start new services automatically. You can check its status with:

$ systemctl status mydaemon
● mydaemon.service - Simple supervisor
   Loaded: loaded (/lib/systemd/system/mydaemon.service; static; vendor preset: enabled)
   Active: inactive (dead)

And start it with:

$ systemctl start mydaemon

Now the status should show that mydaemon.rb has been started and running with a process ID assigned:

$ systemctl status mydaemon
● mydaemon.service - Simple supervisor
   Loaded: loaded (/lib/systemd/system/mydaemon.service; static; vendor preset: enabled)
   Active: active (running) since *date*
   Main PID: *pid* (ruby)
   Tasks: 2
   Memory: 4.0M
   CPU: 52ms
   CGroup: /system.slice/mydaemon.service
           └─*pid* /usr/bin/ruby mydaemon.rb

If you now try to kill the process — supervisor should immediately restart it:

$ kill *pid*
$ systemctl status mydaemon
● mydaemon.service - Simple supervisor
   Loaded: loaded (/lib/systemd/system/mydaemon.service; static; vendor preset: enabled)
   Active: active (running) since *date*
   Main PID: *new_pid* (ruby)
   Tasks: 2
   Memory: 4.0M
   CPU: 49ms
   CGroup: /system.slice/mydaemon.service
           └─*new_pid* /usr/bin/ruby mydaemon.rb

Bigger systemd service example:

[Unit]
Description=My description here

After=another.target
After=some.service

[Service]
Type=simple # forking/oneshot/dbus/notify/idle

PIDFile=/absolute/file/name

User=username
Group=groupname

WorkingDirectory=/home/username/some/dir

Restart=on-failure

Environment=ONE='one' "TWO='two two' too" THREE=
EnvironmentFile=/path/to/file/with/variables

ExecStartPre=/run/some/command
ExecStartPre=/run/some/other/command
ExecStart=/usr/bin/node /opt/webhook/server.js
ExecStartPost=/lastly/run/this

[Install]
WantedBy=multi-user.target

Restart options:

Systemd restart settings

Upstart <-> Systemd commands cheatsheet:

Upstart to systemd

References

Systemd man
Systemd for Upstart users