Heimserver & Self-Hosting

systemd-Socket-Aktivierung - Der Socket wartet, bevor der Dienst läuft

systemd-Socket-Aktivierung - Der Socket wartet, bevor der Dienst läuft

Ein Dienst kann in der Prozessliste fehlen, obwohl seine Adresse bereits lauscht. Das wirkt nur dann widersprüchlich, wenn der Daemon beide Aufgaben selbst übernehmen muss. Bei der Socket-Aktivierung durch systemd kann der Service Manager zuerst den lauschenden Socket anlegen, eingehenden Datenverkehr kurz halten und den Dienst starten, sobald der Socket verwendet wird.

Das kann auf einem kleinen Server nützlich sein, ist aber kein Schalter, der jeden Daemon schneller oder zuverlässiger macht. Die Anwendung muss wissen, wie sie einen Socket übernimmt, den sie nicht selbst erstellt hat. Eine eingereihte Verbindung beweist nicht, dass die Anwendung fehlerfrei arbeitet. Die Warteschlange ist begrenzt, und die erste Anfrage muss möglicherweise warten, bis ein noch nicht laufender Prozess gestartet ist.

Die hilfreiche Frage lautet daher nicht: “Kann ich eine .socket-Datei hinzufügen?”, sondern: “Welche Verantwortung verlagere ich zu systemd, und unterstützt dieser konkrete Dienst diese Grenze?”

Was wird vom Daemon zu systemd verlagert?

Ein herkömmlicher Netzwerk-Daemon erstellt einen Socket, bindet ihn an eine Adresse, versetzt ihn in den Listening-Zustand und nimmt anschließend Verbindungen an. Die Linux-Dokumentation zu listen(2) beschreibt diesen lauschenden Socket als passiven Endpunkt mit einer Warteschlange für anstehende Verbindungsanfragen.

Die Socket-Aktivierung trennt den ersten Teil von der Anwendung. Eine .socket-Unit von systemd deklariert die Adresse. PID 1 öffnet diesen Endpunkt und aktiviert bei eingehendem Datenverkehr eine zugehörige .service-Unit. Standardmäßig stellt der Name die Beziehung her: example.socket aktiviert example.service. Das Handbuch systemd.socket(5) dokumentiert außerdem eine explizite Überschreibung mit Service=.

Beim nativen systemd-Protokoll erhält der Dienst bereits geöffnete Dateideskriptoren. Die Dokumentation zu sd_listen_fds(3) erklärt, dass sie bei Dateideskriptor 3 beginnen und durch LISTEN_*-Umgebungsvariablen beschrieben werden. Anwendungen sollten die API verwenden und den Deskriptortyp prüfen, statt blind anzunehmen, Deskriptor 3 sei der erwartete Socket.

Der Unterschied ist architektonisch: systemd besitzt den stabilen Listening-Endpunkt; der Daemon verarbeitet die Anfragen. Es handelt sich nicht nur um eine andere Schreibweise für After=network.target.

Verfügbar ist nicht dasselbe wie bereit

Sobald die Socket-Unit aktiv ist, kann ein Client möglicherweise eine Verbindung herstellen, bevor der Dienstprozess vollständig gestartet ist. Der Kernel kann Arbeit einreihen, während systemd den Daemon startet. Das überbrückt ein kurzes Startfenster, verändert aber die Bedeutung eines offenen Ports.

Eine Portprüfung kann nun belegen, dass die Socket-Unit lauscht. Sie kann nicht belegen, dass die Anwendung ihre Konfiguration geladen, ihre Datenbank geöffnet, eine Migration abgeschlossen oder eine korrekte Antwort erzeugt hat. Ein Health Check auf Anwendungsebene muss weiterhin das Anwendungsprotokoll verwenden und das Ergebnis bewerten.

Die Warteschlange ist kein unbegrenzter Warteraum. listen(2) dokumentiert, dass eine volle Warteschlange dazu führen kann, dass eine Verbindung abgelehnt oder ignoriert wird, sodass ein späterer Versuch gelingen kann. systemd bietet für Stream-Sockets die Einstellung Backlog=, doch der Kernel begrenzt sie durch net.core.somaxconn. Socket-Aktivierung kann eine begrenzte Lücke überbrücken; sie kann einen langsamen Start, eine Absturzschleife oder einen überlasteten Dienst nicht in garantierte Zustellung verwandeln.

Accept=no und Accept=yes sind unterschiedliche Modelle

Der Standardwert Accept=no startet einen Dienst und übergibt ihm den lauschenden Socket. Der Daemon nimmt die Verbindungen anschließend mit seinem eigenen Nebenläufigkeitsmodell an und verarbeitet sie. Dies ist im Allgemeinen der natürliche Modus für einen langlebigen Server, der die Deskriptorübergabe von systemd ausdrücklich unterstützt.

Mit Accept=yes nimmt systemd jede Verbindung an und erstellt eine Instanz eines Template-Dienstes wie [email protected]. Diese Instanz erhält einen verbundenen Socket, bei inetd-kompatiblen Programmen üblicherweise über Standardeingabe und Standardausgabe. Eine Instanz pro Verbindung kann eine klare Isolationsgrenze bieten, doch ein Prozessstart für jede Verbindung passt schlecht zu vielen stark genutzten Protokollen.

Die Wahl ist daher kein Tuning-Schalter. Sie bestimmt, ob die Anwendung einen Listening-Endpunkt oder ein einzelnes verbundenes Gespräch erhält, welche Unit vorhanden sein muss und wo die Nebenläufigkeit verwaltet wird.

Kompatibilität ist die erste Hürde

Ein Daemon wird nicht allein deshalb socket-aktivierbar, weil systemd seinen Port binden kann. Die offizielle Dokumentation der Socket-Unit verlangt, dass die Software geerbte Sockets über die native systemd-Schnittstelle oder eine inetd-artige Schnittstelle mit StandardInput=socket annimmt.

Vor dem Schreiben einer Unit sollten die Dokumentation des Daemons und die vom Paket bereitgestellten Units geprüft werden. Relevant sind ausdrückliche Hinweise auf Socket-Aktivierung, sd_listen_fds, LISTEN_FDS oder den inetd-Betrieb. Eine herkömmliche Option --listen nimmt nicht automatisch einen geerbten Deskriptor an; häufig weist sie das Programm an, die Adresse selbst zu binden, was mit dem bereits von systemd gehaltenen Socket kollidieren würde.

Ein Proxy kann einen inkompatiblen Daemon manchmal überbrücken, fügt jedoch einen weiteren Prozess und eine weitere Fehlergrenze hinzu. Auf einem kleinen Server kann es einfacher und besser beobachtbar sein, einen bescheidenen Dienst dauerhaft laufen zu lassen, als nur für einen bedarfsgesteuerten Start einen Adapter einzuführen.

Ein begrenztes lokales Experiment

Das systemd-Projekt stellt systemd-socket-activate ausdrücklich zum Testen socket-aktivierter Programme bereit. Das Handbuch enthält dieses Beispiel für einen Echo-Server:

systemd-socket-activate -l 127.0.0.1:2000 --inetd -a cat

In einem anderen Terminal kann sich ein Client mit 127.0.0.1:2000 verbinden; jede an cat gesendete Zeile wird zurückgegeben. Die ausdrückliche Bindung an Loopback hält diese Demonstration von externen Schnittstellen fern. Dennoch ist sie ein nicht authentifizierter Echo-Dienst. Er sollte nach dem Experiment beendet und nicht als Produktionsfunktion veröffentlicht werden.

Die entsprechende Unit-Struktur macht das Modell pro Verbindung sichtbar:

# example-echo.socket
[Unit]
Description=Local socket-activation demonstration

[Socket]
ListenStream=127.0.0.1:2000
Accept=yes

[Install]
WantedBy=sockets.target
# [email protected]
[Unit]
Description=Local echo demonstration for %I

[Service]
Type=exec
ExecStart=/usr/bin/cat
StandardInput=socket
StandardOutput=socket
DynamicUser=yes
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict

Dies ist ein Laborbeispiel und keine Vorlage zur Umstellung eines beliebigen Daemons. Es verwendet Accept=yes, weil cat ein einzelnes Gespräch über Standardeingabe und Standardausgabe versteht. Ein echter Server mit vielen Verbindungen benötigt häufig Accept=no und native Deskriptorunterstützung.

Vor der Installation eines eigenen Unit-Paars sollte es mit dem lokalen Manager geprüft werden:

systemd-analyze verify ./example-echo.socket ./[email protected]
systemctl --version

Eine fehlerfreie Verifikation prüft die Unit-Syntax und einige Abhängigkeiten. Sie beweist nicht, dass das Protokoll funktioniert, die Bindung angemessen eingeschränkt ist oder der Dienst bei gleichzeitigem Datenverkehr sicher arbeitet. Auch die Versionsprüfung ist wichtig, weil die Online-Dokumentation des aktuellen systemd-Quellcodes Einstellungen enthalten kann, die einer älteren Distribution fehlen.

Wo Socket-Aktivierung helfen kann

Selten verwendete lokale Dienste

Ein selten verwendetes Hilfsprogramm kann gestoppt bleiben, bis ein Client seinen Unix- oder Loopback-Socket erreicht. Dadurch kann die Zahl inaktiver Prozesse sinken. Die tatsächliche Ressourceneinsparung hängt jedoch vom Daemon ab und sollte gemessen statt vorausgesetzt werden.

Startreihenfolge über einen Endpunkt

Clients können einen Socket sehen, bevor der Serverprozess vollständig gestartet ist. Lennart Poetterings ursprüngliche Erklärung der Socket-Aktivierung beschreibt, wie dies einen Teil expliziter Dienstreihenfolgen durch vom Kernel verwaltetes Warten ersetzen und mehr parallelen Start ermöglichen kann.

Den Listening-Endpunkt bei einem Neustart behalten

Bei nativer Aktivierung mit Accept=no behält der Service Manager seine Kopie des lauschenden Sockets. Die API-Dokumentation erklärt, dass ein neu gestarteter Daemon denselben zugrunde liegenden Socket erhalten kann, solange die Socket-Unit aktiv bleibt. Anstehende Arbeit kann während eines kurzen Neustarts warten.

“Kann warten” ist die vorsichtige Formulierung. Anfragen, die der alte Prozess bereits aus der Warteschlange entnommen hat, Anwendungsspeicher, Datenbanktransaktionen, Protokoll-Timeouts und endliche Kernel-Puffer liegen außerhalb dieser Zusage. Socket-Eigentum ist ein Teil der Kontinuität, aber für sich genommen keine Zero-Downtime-Garantie.

Kosten, die leicht verborgen bleiben

Die erste Verbindung trägt die Aktivierungslatenz. Bei langsamer Initialisierung können Clients mit kurzen Timeouts scheitern, obwohl spätere Anfragen schnell sind. Ein wiederholt abstürzender Prozess kann außerdem durch Datenverkehr erneut ausgelöst werden, bis Start- oder Trigger-Limits von systemd greifen. Logs und der Zustand der Rate Limits sind deshalb für die Diagnose wichtig.

Der Socket selbst ist eine exponierte Ressource. ListenStream=2000 und ListenStream=127.0.0.1:2000 drücken nicht dieselbe Grenze aus. Auch Unix-Domain-Sockets benötigen bewusst gewählte Eigentümer und Modi. Die Verlagerung von bind() zu PID 1 beseitigt keine Entscheidungen über Firewall, Authentifizierung oder minimale Berechtigungen.

Der Betrieb wird zu einer Aufgabe mit zwei Units. Wird nur der Dienst gestoppt, kann der Socket weiter lauschen und eingehender Datenverkehr den Dienst erneut aktivieren. Deployment-, Monitoring- und Rollback-Verfahren müssen beide Units nennen und zwischen “Socket verfügbar” und “Dienst läuft” unterscheiden. Dieses zusätzliche Modell lohnt sich nur, wenn es ein tatsächliches Lebenszyklusproblem löst.

Checkliste für eine vorsichtige Einführung

  1. Bestätigen, dass der Daemon und seine installierte Version native systemd- oder inetd-artige Socket-Aktivierung ausdrücklich unterstützen.
  2. Die aktuelle Bind-Adresse, den Socket-Typ, Eigentümer, Berechtigungen, Backlog-Annahmen und das Startverhalten dokumentieren.
  3. Accept=no oder Accept=yes anhand der Schnittstelle des Daemons wählen, nicht anhand eines kopierten Beispiels.
  4. An die engste erforderliche Adresse binden und bestehende Authentifizierungs- und Firewall-Kontrollen beibehalten.
  5. Units validieren und anschließend Latenz der ersten Verbindung, parallelen Datenverkehr, Fehler, Neustart und Warteschlangensättigung außerhalb der Produktion testen.
  6. Beide Units überwachen und einen Health Check auf Anwendungsebene verwenden.
  7. Ein Rollback bereithalten, das zuerst den Socket stoppt und deaktiviert, bevor die eigene Bind-Konfiguration des Daemons wiederhergestellt wird.

Der Socket ist eine Grenze, kein Heilmittel

Die Socket-Aktivierung von systemd kann einem Listening-Endpunkt einen Lebenszyklus geben, der von einem einzelnen Daemon-Prozess unabhängig ist. Das ist für bedarfsgesteuerte Dienste, ausgewählte Startabhängigkeiten und kurze Neustartfenster nützlich. Ihr stärkster Vorteil ist keine magische Geschwindigkeit, sondern der ausdrückliche Besitz des Endpunkts.

Dieselbe Trennung kann die Realität verschleiern, wenn eine Portprüfung mit Anwendungsgesundheit verwechselt oder eine endliche Warteschlange als verlustfrei beschrieben wird. Am Anfang sollten Kompatibilität und Fehlerverhalten stehen. Wenn ein Dienst günstig dauerhaft betrieben werden kann, keine geerbten Deskriptoren unterstützt oder eine vorhersehbare Latenz der ersten Anfrage benötigt, kann eine gewöhnliche, ständig laufende Unit die ehrlichere Architektur sein.

References