Zum Inhalt

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-Template infra/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 ufw und iptables-persistent nicht koexistieren — Persistenz läuft daher über einen Custom-systemd- Oneshot-Service
  • PartOf=docker.service ist 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 dockeractive
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

sudo /usr/local/sbin/docker-firewall.sh --install --dry-run

Erwartete Ausgabe: alle iptables/ip6tables-Aufrufe + systemd-Schritte werden mit [dry]-Präfix gelistet, kein Live-Touch.

3. Apply

sudo /usr/local/sbin/docker-firewall.sh --install

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)

sudo iptables -L DOCKER-USER -n -v --line-numbers

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)
sudo ip6tables -L DOCKER-USER -n -v --line-numbers

Erwartung: zwei DROP-Regeln (Port 8000, 9835). Keine RETURN-Regel — IPv6 ist asymmetrisch.

4b. Service-Status

systemctl status docker-firewall.service --no-pager

Erwartet: Active: active (exited) (Type=oneshot mit RemainAfterExit).

systemctl is-enabled docker-firewall.service

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

sudo /usr/local/sbin/docker-firewall.sh --remove

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:

ALLOWED_SOURCE_IP="192.168.0.7"
PORTS_STR="8000 9835"

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