Der Tunnel haengt nicht mehr an einem huebschen Namen und nicht an einem Arbeiter
Zwei Ursachen, eine davon habe ich mit v1.4.2 selbst gelegt. 1. DER NETZ-ALIAS. Damit in docker/nginx/default.conf `terminal:8082` stehen konnte statt des Namens des Nachbarcontainers, bekam queue-provisioning einen Eintrag unter `networks: aliases:`. Das ist Teil seiner NETZKONFIGURATION — und Compose baut einen Container neu, sobald die sich aendert. Neu gebaut heisst neue Adresse im Compose-Netz, heisst neu geschriebene Weiterleitungsregeln fuer den veroeffentlichten UDP-Port 51820. Auf dem liegt jede bestehende WireGuard-Sitzung. Der Preis fuer einen sprechenderen Namen in einer Konfigurationszeile war also ein Abriss saemtlicher Tunnel beim Ausrollen — der des Betreibers am Telefon wie der jedes Hosts. Alias entfernt, nginx spricht die Bruecke unter `queue-provisioning:8082` an. Nachgemessen: danach laesst `docker compose up -d` den Tunnel-Container unangetastet, und nginx erreicht die Bruecke weiterhin (426 statt 502). 2. DER ARBEITER ALS PROZESS 1. wg0 stand im Namensraum von queue-provisioning, und dessen Prozess 1 war `exec php artisan queue:work provisioning`. Endete der Arbeiter, endete der Container — und mit ihm der Namensraum und jede Sitzung darin. Ein Arbeiter endet oefter, als man denkt: `--timeout=2100` beendet ihn bei einem langen Provisionierungs-Schritt, ein fataler Fehler beendet ihn, ein Speicherlimit beendet ihn, `queue:restart` beendet ihn absichtlich. Aus jedem dieser vier Faelle wurde bisher "das VPN ist unzuverlaessig". Der Rumpf liegt jetzt in docker/provisioning-worker.sh: wg0 einmal hochziehen, danach den Arbeiter in einer Schleife halten, SIGTERM sauber weiterreichen. Nachgemessen: Arbeiter getoetet -> Container hat NULL Neustarts, wg0 steht weiter, Arbeiter ist von allein wieder da. Beide Male bleibt der Netz-Namensraum stehen. Damit verschwindet nebenbei auch der Grund, aus dem update.sh die Bruecke hinterher neu starten musste — die Zeile bleibt trotzdem, sie kostet nichts und traegt den Fall, dass der Container doch einmal neu gebaut wird. NICHT GETAGGT, mit Absicht: diese Aenderung fasst genau das an, was den Zugang des Betreibers traegt. Sie gehoert ausgerollt, wenn jemand davorsitzt und die Konsole des Anbieters als Rueckfahrkarte hat — nicht unbeaufsichtigt vom Update-Agenten, waehrend der Betreiber unterwegs ist. 2508 Tests gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>main
parent
7224825db1
commit
2b79a6a2ba
|
|
@ -91,7 +91,11 @@ services:
|
|||
# renders no mail, so it is not the one that was writing compiled views —
|
||||
# and update.sh normalises ownership at the end of every deployment, which
|
||||
# heals it if it ever does.
|
||||
command: sh -c 'wg-quick up wg0 2>/dev/null || true; exec php artisan queue:work provisioning --queue=provisioning --tries=1 --timeout=2100 --sleep=3'
|
||||
# Der Rumpf steht in einer Datei, nicht hier: er haelt den Arbeiter in einer
|
||||
# Schleife, damit ein Zeitueberlauf oder ein fataler Fehler nicht mehr den
|
||||
# ganzen Container — und mit ihm wg0 und jede WireGuard-Sitzung — mitnimmt.
|
||||
# Die Begruendung steht ausfuehrlich in der Datei.
|
||||
command: sh /var/www/html/docker/provisioning-worker.sh
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
|
|
@ -100,21 +104,6 @@ services:
|
|||
- net.ipv4.ip_forward=1
|
||||
ports:
|
||||
- "${WG_HUB_PORT:-51820}:51820/udp"
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
# Der Name, unter dem nginx die Terminal-Bruecke erreicht. Sie teilt
|
||||
# sich den Netz-Namensraum DIESES Containers (`network_mode` unten bei
|
||||
# `terminal`) und hat deshalb keine eigene Adresse im Compose-Netz und
|
||||
# keinen eigenen DNS-Eintrag — ein Container ohne eigenes Netz bekommt
|
||||
# keinen. Der Alias gibt der Adresse den Namen des Dienstes, der dort
|
||||
# antwortet, statt in docker/nginx/default.conf den Namen des
|
||||
# Provisionierungs-Workers stehen zu haben.
|
||||
#
|
||||
# Faellt `network_mode` bei `terminal` je weg, muss dieser Alias mit
|
||||
# weg: sonst zeigen zwei DNS-Eintraege auf denselben Namen und nur
|
||||
# einer davon hat einen Zuhoerer.
|
||||
- terminal
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
- wireguard:/etc/wireguard
|
||||
|
|
|
|||
|
|
@ -77,7 +77,20 @@ server {
|
|||
# eingebauter DNS) wird daraus ein 502 an genau dieser Stelle, waehrend
|
||||
# alles andere weiterlaeuft.
|
||||
resolver 127.0.0.11 valid=10s ipv6=off;
|
||||
set $terminal_upstream terminal:8082;
|
||||
# Der Name des Provisionierungs-Workers, nicht ein huebscherer Alias.
|
||||
#
|
||||
# Hier stand `terminal:8082`, und dieser Alias war ein Eintrag unter
|
||||
# `networks:` von queue-provisioning. Genau das hat den Tunnel-Container
|
||||
# beim ersten Ausrollen NEU BAUEN lassen — Compose baut einen Container
|
||||
# neu, sobald sich seine Netzkonfiguration aendert. Ein neu gebauter
|
||||
# Container bekommt eine neue Adresse im Compose-Netz, und damit werden
|
||||
# die veroeffentlichten Regeln fuer UDP 51820 neu geschrieben, auf denen
|
||||
# JEDER bestehende WireGuard-Fluss liegt.
|
||||
#
|
||||
# Der Preis war also: ein Wort in dieser Zeile gegen einen Abriss aller
|
||||
# Tunnel. Die Bruecke teilt sich ohnehin den Namensraum, `:8082` ist ihr
|
||||
# Port darin — der Name daneben darf ruhig der des Nachbarn sein.
|
||||
set $terminal_upstream queue-provisioning:8082;
|
||||
|
||||
proxy_pass http://$terminal_upstream;
|
||||
proxy_http_version 1.1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
#!/bin/sh
|
||||
# Der Tunnel und der Arbeiter, die sich einen Container teilen — aber nicht
|
||||
# mehr ein Schicksal.
|
||||
#
|
||||
# Bis hierher war `php artisan queue:work` der Prozess 1 dieses Containers.
|
||||
# Damit hing wg0 an seinem Leben: jedes Mal, wenn der Arbeiter endete, endete
|
||||
# der Container, und mit ihm der Netz-Namensraum, in dem der Tunnel steht.
|
||||
#
|
||||
# Ein Arbeiter endet oefter, als man denkt. `--timeout=2100` beendet ihn, wenn
|
||||
# ein Provisionierungs-Schritt zu lange braucht. Ein fataler Fehler beendet ihn.
|
||||
# Ein ueberschrittenes Speicherlimit beendet ihn. `queue:restart` beendet ihn
|
||||
# absichtlich. In allen vier Faellen fielen bisher saemtliche WireGuard-Sitzungen
|
||||
# mit — die des Betreibers am Telefon genauso wie die jedes Hosts. Von aussen sah
|
||||
# das aus wie „das VPN ist unzuverlaessig", und die Ursache stand in einer Zeile
|
||||
# docker-compose.yml, die von Warteschlangen handelt.
|
||||
#
|
||||
# Deshalb: wg0 einmal hochziehen, und danach den Arbeiter in einer Schleife
|
||||
# halten. Faellt er, kommt er wieder — der Tunnel merkt nichts davon.
|
||||
#
|
||||
# Der Container endet dann nur noch, wenn Docker ihn beendet. Das ist Absicht:
|
||||
# ein Arbeiter, der nicht startet, ist ein Fall fuers Protokoll, kein Grund,
|
||||
# jedem Host die Verbindung abzuschneiden.
|
||||
set -u
|
||||
|
||||
wg-quick up wg0 2>/dev/null || true
|
||||
|
||||
# SIGTERM sauber weiterreichen: sonst wartet `docker compose stop` zehn Sekunden
|
||||
# und schlaegt dann mit SIGKILL zu — mitten in einem Provisionierungs-Schritt.
|
||||
child=''
|
||||
stop() {
|
||||
[ -n "$child" ] && kill -TERM "$child" 2>/dev/null
|
||||
exit 0
|
||||
}
|
||||
trap stop TERM INT
|
||||
|
||||
while true; do
|
||||
php artisan queue:work provisioning \
|
||||
--queue=provisioning \
|
||||
--tries=1 \
|
||||
--timeout=2100 \
|
||||
--sleep=3 &
|
||||
child=$!
|
||||
wait "$child"
|
||||
code=$?
|
||||
|
||||
# Ins Protokoll, nicht ins Nichts: ein Arbeiter, der staendig neu startet,
|
||||
# ist ein eigenes Problem — es soll nur nicht mehr das Problem des Tunnels
|
||||
# sein.
|
||||
echo "[provisioning] Arbeiter beendet (Code ${code}) — wg0 bleibt oben, Neustart in 2 s" >&2
|
||||
sleep 2
|
||||
done
|
||||
|
|
@ -197,8 +197,56 @@ it('laesst die Terminal-Bruecke dort stehen, wo ein Host ueberhaupt erreichbar i
|
|||
// — Docker lehnt das Compose-File dann komplett ab.
|
||||
->and($block)->not->toContain('ports:');
|
||||
|
||||
// Und der Name, unter dem nginx ihn anspricht, muss der Alias des
|
||||
// Namensraum-Eigentümers sein: ein Container ohne eigenes Netz hat keinen
|
||||
// Und nginx muss ihn unter dem Namen des Namensraum-Eigentümers ansprechen,
|
||||
// nicht unter einem eigenen: ein Container ohne eigenes Netz hat keinen
|
||||
// eigenen DNS-Eintrag.
|
||||
expect($compose)->toContain(' - terminal');
|
||||
//
|
||||
// Hier stand einmal ein Netz-Alias `terminal` auf queue-provisioning, damit
|
||||
// in der nginx-Konfiguration ein sprechender Name steht. Der Preis dafür war
|
||||
// hoch und fiel erst im Betrieb auf: ein Eintrag unter `networks:` ist Teil
|
||||
// der Netzkonfiguration eines Dienstes, und Compose BAUT einen Container NEU,
|
||||
// sobald die sich ändert. Neu gebaut heißt neue Adresse im Compose-Netz,
|
||||
// heißt neu geschriebene Weiterleitungsregeln für den veröffentlichten
|
||||
// UDP-Port 51820 — auf dem jede bestehende WireGuard-Sitzung liegt. Ein
|
||||
// hübscherer Name in einer Konfigurationszeile hat sämtliche Tunnel gekostet.
|
||||
//
|
||||
// Deshalb: kein Alias. Der Dienstname des Nachbarn tut es auch.
|
||||
expect($compose)->not->toContain('aliases:');
|
||||
|
||||
$hub = preg_split('/^ queue-provisioning:$/m', $compose)[1] ?? '';
|
||||
$hub = preg_split('/^ \S/m', $hub)[0] ?? '';
|
||||
expect($hub)->not->toContain('networks:');
|
||||
|
||||
expect(File::get(base_path('docker/nginx/default.conf')))
|
||||
->toContain('set $terminal_upstream queue-provisioning:8082;');
|
||||
});
|
||||
|
||||
it('haengt den Tunnel nicht an das Leben eines Warteschlangen-Arbeiters', function () {
|
||||
// Der Fall, der den Betreiber unterwegs erwischt hat: wg0 stand im
|
||||
// Namensraum von queue-provisioning, und dessen Prozess 1 war
|
||||
// `php artisan queue:work`. Endete der Arbeiter — Zeitüberlauf nach 2100
|
||||
// Sekunden, fataler Fehler, Speicherlimit, `queue:restart` —, endete der
|
||||
// Container, und mit ihm fielen ALLE WireGuard-Sitzungen: die des Betreibers
|
||||
// am Telefon wie die jedes Hosts. Von außen las sich das als „das VPN ist
|
||||
// unzuverlässig"; die Ursache stand in einer Zeile, die von Warteschlangen
|
||||
// handelt.
|
||||
$compose = File::get(base_path('docker-compose.yml'));
|
||||
|
||||
$hub = preg_split('/^ queue-provisioning:$/m', $compose)[1] ?? '';
|
||||
$hub = preg_split('/^ \S/m', $hub)[0] ?? '';
|
||||
|
||||
// Kein `exec` auf den Arbeiter — das ist genau das Ersetzen von Prozess 1,
|
||||
// das den Container am Arbeiter sterben ließ.
|
||||
expect($hub)->not->toContain('exec php artisan queue:work')
|
||||
->and($hub)->toContain('docker/provisioning-worker.sh');
|
||||
|
||||
$runner = File::get(base_path('docker/provisioning-worker.sh'));
|
||||
|
||||
expect($runner)->toContain('wg-quick up wg0')
|
||||
// Die Schleife ist der ganze Punkt: fällt der Arbeiter, kommt er wieder,
|
||||
// ohne dass der Namensraum stirbt.
|
||||
->and($runner)->toContain('while true')
|
||||
// Und SIGTERM wird weitergereicht, sonst wartet `docker compose stop`
|
||||
// zehn Sekunden und schlägt dann mitten in einem Schritt mit SIGKILL zu.
|
||||
->and($runner)->toContain('trap stop TERM INT');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue