Senior Admin

Published by senioradmin on

Debian 11 mit runit als init

senioradmin

Debian 11 mit runit als init

English version

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:

Generell besteht ein Init-System aus

Runit ist sehr minimal gehalten und hat keinen ausgewachsenen Service Manager. Zum starten und stoppen wird sv benutzt.

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.

Tags: Debian.