Aus Linux-Magazin 02/2016

Systemd und die Prozesse

© ammentorp, 123RF

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.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben