Systemd Hellod World

26 June 2021

Coming from a world of containers managed by Docker, systemd lets us do that for binaries on a host. We will be setting up a small webserver that is managed by systemd on our machine. Our little server will be called hellod.

We will be using go net/http, and creating 2 paths that are available on port 8080: / responds to a request /kill exits the process, simulating something bad happening

// main.go
package main

import (
  "fmt"
  "net/http"
  "os"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    fmt.Println("got a request!")
    fmt.Fprintf(w, "Systemd %s!", "hello")
  })

  http.HandleFunc("/kill", func(w http.ResponseWriter, req *http.Request) {
    fmt.Println("killed by handler")
    os.Exit(1)
  })

  fmt.Println("starting up!")
  if err := http.ListenAndServe(":8080", nil); err != nil {
    panic(err)
  }
  fmt.Println("shutting down")
}

The systemd docs have an example service file we can mostly fill out:

  • Description - to tell us what this is
  • ExecStart - the command we will be starting, our go hellod binary
  • WantedBy - the target machine state for enabling start on boot, multi-user.target is when everything before the * GUI has been started
  • Restart - restarts our binary when it stops
  • RestartSec - time interval between restarts
# hellod.service
[Unit]
Description=our hellod service

[Service]
ExecStart=/usr/local/bin/hellod
Restart=on-failure
RestartSec=100ms

[Install]
WantedBy=multi-user.target

Now we need a few commands to put the files in the right places, this is the Makefile I will be using:

# Makefile
setup:
  go build -o hellod ./...
  sudo mv hellod /usr/local/bin/hellod

  sudo cp hellod.service /etc/systemd/system/hellod.service
  sudo systemctl daemon-reload

logs:
  journalctl -u hellod.service --follow

Seeing it in action

We need two shells, one for tailing our service’s logs and the other for hitting our webserver.

In one shell

First, we need to start by building our go webserver, putting the binary in a bin folder, and moving our hellod.service to where systemd expects to find it.

make setup
systemctl start hellod.service
make logs
# Jun 27 13:34:28 hellod-machine systemd[1]: Started hellod.
# Jun 27 13:34:28 hellod-machine hellod[41947]: starting up!

In another shell

This shell will be for viewing the status of our webserver and hitting its endpoints. Assuming all goes well, systemctl will show us that our binary is active and running:

systemctl status hellod.service
# ● hellod.service - our hellod service
#      Loaded: loaded (/etc/systemd/system/hellod.service; disabled; vendor preset: enabled)
#      Active: active (running) since Sun 2021-06-27 13:34:28 EDT; 52s ago
#    Main PID: 41947 (hellod)
#       Tasks: 5 (limit: 38391)
#      Memory: 1.3M
#      CGroup: /system.slice/hellod.service
#              └─41947 /usr/local/bin/hellod

# Jun 27 13:34:28 hellod-machine systemd[1]: Started hellod.
# Jun 27 13:34:28 hellod-machine hellod[41947]: starting up!

Next, we want to hit it with a few web requests to make sure everything is working, using our /kill endpoint to make sure it also gets restarted correctly.

# the output here is from our journalctl log tail

curl 0.0.0.0:8080
# Jun 27 13:37:30 hellod-machine hellod[41947]: got a request!

curl 0.0.0.0:8080/kill
# Jun 27 13:56:51 hellod-machine hellod[54506]: killed by handler
# Jun 27 13:56:51 hellod-machine systemd[1]: hellod.service: Main process exited, code=exited, status=1/FAILURE
# Jun 27 13:56:51 hellod-machine systemd[1]: hellod.service: Failed with result 'exit-code'.
Jun 27 15:16:32 potato-v2 systemd[1]: hellod.service: Scheduled restart job, restart counter is at 1.
Jun 27 15:16:32 potato-v2 systemd[1]: Stopped our hellod service.
Jun 27 15:16:32 potato-v2 systemd[1]: Starting our hellod service...
Jun 27 15:16:32 potato-v2 systemd[1]: Started our hellod service.
Jun 27 15:16:32 potato-v2 hellod[71206]: starting up!

We can see our service is running and exited when we expected it to. Last is making sure it started back up again:

systemctl status hellod.service
● hellod.service - our hellod service
     Loaded: loaded (/etc/systemd/system/hellod.service; disabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-06-27 15:16:32 EDT; 37s ago
    Process: 71205 ExecStartPre=/bin/echo startup begin (code=exited, status=0/SUCCESS)
    Process: 71208 ExecStartPost=/bin/echo startup end (code=exited, status=0/SUCCESS)
   Main PID: 71206 (hellod)
      Tasks: 6 (limit: 38391)
     Memory: 1.6M
     CGroup: /system.slice/hellod.service
             └─71206 /usr/local/bin/hellod

Jun 27 15:16:32 potato-v2 systemd[1]: Starting our hellod service...
Jun 27 15:16:32 potato-v2 echo[71205]: startup begin
Jun 27 15:16:32 potato-v2 echo[71208]: startup end
Jun 27 15:16:32 potato-v2 systemd[1]: Started our hellod service.
Jun 27 15:16:32 potato-v2 hellod[71206]: starting up!

curl 0.0.0.0:8080
# Jun 27 15:17:25 potato-v2 hellod[71206]: got a request!

The last piece of the puzzle is to enable this to start when the machine starts, or restarts. With sudo systemctl start hellod.service we now have a fully running systemd service, hellod!