Remote-vLLM-Node — Firewall-Setup-Runbook¶
Codifiziert das idempotente iptables-Setup für die Remote-vLLM-Node
192.168.0.223(TODO-Platform-04). Skript-Source:infra/remote-node/docker-firewall.sh+ Service-Templateinfra/remote-node/docker-firewall.service.
Architektur-Kontext¶
- Docker-Container umgehen UFW vollständig — Port-Restriktionen für
Container müssen in der
DOCKER-USER-iptables-Chain durchgesetzt werden - Auf Ubuntu Noble können
ufwundiptables-persistentnicht koexistieren — Persistenz läuft daher über einen Custom-systemd- Oneshot-Service PartOf=docker.serviceist gewollt: bei Docker-Stop wird der Service mitgestoppt, bei Docker-Restart wird er mit-gestartet — die Regeln bleiben mit dem Daemon-Lifecycle synchron- IPv4 vs. IPv6: asymmetrisch. Pro Port eine RETURN-Regel für die ALLOWED_SOURCE_IP plus eine DROP-Catchall-Regel in IPv4. In IPv6 nur DROP — internes LAN-Routing nutzt ausschließlich IPv4, IPv6 hat keinen erlaubten Pfad
Voraussetzungen¶
| Voraussetzung | Verifikation |
|---|---|
| Ubuntu 24.04 (Noble) oder kompatibel | lsb_release -a |
| Docker installiert + lauffähig | systemctl is-active docker → active |
| Container für vLLM (Port 8000) + GPU-Exporter (Port 9835) laufen | docker ps \| grep -E '8000\|9835' |
iptables-persistent nicht installiert |
dpkg -l \| grep iptables-persistent (sollte leer sein) |
| Root-Zugang | sudo -v |
Aufruf-Reihenfolge¶
1. Skript auf die Node kopieren¶
Vom Repo-Root auf der lokalen Workstation:
scp infra/remote-node/docker-firewall.sh \
infra/remote-node/docker-firewall.service \
user@192.168.0.223:/tmp/
Auf der Remote-Node:
sudo install -m 0755 /tmp/docker-firewall.sh /usr/local/sbin/docker-firewall.sh
sudo install -m 0644 /tmp/docker-firewall.service /etc/systemd/system/docker-firewall.service
(Die Service-File-Installation erfolgt im nächsten Schritt durch das
Skript selbst — der explizite install oben ist nur, falls man das
Skript---install nicht nutzen will.)
2. Trockenlauf¶
Erwartete Ausgabe: alle iptables/ip6tables-Aufrufe + systemd-Schritte
werden mit [dry]-Präfix gelistet, kein Live-Touch.
3. Apply¶
Erwartete Ausgabe:
Apply DOCKER-USER-Regeln (ALLOWED=192.168.0.7, PORTS=8000 9835)
Apply abgeschlossen.
Installiere Service-File: /etc/systemd/system/docker-firewall.service
systemctl daemon-reload + enable + start
Bei Re-Run: identische Output-Form, weil das Delete-then-Insert-Pattern die Chain deterministisch in denselben Zustand bringt.
4. Verifikation¶
4a. iptables-Inspection (Standard-Smoke, kein Drittel-Host nötig)¶
Erwartete Reihenfolge (genau dieses Pattern, sonst Drift):
1 ... RETURN tcp -- 192.168.0.7 ... tcp dpt:8000
2 ... DROP tcp -- ... tcp dpt:8000
3 ... RETURN tcp -- 192.168.0.7 ... tcp dpt:9835
4 ... DROP tcp -- ... tcp dpt:9835
Counter-Check (pkts-Spalte):
- RETURN-Counter sollten wachsen mit der Zeit (legitimer Traffic von luki-ai)
- DROP-Counter sollten idealerweise 0 oder sehr niedrig sein (kein unauthorisierter Zugriff aus dem LAN)
Erwartung: zwei DROP-Regeln (Port 8000, 9835). Keine RETURN-Regel — IPv6 ist asymmetrisch.
4b. Service-Status¶
Erwartet: Active: active (exited) (Type=oneshot mit RemainAfterExit).
Erwartet: enabled.
4c. Funktional-Test von luki-ai (192.168.0.7)¶
# Soll 200 (oder vLLM-Health-Format) liefern:
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://192.168.0.223:8000/health
# Soll 200 (Prometheus-Metrics) liefern:
curl -s -o /dev/null -w "%{http_code}\n" --max-time 3 http://192.168.0.223:9835/metrics
4d. Optional — Container-Negative-Test (ohne Drittel-Host)¶
Falls kein Drittel-LAN-Host für nmap verfügbar ist, kann man von der
Remote-Node selbst aus über einen Container im Default-Bridge-Network
testen, dass die DROP-Regeln greifen:
# Auf 192.168.0.223:
docker run --rm --network bridge alpine sh -c '
apk add --no-cache curl >/dev/null 2>&1
echo "From bridge-IP (sollte gefiltert sein, weil != 192.168.0.7):"
curl -s -o /dev/null -w " %{http_code}\n" --max-time 3 http://192.168.0.223:8000/health || echo " timeout/blocked (erwartet)"
'
Erwartung: Connection-Timeout (nicht 200), weil die Container-Bridge-IP
kein 192.168.0.7 ist.
Fehlerbehebung¶
| Symptom | Ursache | Fix |
|---|---|---|
ERROR: DOCKER-USER-Chain existiert nicht |
Docker wurde noch nie gestartet | sudo systemctl start docker + Skript erneut |
WARN: Service-File existiert mit abweichendem Inhalt |
Manuelle Edits an /etc/systemd/system/docker-firewall.service driften vom Repo-Template |
Diff prüfen; falls Repo-Template korrekt ist, Live-File durch sudo cp /usr/local/sbin/... ersetzen, dann --install erneut |
| Regeln verschwinden nach Reboot | Service nicht enabled, oder Docker startet nach dem Service | systemctl is-enabled docker-firewall.service prüfen; bei „disabled" systemctl enable |
curl von luki-ai schlägt fehl |
DOCKER-USER-Chain leer (Skript nicht apply), oder Container nicht aktiv | iptables -L DOCKER-USER -n -v + docker ps prüfen |
| Counter steigt nicht trotz erfolgreichem Traffic | Reihenfolge der Regeln falsch (DROP vor RETURN) | Skript erneut: das Delete-then-Insert-Pattern stellt die korrekte Reihenfolge wieder her |
Rollback¶
Effekt:
- Alle 6 IPv4 + 2 IPv6 Regeln werden aus DOCKER-USER gelöscht
- docker-firewall.service wird gestoppt + disabled
- Service-File wird entfernt
- systemctl daemon-reload
Verifikation:
sudo iptables -L DOCKER-USER -n -v
sudo ip6tables -L DOCKER-USER -n -v
systemctl is-enabled docker-firewall.service # Erwartet: disabled / not-found
Zweite Remote-Node oder Anpassung¶
Default-Konfiguration im Skript:
Variante A — Skript-Edit: Variablen am Skript-Anfang anpassen, dann deployen wie oben.
Variante B — Env-Override (für Tests/temporäre Anpassung):
sudo PORTS_STR="8000 9835 9090" \
ALLOWED_SOURCE_IP="10.0.5.42" \
/usr/local/sbin/docker-firewall.sh --install
Das überschreibt die Defaults nur für diesen Aufruf. Für persistente
Änderungen (sonst zieht der Service beim nächsten Boot wieder die
Defaults) das Skript editieren oder ein Drop-In Environment=-File für
den Service ergänzen.
Referenzen¶
- TODO-Platform-04 Archiv:
offene-todos.md - Routing-Page §4:
operations/routing-and-endpoints.md