Systemd: start und stop dependencies
Das Problem
Auf einem Debian-Server (jessie, systemd 215) läuft eine Web-Applikation, die einen Samba-Share (cifs) via openvpn (IPv6) benötigt. Mit der naiven Konfiguration funktioniert manuelles Starten und Stoppen problemlos, beim Reboot geht’s aber nicht richtig.
Naive Konfiguration:
- Debian Packages:
apache2
cifs-utils
openvpn
- cifs mount in
/etc/fstab
konfiguriert - openvpn Konfiguration in
/etc/openvpn/client.conf
Wenn man alles einzeln startet, funktioniert es reibungslos:
systemctl start openvpn@client.service
mount /var/www/data
systemctl start apache2.service
Auch das Stoppen geht manuell problemlos:
systemctl stop apache2.service
umount /var/www/data
systemctl stop openvpn@client.service
Aber bei einem reboot klappt weder Hochfahren…
- Beim Start wird der mount versucht, bevor das VPN verfügbar ist und schlägt natürlich fehl.
- Apache läuft zwar, aber die Web-Applikation hat ein Problem, sobald sie auf Files im cifs mount zugreifen will.
… noch Runterfahren:
- Das VPN ist unterbrochen, bevor der umount abgeschlossen ist.
- Das System hängt 2 Minuten bis der Timeout für den umount abgelaufen ist. Sehr lästig.
Ansatz #1
Die Lösung sollte mit systemd units nicht allzu schwierig sein.
Da sich in /etc/fstab
keine Abhängigkeiten explizit formulieren lassen, wird für den mount eine eigene Unit-Datei erstellt. Wir starten mit der automatisch erzeugten Unit-Datei:
systemctl cat var-www-data.mount > /etc/systemd/system/var-www-data.mount
Und löschen die Einträge SourcePath
und Documentation
, so dass nur noch das Minimum da steht:
#/etc/systemd/system/var-www-data.mount
[Unit]
Before=remote-fs.target
[Mount]
What=//v6.smb.example.com/WwwDataShare
Where=/var/www/data
Type=cifs
Options=ro,guest,iocharset=utf8
Dann wird der Eintrag aus /etc/fstab
gelöscht und die Unit aktiviert mit:
systemctl enable var-www-data.mount; systemctl daemon-reload
/var/www/data
müsste jetzt sauber ein- und wieder ausgehängt werden können.
Jetzt können wir die Abhängigkeit zu openvpn formulieren:
#/etc/systemd/system/var-www-data.mount
[Unit]
Before=remote-fs.target
After=openvpn@client.service
Requires=openvpn@client.service
systemctl daemon-reload
Funktioniert leider immer noch nicht. Nach diversen Analysen mit journalctl -f
war klar, dass
- die openvpn Unit beim Start behauptet, sie sei “fertig”, obwohl die IPv6 Routen noch nicht korrekt gesetzt sind.
- der umount von /var/www/data beim Stoppen behauptet, er sei fertig, obwohl es noch ausstehende Netzwerk-Kommunikation hat.
Lösung
Zuerst braucht es ein Script, welches die Vorbedingungen beim Starten (ping
) und Stoppen (umount
) prüft:
/etc/openvpn/checks_updown
#!/bin/bash
LOGGER_RED="systemd-cat -t $0 -p err"
LOGGER_BOLD="systemd-cat -t $0 -p notice"
LOGGER_NORM="systemd-cat -t $0 -p info"
case "$1" in
ping)
while true
do
if ping6 -q -c 3 v6.smb.example.com > /dev/null 2>&1
then
echo ping6 ok | $LOGGER_BOLD
exit 0
else
echo "." | $LOGGER_NORM
sleep 4
fi
done
;;
umount)
while true
do
if mount | grep "//v6.smb.example.com/" >/dev/null 2>&1
then
echo ";" | $LOGGER_NORM
sleep 4
else
echo umount ok | $LOGGER_BOLD
exit 0
fi
done
;;
*)
echo "invalid call: $1" | tee | $LOGGER_RED
exit 1
;;
esac
Für dieses Script bauen wir eine eigene Unit:
# /etc/systemd/system/vpnbarrier.service
[Unit]
Requires=openvpn@client.service
After=openvpn@client.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/etc/openvpn/checks_updown ping
ExecStop=/etc/openvpn/checks_updown umount
[Install]
RequiredBy=var-www-data.mount
Das RemainAfterExit=yes
bewirkt (zusammen mit oneshot
), dass ExecStop
erst beim Stop dieser Unit ausgeführt wird. Ohne diese Einstellung würde ExecStop
unmittelbar nach ExecStart
, also noch während des Hochfahrens, ausgeführt.
Abhängigkeiten in der mount
-Unit erweitern:
# /etc/systemd/system/var-www-data.mount
[Unit]
Before=apache2.service
Requires=vpnbarrier.service
After=vpnbarrier.service
[Mount]
...
[Install]
RequiredBy=apache2.service
systemctl enable vpnbarrier.service; systemctl daemon-reload
Erst jetzt funktioniert alles wie es muss:
systemctl stop openvpn@client.service
: Zuerst wird apache2 gestoppt, dann/var/www/data
abgehängt und erst dann das VPN gestoppt.systemctl start apache2.service
: Zuerst wird das VPN gestartet, dann der cifs-mount und schliesslich apache2.- Ein reboot dauert knapp 20 Sekunden.
Bemerkungen
- Die Dokumentation von systemd ist recht gut, zum Beispiel bei digitalocean. Der Schwerpunkt ist aber meistens auf dem Hochfahren und man findet kaum etwas zum Runterfahren.
- Eine etwas andere Diskussion hat gezeigt, dass es auch andere gibt, denen etwas fehlt.
- Die wichtigste Einsicht: Immer sowohl
Requires
als auchAfter
benutzen, nur so kriegt man auch das Runterfahren in den Griff. - Root Cause: Ist das Problem ein generisches systemd-Problem oder ein Bug im openvpn-Paket? Und beim cifs umount?
- Könnte man das Problem noch etwas eleganter Lösen, z.B. indem man auf eine separate Unit
vpnbarrier.service
verzichtet und den “ping”- bzw “umount”-Check durch Einträge in*.conf.d
-Verzeichnisse geeignet einbaut? - Ab und zu war es nicht ausreichend,
systemd daemon-reload
zu machen, erst nach einem Reboot hat alles wie erwartet funktioniert.