Fast alles, was ein Computer tut – rechnen, speichern, ein- und ausgeben von Daten –, das tut er mit Hilfe von Prozessen. Entsprechend wichtig ist die Kontrolle über diese Prozesse, bei der Systemd ein gewichtiges Wörtchen mitzureden hat.
Die ersten Prozesse starten beim Booten, und schon hier hat Systemd gleich mehrfach seine Finger im Spiel. Zum einen, indem es den gesamten Bootvorgang steuert und organisiert, zum anderen, indem es später Auskunft gibt, wie es den gestarteten Prozessen geht.
Dabei tritt Systemd die Nachfolge des System-V-Init-Systems an, das inzwischen in die Jahre gekommen ist. Das lässt sich beispielsweise daran ablesen, dass diese Methode für den Systemstart die nötigen Prozesse nur streng nacheinander und in einer starren Reihenfolge in die Spur schicken kann, was verglichen mit dem gleichzeitigen Start verschiedener Services Zeit kostet.
Außerdem benutzt System-V-Init für alle Aktionen Shellskripte, die zwar wortreich, aber dennoch schwer leserlich und zudem langsam sind. Außerdem eignen sie sich beispielsweise für die Koordination parallel laufender Prozesse nur schlecht. Systemd ist dazu angetreten, eine bessere Alternative zu bieten.
Der neue Bootvorgang
Systemd managt nicht nur Dienste, sondern auch Geräte oder Mountpoints. All diese Dinge bezeichnet es als Unit. Die Dateien, die eine solche Unit während des Bootens initialisieren und starten, heißen folgerichtig Unitfiles. Sie sind der direkte Nachfolger der Initskripte. Der Admin findet sie nicht mehr wie bei System-V in Runlevel-Verzeichnissen, sondern unter:
- »/etc/systemd/system/*«
- »/run/systemd/system/*«
- »/usr/lib/systemd/system/*«
Unitfiles sind keine ausführbaren Dateien mehr, sondern Konfigurationsfiles im Stil der Ini-Dateien von Windows. Ein beispielhafter Blick auf das Unitfile für den Start eines MySQL-Servers zeigt die Systemd-Funktionsweise (Listing 1).
Der Abschnitt »[Unit]« enthält zum einen eine Klartext-Beschreibung des Dienstes und zum anderen mit »After« die Vorgabe, welche anderen Dienste vorher gestartet sein müssen. In diesem Fall verlässt sich MySQL darauf, dass das Netzwerk und der Syslog-Service bereits laufen. Mit »Before« ließe sich die gegensätzliche Abhängigkeit zwischen Diensten festlegen, dass also der im Unitfile definierte Dienst vor dem bezeichneten Service starten soll.
Der »[Service]« -Abschnitt legt fest, mit den Rechten welches Benutzers und welcher Gruppe der Datenbankserver laufen soll. Außerdem bestimmt »Type« hier die Art und Weise des Startvorgangs: »Simple« meint, dass das unter »ExecStart« angegebene Programm den Hauptprozess startet. Um Vorarbeiten kümmern sich die beiden unter »ExecStartPre« angegebenen MySQL-Skripte.
Mit »ExecStartPost« werden Skripte aufgerufen, die nach dem Start des Hauptprogramms laufen sollen. So wartet hier das Skript »mysql-wait-ready« darauf, dass MySQL die Aufräumarbeiten abschließt, die es beim Starten normalerweise erledigt. Dadurch starten Dienste, die MySQL voraussetzen, erst dann, wenn die Datenbank auch tatsächlich bereit ist Verbindungen anzunehmen.
Zudem legt das Unitfile noch einen Timeout fest und ordnet den Datenbankservice dem Multiuser-Target zu. Dieses Target ist eine spezielle Unit, die ungefähr die Rolle des früheren Runlevel 3 bei System-V spielt. Wenn sie aufgerufen wird, soll auch die Datenbank starten.
Listing 1
MySQL-Unitfile
01 [Unit] 02 Description=MySQL 5.6 database server 03 After=syslog.target 04 After=network.target 05 06 [Service] 07 Type=simple 08 User=mysql 09 Group=mysql 10 11 # Execute pre and post scripts as root 12 PermissionsStartOnly=true 13 14 ExecStartPre=/usr/libexec/mysql-check-socket 15 ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n 16 ExecStart=/usr/bin/mysqld_safe --basedir=/usr 17 ExecStartPost=/usr/libexec/mysql-wait-ready $MAINPID 18 ExecStartPost=/usr/libexec/mysql-check-upgrade 19 20 # Give a reasonable amount of time for the server to start up/shut down 21 TimeoutSec=300 22 23 # Place temp files in a secure directory, not /tmp 24 PrivateTmp=true 25 26 [Install] 27 WantedBy=multi-user.target
Mehr Sicherheit
Die Unitfiles kennen noch eine ganze Reihe weiterer Parameter, darunter solche, mit denen sich leicht die Sicherheit von Diensten verbessern lässt – und zwar sowohl einfacher als auch wirkungsvoller als das mit dem klassischen System-V-Init-System möglich gewesen ist.
Der erste dieser Parameter im Abschnitt »[Service]« ist:
PrivateNetwork=yes
Damit ist der Dienst von jedem Netzwerk vollkommen abgeschnitten. Er sieht dann nur noch ein Loopback-Device »lo« , und selbst das hat keine Verbindung zum echten Loopback-Device des Host. Natürlich hilft diese Option nicht bei Netzwerk-basierten Diensten weiter. Alle Diente aber, die ohne Netzwerk auskommen, sind damit vollkommen sicher vor Angriffen aus dieser Richtung.
Aber Achtung: Manchmal wird das Netzwerk doch gebraucht, obwohl das nicht auf den ersten Blick erkennbar ist – etwa weil der Dienst die Authentifikation via LDAP abwickeln will. In diesem Fall sollte sicher sein, dass nur Nutzer zu authentifizieren sind, die eine User-ID unterhalb von 1000 haben, denn für diese müssen sich die Namen lokal via »/etc/passwd« in UIDs auflösen lassen.
Ein zweites Sicherheitsfeature in »[Service]« ist:
PrivateTmp=yes
Ist diese Option gesetzt, benutzt der Dienst nicht mehr das globale »/tmp« -Verzeichnis, sondern sein eigenes. Das schützt ihn vor mancherlei bösartigen Symlink- und DoS-Attacken, die via »/tmp« gebräuchlich sind. Allerdings ist auch diese Maßnahme leider nicht in jedem Fall anwendbar. Manche Dienste platzieren nämlich Kommunikationssockets in »/tmp« , die nicht funktionieren, wenn sie sich in einem privaten Verzeichnis befinden.
Mit den nächsten beiden Optionen lässt sich verhindern, dass ein Dienst bestimmte Verzeichnisse beschreiben beziehungsweise überhaupt irgendwie auf sie zugreifen kann:
ReadOnlyDirectories=/var InaccessibleDirectories=/home
Neben der Einschränkung mit Blick auf einzelne Verzeichnisse ist es schließlich auch möglich, einem Dienst einen Satz bestimmter Capabilities zuzuweisen oder ihm bestimmte Fähigkeiten zu entziehen. Eine Liste aller existierenden Capabilities findet sich in der Manpage »capabilities« . Gibt der Admin im Unitfile unter »[Service]« nun beispielsweise
CapabilityBoundingSet=CAP_CHOWN CAP_KILL
an, so hat er damit quasi eine Whitelist der Fähigkeiten definiert, die der Prozess haben muss.
Eine solche Whitelist festzulegen ist nicht immer einfach, oft wird der Admin nur durch Testen ermitteln können, welche Fähigkeiten er einem Dienst unbedingt zubilligen muss. Deshalb ist auch der umgekehrte Weg möglich. Wenn der Admin einer Capability eine Tilde voranstellt, dann bedeutet dies: Diese Fähigkeit soll dem Dienst explizit entzogen werden.
Zu guter Letzt ist es auch möglich, im Unitfile die Ressourcen zu begrenzen, auf die ein Dienst zugreifen darf. Alle beschränkbaren Ressourcen listet die Manpage »setrlimit« . Setzt der Admin wie im Beispiel unten beispielsweise die maximale Größe eines File, das der Dienst erzeugen darf (»FSIZE« ), auf »0« , so darf dieser nirgendwohin schreiben. Definiert man als maximale Anzahl von Prozessen, die der Dienst erzeugen darf (»NPROC« ), den Wert »1« , dann kann der Dienst keine weiteren Prozesse forken:
LimitNPROC=1 LimitFSIZE=0
In analoger Weise lassen sich auch andere Ressourcen begrenzen.
Monitoring für Prozesse
Nach dem Booten ist interessant, ob alle gewünschten Dienste auch funktionieren. Einen Überblick gibt das Kommando »systemctl« . Es listet alle gebooteten Dienste mit einer Statusinformation auf (Listing 2). Möchte der Admin nur die Problemfälle sehen, dann zeigt
systemctl --state=failed
nur die gescheiterten Startversuche. Für einen einzelnen Service erhält er ausführliche Auskünfte mit:
systemctl status mysqld.service
Diese Ausgabe (Listing 3) listet unter anderem auch den Exit-Status der Pre- und Post-Skripte aus dem Unitfile.
Die Statusmeldungen werden von Fall zu Fall recht lang. Deshalb kann der Admin hier entweder mit dem Parameter »-n Zeilenzahl« die Anzahl auszugebender Zeilen beschränken oder mit Hilfe des Parameters »-o Datei« alles in ein File umleiten.
Listing 2
systemctl (Ausschnitt)
01 jcb@localhost:~$ systemctl 02 [...] 03 session-1.scope loaded active running Session 1 of user jcb 04 abrt-ccpp.service loaded active exited Install ABRT coredump hook 05 abrt-oops.service loaded active running ABRT kernel log watcher 06 abrtd.service loaded active running ABRT Automated Bug Reportin 07 accounts-daemon.service loaded active running Accounts Service 08 alsa-state.service loaded active running Manage Sound Card State (re 09 atd.service loaded active running Job spooling tools 10 auditd.service loaded active running Security Auditing Service 11 avahi-daemon.service loaded active running Avahi mDNS/DNS-SD Stack 12 bluetooth.service loaded active running Bluetooth service 13 chronyd.service loaded active running NTP client/server 14 colord.service loaded active running Manage, Install and Generat 15 crond.service loaded active running Command Scheduler 16 cups.service loaded active running CUPS Printing Service
Listing 3
Statusabfrage für einen Service
01 jcb@localhost:~$ systemctl status mysqld.service 02 * mysqld.service - MySQL 5.6 database server 03 Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled) 04 Active: active (running) since Do 2015-11-26 09:52:45 CET; 7h ago 05 Process: 1528 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS) 06 Process: 1000 ExecStartPost=/usr/libexec/mysql-wait-ready $MAINPID (code=exited, status=0/SUCCESS) 07 Process: 919 ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n (code=exited, status=0/SUCCESS) 08 Process: 793 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS) 09 Main PID: 999 (mysqld_safe) 10 CGroup: /system.slice/mysqld.service 11 |- 999 /bin/sh /usr/bin/mysqld_safe --basedir=/usr 12 |_1309 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql... 13 14 Nov 26 09:52:44 localhost.localdomain mysqld_safe[999]: 151126 09:52:44 mysql... 15 Nov 26 09:52:44 localhost.localdomain mysqld_safe[999]: 151126 09:52:44 mysql... 16 Hint: Some lines were ellipsized, use -l to show in full.
Supervisor
Hin und wieder wird es notwendig sein, einzelne Dienste nach dem Booten anzuhalten oder neu zu starten. Auch das gelingt mit »systemctl« und den Kommandos »stop« »start« , »restart« beziehungsweise »reload« , zum Beispiel:
systemctl stop mysqld systemctl start mysqld
Dabei muss sich der Benutzer authentifizieren, wenn er einen Systemdienst starten oder stoppen will. Sollte der Prozess auf das Stop-Kommando nicht mehr reagieren, bleibt noch:
systemctl kill Unitname
Das verschickt ein Kill-Signal an jeden Prozess der Prozessgruppe, also auch an solche, die der Vaterprozess später geforkt hat. Die Wirkung ähnelt daher eher der eines »killall Prozessname« . Mit der Option »-s« ist es auch möglich, einem Prozess ein anderes spezifisches Signal zu schicken, das beispielsweise (wie »SIGHUP« ) einen Reload auslöst:
systemctl kill -s HUP --kill-who=main crond.service
Die zusätzliche Option »–kill-who« bewirkt hier, dass nur der Hauptprozess (»main« ) das Signal erhält. Alternativ könnte an dieser Stelle auch »–kill-who=control« stehen, dann wären alle Control-Prozesse betroffen, also beispielsweise alle, die über die Optionen »ExecStartPre=« , »ExecStop=« oder »ExecReload=« im Unitfile aufgerufen wurden. Mit »–kill-who=all« (das ist der Default) wären dagegen Control- und Hauptprozesse betroffen.
Will der Admin einen Dienst nicht nur einfach beenden, sondern auch verhindern, dass er beim nächsten Booten wieder startet, ist auch das mit »systemctl« möglich. Dafür gibt es das Kommando:
systemctl disable Unitname
Läuft der Prozess noch, wird er durch das Disablen nicht angehalten, war er bereits gestoppt, kann er auch nach dem Disablen noch manuell starten. Lediglich der automatische Neustart beim nächsten Booten wird so verhindert. Es geht noch schärfer, auch wenn das selten nötig sein sollte: Nach
systemctl mask Unitname
startet der Dienst nicht nur nicht mehr (wie bei »disable« ), sondern er lässt sich auch manuell nicht mehr starten. Dabei wird das Unitfile nach »/dev/null« verlinkt. Will der Admin diese Aktion rückgängig machen, muss er diesen Link löschen.
Analyse des Zeitbedarfs
Wer sich schon einmal Gedanken darüber gemacht hat, wo sein Rechner beim Booten die Zeit verplempert, und vielleicht ein Tool wie Bootchart genutzt hat, um dem auf die Schliche zu kommen, der hat es mit Systemd noch viel einfacher. Denn Systemd hat passende Analysetools bereits eingebaut. Das Kommando dafür heißt:
systemd-analyze blame
Es produziert eine absteigend sortierte Liste aller gestarteten Dienste mit dem Zeitbedarf für ihre Initialisierung (Listing 4). Zu beachten ist allerdings, dass die hier aufgeführten Zeiten parallel abgelaufen sein können, der Bootprozess ist ja nicht mehr streng seriell. Über die Ursachen des hohen Zeitbedarfs verrät das Tool nichts, aber der Systemverwalter könnte zumindest überlegen, ob er die Zeitfresser wirklich braucht.
Noch anschaulicher wird das Ganze, wenn man die Daten visualisiert:
systemd-analyze plot > plot.svg eog plot.svg
Auch hierfür bringt Systemd bereits alles Nötige mit.
Listing 4
Analyze (Auszug)
01 jcb@localhost:/var/log$ systemd-analyze blame 02 3.234s docker.service 03 2.152s dnf-makecache.service 04 1.281s plymouth-start.service 05 1.269s mysqld.service 06 1.009s plymouth-quit-wait.service 07 958ms systemd-udev-settle.service 08 603ms slapd.service 09 02ms firewalld.service 10 451ms systemd-journal-flush.service 11 402ms cups.service 12 279ms accounts-daemon.service 13 244ms libvirtd.service 14 198ms ModemManager.service 15 187ms systemd-logind.service 16 183ms NetworkManager.service 17 170ms lvm2-monitor.service 18 167ms chronyd.service 19 155ms avahi-daemon.service 20 155ms systemd-vconsole-setup.service 21 135ms mcelog.service 22 126ms sysstat.service 23 126ms udisks2.service 24 125ms jexec.service 25 124ms bluetooth.service 26 124ms docker-storage-setup.service 27 123ms netcf-transaction.service 28 121ms rtkit-daemon.service 29 120ms livesys.service 30 115ms packagekit.service 31 104ms abrt-ccpp.service 32 102ms systemd-udevd.service 33 100ms var-lib-nfs-rpc_pipefs.mount 34 [...]
Fazit
Wie ein Dienst zu Starten ist und was ihm zur Laufzeit alles erlaubt ist, das lässt sich mit Systemd viel besser und genauer einstellen, als das mit dem alten Init-System bisher möglich war. Die Syntax dafür ist einfach und klar. Im Unterschied zu der Methode mit Shellskripten muss nicht einmal etwas Spezielles programmiert werden.





