Debian 11 mit runit als init
senioradminDebian 11 mit runit als init
Was ist ein Init-System?
Unter unixoiden Betriebssystemen - dazu zählen u. a. GNU/Linux, FreeBSD und OpenBSD - wird als erster Prozess immer ein Programm namens “init” gestartet. Dies ist im Betriebssystem-Kern, dem Kernel, so festgelegt. Das Programm init wiederum startet weitere Programme, wie z. B. Systemdienste, und stellt am Ende des Bootprozesses einen Login-Aufforderung bereit
Vereinfacht gesagt läuft das starten eines Unixartigen System so ab:
Bootloader (z.B. Grub) -> Kernel (z.B. der Linux-Kernel) -> init -> Login-Prompt
Vor einigen Jahren fand in den meisten Linux-Distributionen ein Wechsel des Init-Systems statt. Das zuvor benutzte Init-System “SysVinit” hat seinen Ursprung noch in der Urzeit von Unix. Dienste werden bei SysVInit durch Shellskripte gestartet, die sehr komplex werden können. Durch die sequentielle Abarbeitung dieser Skripte konnte ein System sehr lange zum starten brauchen. Dies erschien nicht mehr zeitgemäß. Es gab daher schon seit längerer Zeit Versuche, SysVInit durch etwas moderneres zu ersetzen.
Canonical versuchte es bei Ubuntu mit dem System “Upstart”. Schon lange zuvor hat der IT-Professor Daniel J. Bernstein (auch bekannt als “djb”) mit den “daemontools” versucht, das Init-System zu verbessern.
2010 programmierte Lennart Poettering - Angestellter bei Red Hat - die Software “systemd”. Diese soll nicht nur als Init-System dienen, sondern ein komplettes Framework bereitstellen, welches der Verwaltung von Linux-Systemen dient. Systemd startet nicht nur Dienste, sondern stellt auch Sockets zur Verfügung. Außerdem stellt systemd Dienste mit eigenen Dienstprogrammen als Ersatz zu den traditionellen Unixprogrammen bereit. So existieren z.B. systemd-networkd, systemd-logind, systemd-journald (als Ersatz für syslog), systemd-resolved (Namensauflösung), systemd-timesyncd, usw. Und die Zahl wächst ständig weiter.
Im letzten Jahrzehnt hat sich systemd bei den meisten Distributionen als Standard durchgesetzt. Bei Debian ist es seit Version 8 das Standard Init-System.
Warum nicht das vorgegebene Init-System “systemd” nutzen?
Systemd ist, wie gesagt, nicht nur ein Init-System, sondern nimmt eine Vielzahl von Aufgaben wahr. Wenn man sich vor Augen führt, welche Aufgaben ein modernes Desktop-Betriebssystem zu leisten hat, dann macht es sicher Sinn,diese Aufgaben in einem integrierten System zusammenzufassen. Im allgemeinen interessieren sich Anwenderinnen und Anwender nicht für die einzelnen internen Dienste, die auf dem Rechner laufen. Sie möchten, dass der Rechner funktioniert und schnell ist. Das ist vollkommen legitim und für diesen Zweck ist systemd auch in Ordnung.
In der IT sprechen wir oft von “Anwendungsfällen” und der Anwendungsfall “Desktop” ist nur einer von vielen. Im Bereich der Systemadministration kommt es oft darauf an, das System (Server, Router usw.) stabil und sicher zu halten. Zwei Unix-Grundprinzipien um dies zu erreichen heißen
Keep it small and simple
und
Do one thing and do it well
Dies hat auch den Sinn, all zu große Komplexität zu vermeiden, denn Komplexität ist der Feind der Sicherheit. Nun muss man konstatieren, dass systemd diese Prinzipien nur wenig beachtet. Die Software besteht aus über 1,2 Mio Zeilen Code (Stand 2019) und sie hat es - wenig überraschend - schon zu Schlagzeilen durch spektakuläre Sicherheitslücken gebracht.
Eine Software, die so nahe am Betriebssystem arbeitet, sollte daher - wenn Wert auf Sicherheit gelegt wird - “small and simple” sein, um Sicherheitslücken möglichst zu vermeiden.
Was ist “runit”?
Wie schon geschrieben, wurden im Laufe der letzten 20 Jahre einige Versuche unternommen, das Init-System zu modernisieren. Einige davon fanden durchaus etwas Verbreitung. Das System “OpenRC” könnte man als evolutionäre Weiterentwicklung von SysVInit betrachten. Dieses System kommt vor allem bei Alpine Linux und Gentoo zum Einsatz.
Auf Basis der o.g. daemontools von djb entanden weitere Systeme, wie s6 oder eben runit. Diese daemontools-inspirierten Init-Systeme ähneln sich in der Struktur und Anwendung, sind aber unterschiedlich komplex.
Das System runit ist vor allem auf Einfachheit und eine kleine Codebasis ausgelegt. Dies ist schonmal eine gute Voraussetzung um ein sicheres System aufzubauen. Es besteht aus mehreren kleinen Programmen und kennt per default 3 “stages”:
Stage 1 - Systeminitiierung
Stage 2 - Dienste starten
Stage 3 - Herunter fahren oder neu starten
Die einzelnen Programme sind:
- sv - zur Kontrolle von Diensten
- chpst - Kontrolle einer Prozessumgebung
- runsv - Überwacht einen Prozess (Supervision) und optional einen Protokolldienst für diesen Prozess.
- svlogd - ein einfacher, aber leistungsfähiger Logger,
- runsvchdir - ändert die Service Level
- runsvdir - Startet einen Supervision Tree
Generell besteht ein Init-System aus
- init / PID1 - Initiiert alles weitere
- einem Service Manager - Startet, stoppt und verwaltet Dienste
- einem Supervisor - dieser überwacht laufende Dienste
Runit ist sehr minimal gehalten und hat keinen ausgewachsenen Service Manager. Zum starten und stoppen wird sv
benutzt.
- Dienst aktivieren
ln -s /etc/runit/sv/service_name /run/runit/service
- Dienst deaktivieren
touch /run/runit/service/service_name/down
- Dienst stoppen
sv down service_name # oder sv stop service_name
- Dienst starten
sv up service_name # oder sv restart service_name
- Dienst neu starten
sv restart service_name
- Dienst Status überprüfen
sv status service_name
- Runlevel wechseln (wird alle Dienste beenden und alle Dienste im neuen Runlevel starten)
runsvchdir runlevel
Installation
Ich gehe hier von einer minimalen Systeminstallation von Debian 11 aus, die mit einem “Netinst” ISO Image durchgeführt wurde. Wie eine solche minimale Installation von Debian durchgeführt wird ist nicht Teil dieses Beitrags. Wichtig ist nur: in der Software-Auswahl sollte alles abgewählt werden.
Nach der Anmeldung am System als root werden zuerst die runit Pakete installiert
apt install runit runit-init
Da dies das Init-System austauscht, erfolgt eine Sicherheitsabfrage, bei der Yes, do as I say!
eingeben muss.
Danach wird das System mit reboot
neu gestartet.
Dann erneut Login als root
. Runit sollte bereits rennen, aber es muss noch etwas aufgeräumt werden.
Als erstes wird systemd deinstalliert
apt --purge remove systemd
In der Regel wird ein Login-Manager benötigt.
apt install libpam-elogind
Schließlich wird mit APT-Präferenzen dafür gesorgt, dass sich systemd nicht wieder durch die Hintertür (durch irgendwelche Abhängigkeiten) rein schleicht
cat << EOF > /etc/apt/preferences.d/00systemd
Package: systemd
Pin: origin ""
Pin-Priority: -1
EOF
runit services
Nun läuft runit zwar, aber außer, dass es als init dient und getty startet und überwacht, tut es noch nicht viel.
Genau wie sysvinit startet es zwar auch Dienste über die Skripte in /etc/init.d
, aber das könnte man auch mit SysVinit haben.
Um die Vorteile von runit mit der Supervision von Diensten zu nutzen müssen diese Dienste im “runit Stil” gestartet werden.
Glücklicherweise ist dies sehr einfach. Runit Dienste benötigen, im Gegensatz zu SysVInit, meist nur ein ganz kurzes Startskript.
Die Dienste laufen dabei im Vordergrund und dürfen nicht in den Hintergrund forken (also kein “daemonizing”). Auch Krücken wie
“start-stop-daemon” werden mit runit nicht mehr gebraucht.
rsyslogd
Als erstes wird der Dienst rsyslogd
zu einem runit Dienst “konvertiert"
# runit Serviceverzeichnis anlegen
mkdir /etc/sv/rsyslogd
# run Datei erzeugen
cat << EOF >/etc/sv/rsyslogd/run
#!/bin/sh
exec /usr/sbin/rsyslogd -n
EOF
# Ausführbar machen
chmod a+x /etc/sv/rsyslogd/run
# SysV rsyslogd stoppen
/etc/init.d/rsyslog stop
# SysV Dienst deaktivieren
update-rc.d -f rsyslog remove
# runit Dienst aktivieren
ln -s /etc/sv/rsyslogd /etc/runit/runsvdir/default/
Wie zu sehen ist, sorgt das anlegen eines Symlinks vom Serviceverzeichnis in das runsvdir default Verzeichnis dafür, dass der Dienst als “aktiv” gesetzt und auch gleich gestartet wird.
dbus
Dbus benötigt neben “run” eine weitere Datei namens “check"
mkdir /etc/sv/dbus
cat << EOF > /etc/sv/dbus/check
#!/bin/sh
exec dbus-send --system / org.freedesktop.DBus.Peer.Ping >/dev/null 2>&1
EOF
chmod a+x /etc/sv/dbus/check
cat << EOF > /etc/sv/dbus/run
#!/bin/sh
dbus-uuidgen --ensure=/etc/machine-id
[ ! -d /run/dbus ] && install -m755 -g 81 -o 81 -d /run/dbus
exec dbus-daemon --system --nofork --nopidfile
EOF
chmod a+x /etc/sv/dbus/run
/etc/init.d/dbus stop
update-rc.d -f dbus remove
# Es folgte eine Fehlermeldung, davon nicht irritieren lassen: insserv: FATAL: service dbus has to be enabled to use service elogind
ln -s /etc/sv/dbus /etc/runit/runsvdir/default/
elogind
mkdir /etc/sv/elogind
cat << EOF > /etc/sv/elogind/run
#!/bin/sh
sv check dbus >/dev/null || exit 1
exec /usr/lib/elogind/elogind
EOF
chmod a+x /etc/sv/elogind/run
update-rc.d -f elogind remove
ln -s /etc/sv/elogind /etc/runit/runsvdir/default/
Weitere Dienste zu “runit-fizieren” sollte kein Problem sein. Im Zweifelsfall kann auch bei der auf Arch basierenden Distro Artix nachgesehen werden. Dieses Repo beinhaltet viele Beispiele für runit-Dienstskripte
Optional - Logging mit runit
Runit bringt mit svlogd einen Logging daemon mit, wwelcher Autorotate beherrscht. Dazu wird im Service-Verzeichnis ein Verzeichnis log
angelegt.
In diesem wir wiederum eine Datei ausführbares Skript run
angelegt, welches svlogd startet. Hier ein Beispiel
mkdir -p /etc/sv/<dienstname>/log
cat << EOF > /etc/sv/<dienstname>/log/run
#!/bin/sh
S="dienstname"
mkdir -p /var/log/runit/$S
chown _runit-log:adm /var/log/runit/$S
chmod 750 /var/log/runit/$S
exec chpst -u _runit-log svlogd -tt /var/log/runit/$S
EOF
chmod a+x /etc/sv/<dienstname>/log/run
Damit svlogd loggen kann, muss der Dienst seine Meldungen auf stdout ausgeben. Einige Dienste benötigen dafür eine zusätzliche Konfiguration.
Fazit
Ich habe hier gezeigt, wie systemd auf Debian 11 mit dem Init-Dienst runit ersetzt werden kann. Da runit ein erprobtes, sehr schlankes und sicheres System ist, ist es damit möglich, auch Debian ein ganzes Stück sicherer und zuverlässiger zu konfigurieren. Dies trifft sowohl für Desktops als auch (erst recht) für Server zu. Die Supervision sorgt dafür, dass Dienste überwacht werden.
Ich will nicht verschweigen, dass runit jedoch bei komplexeren Szenarien an seine Grenzen kommt. Dafür ist dann möglicherweise das verwandte und ebenfalls auf den djb daemontools basierende System s6 geeignet, welches jedoch eine deutlich steilere Lernkurve hat.