paulgorman.org/technical

runit

(January 2017)

runit is a multi-platform init system. runit’s process sueprvisor can be used on top of a non-runit init.

Things that attracted me to runit:

runit-init replaces /sbin/init. The command init 0 tells runit to halt the system. init 6 reboots it. Therunsvdirandrunsvchdir` programs handle runlevels. Service dependencies are resolved automatically.

runit reads service configurations from subdirectories of /service/. runit also keeps state information (e.g. the service PID) in the /service/ subdirectories.

(Note: on some systems, /service might be /etc/service or even /var/service.)

Several binaries make up runit:

Init

When acting as the system init, runit has three stages:

  1. runit starts /etc/runit/1 and waits for it to terminate. This handle one-time system initialization tasks.
  2. runit starts /etc/runit/2. This keeps running (if all goes well) until system reboot or halt. If stage two crashes, it restarts.
  3. When stage two returns without errors (at system halt or reboot), /etc/runit/3 runs tasks to cleanly bring down the machine.

Each of these three stages may be very simple shell scripts. Here the sample of 1 from Debian Sarge:

#!/bin/sh
# system one time tasks

PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin

/etc/init.d/rcS
/etc/init.d/rmnologin

touch /etc/runit/stopit
chmod 0 /etc/runit/stopit

Here the sample of 2 from Debian Sarge:

#!/bin/sh

PATH=/command:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin

exec env - PATH=$PATH \
runsvdir -P /etc/service 'log: ...........................................................................................................................................................................................................................................................................................................................................................................................................'

Here the sample of 2 from Debian Sarge:

#!/bin/sh
exec 2>&1

PATH=/command:/sbin:/bin:/usr/sbin:/usr/bin

LAST=0
test -x /etc/runit/reboot && LAST=6

echo 'Waiting for services to stop...'
sv -w196 force-stop /etc/service/*
sv exit /etc/service/*

echo 'Shutdown...'
/etc/init.d/rc $LAST

Not Init

runit can act as a pure process supervisor, started by another init system. The init system simply runs stage two of runit, typically by running runsvdir as a service. On SysV Init, for example, add an entry for /sbin/runsvdir-start to /etc/inittab. runit will need the 2 script copied to /sbin/runsvdir-start and the /service directory created:

 # install -m0750 /package/admin/runit/etc/2 /sbin/runsvdir-start
 # mkdir -p /service

Supervise a Process

First, create a new service directory. Second, create a run script in the new service directory, and make it executable.

# mkdir -p /etc/sv/myservice/
# touch /etc/sv/myservice/run
# chmod a+x /etc/sv/myservice/run

For use with runit, service daemon should not background themselves. In the run script, use exec to have the script replace itself with the newly run daemon. Here’s an example run script to start getty:

#!/bin/sh
exec getty 38400 tty2 linux

To tell runit about the new service, and run it on boot, create a link in the /service/ directory. runnit should notice the next time it checks /service/ (about every five seconds).

# ln -s /etc/sv/myservice /service/

That’s pretty much it.

runnit can handle dependencies. Just have the service’s run file check if the dependency is running yet using sv:

#!/bin/sh
sv start my-earlier-service || exit 1
exec my-later-service

To add an appendant log, create a /log/ subdirectory with another run script.

# mkdir -p /etc/sv/myservice/log
# touch /etc/sv/myservice/log/run
# chmod a+x /etc/sv/myservice/log/run

The run script should run a logging daemon, like:

#!/bin/sh
exec chpst -ulog svlogd -tt ./main

Service Control

# sv hup myservice
# sv status myservice
# sv up myservice
# sv down myservice

[The notes below are slightly older, and contain some repetition of the above.]

Process Supervision

Each service has a service directory. Each service runs as a child of a supervising runsv process that operates out of the service directory. This obviates the need to guess about service PID’s or to write pidfiles.

# sv up /service/myservice
# sv status /service/myservice
# sv down /service/myservice
# sv restart /service/myservice
# sv hup /service/myservice

The /service/ directory holds symlinked directories for each of the processes currently enabled. The symlinks point to /etc/sv/, which contains directories with configs for enabled and disabled services. (This is a bit like Apache2’s /etc/sites-enabled/ and /etc/sites-available/.)

Each service directory contains a run file — a simple shell script to start the process, like:

#!/bin/sh
exec /usr/local/sbin/exim -bdf -q30m

See example run scripts at http://smarden.org/runit/runscripts.html.

Service dependencies and ordering

The run scripts handle this. If a run script returns non-zero, runit will try it again later. So, here’s a run script for “bar” that makes sure “foo” has already started:

 #!/bin/sh
 sv start foo || exit 1
 exec bar --myoption

Runlevels

http://smarden.org/runit/runlevels.html

Runlevels correspond to directories in /etc/runit/runsvdir/. Switching runlevels is done by switching the directory the runsvdir program is running in with runsvchdir:

# runsvchdir single
# runsvchdir default

Create a custom runlevel by making a new directory in /etc/runit/runsvdir/`, and symlinking the directories of the services we want into that directory.

LXC, busybox, and runit

New versions of busybox include runit. This makes busybox attractive as an LXC container template.

Unfortunately, the version of busybox shipped with Debian Stretch is too old to include runit. Here’s how to use a newer busybox to make a runit lxc container on Debian.

# mkdir -p /etc/sv /etc/service /etc/runit/runsvdir
# ln -s /etc/service /service
# busybox --list | awk '/runsv|chpst|svlog|^sv$/' | xargs -I{} ln -sv /bin/busybox /bin/{}
#!/bin/sh
PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin
exec env - PATH=$PATH \
/bin/runsvdir /service 'log: ....................................................................................'
# cat >>/etc/inittab <<EOF
::respawn:/sbin/runsvdir-start
EOF

Set up runit service supervision in an Alpine Linux LXC container

NO, THE BELOW ISN’t QUITE RIGHT YET.

The version of Busybox in Alpine (3.6) does not include the runit applets, so we must download the official Busybox build.

# lxc-create -n test-alpine -t alpine -- -r v3.6
$ cd /tmp
$ wget https://www.busybox.net/downloads/binaries/1.26.2-i686/busybox
# cp /tmp/busybox /var/lib/lxc/alpine-runit/rootfs/bin/
# lxc-start -n alpine-runit
# lxc-attach -n alpine-runit

# busybox --list | awk '/runsv|chpst|svlog|^sv$/' | xargs -I{} ln -sv /bin/busybox /bin/{}
# ln -s /bin/busybox /usr/bin/runsvdir
# rc-update add runsvdir boot

The /etc/init.d/runsvdir (which mysteriously comes with Alpine although runit does not), expects our runit service directory to be /run/openrc/sv… no, that’s a tmpfs….?