Files
bf-infra/docs/nas-vlan20/RUNBOOK.md

5.0 KiB

NAS (DS923) → VLAN20 Docker-Migration — Runbook

Stand: 2026-07-09. Ziel: NAS-NIC ist auf VLAN20; ein Docker-Macvlan vlan20 (10.40.20.0/24, gw .1, parent ovs_eth0), alle Container darauf, default_external (10.40.41.0/24) weg. Mapping-Regel: letztes Oktett behalten, Subnetz .41→.20.

Root-Cause der Dauer-Fehler (diagnostiziert)

  1. vlan20 war ein Config-Only-Macvlan (ConfigOnly:true, Driver:null) — Portainers zweistufiger Assistent, nur Schritt 1. An Config-Only-Netze kann Docker keine Container hängen → jeder Container auf vlan20 blieb created/exited. (gelöscht am 2026-07-09)
  2. Macvlan belegt seinen Parent (ovs_eth0) exklusiv → vlan20 kann erst existieren, wenn default_external weg ist. Deshalb ist „alle LANs raus" technisch zwingend.
  3. default_internal (172.18er Bridge) existiert nicht mehr (404); der Range ist von gitea_default (172.18.0.0/16) belegt. Darum sind Multi-Netz-Stacks doppelt kaputt. → Lösung „nur vlan20": interne Bridge wird nicht neu gebaut, jeder Service nur auf vlan20 (Container am selben Macvlan erreichen sich gegenseitig; DB-/Sidecar-Refs waren fast alle auskommentiert).

IP-Mapping (default_external .41.X → vlan20 .20.X)

Container Stack (ep14) alt neu
omada-controller omada (198) 10.40.41.175 10.40.20.175
prometheus oc-stack (112) .41.151 .20.151
grafana oc-stack (112) .41.109 .20.109
alertmanager oc-stack (112) .41.153 .20.153
node-exporter oc-stack (112) .41.154 .20.154
iob_slave iob_slave (64) .41.33 .20.33
vaultwarden vaultwarden (73) .41.127 .20.127
oc-tzcharlie-gateway oc-charlie (158) .41.162 .20.162
oc-johnny2-gateway oc-johnny2 (150) .41.161 .20.161
oc-qdrant-central oc-central (160) .41.159 .20.159
dashboard-web nginx (101) .41.148 .20.148
hms-mqtt default (72) .41.124 .20.124
gitea gitea (111) .41.157 .20.157
drawio-server standalone .41/.20.139 .20.139
pve-mcp standalone .41.173 .20.173

Unangetastet: cloudflared (host-Netz), portainer_agent (bridge). Transformierte Compose-Files: docs/nas-vlan20/stacks/*.yml (Netz-Block auf vlan20, IPs .41→.20, dns:→10.40.20.1). Standalone-Specs: docs/nas-vlan20/inspect_*.json.

Phase A — Netz-Cutover (irreversibel für laufende Container-States; Volumes bleiben)

Container-Prozesse werden neu gestartet, Volumes werden NICHT angefasst (v=false). Skript: docs/nas-vlan20/phaseA-...py (bzw. /tmp/nasmig/phaseA.py). Es:

  1. entfernt alle Container auf default_external + die 2 auf dem toten vlan20,
  2. löscht default_external,
  3. legt vlan20 an: -d macvlan --subnet 10.40.20.0/24 --gateway 10.40.20.1 -o parent=ovs_eth0.

Manuell (falls per Hand): in Portainer → NAS(ep14) → Networks → default_external: alle Container disconnecten/stoppen → Netz löschen → „Add network": macvlan, parent ovs_eth0, Subnet 10.40.20.0/24, GW 10.40.20.1, Name vlan20.

Phase B — Redeploy (macht Christoph selbst in Portainer)

Je Stack den Inhalt aus docs/nas-vlan20/stacks/<id>-*.yml einspielen → Update/Redeploy. Standalone neu bauen (drawio-server, pve-mcp) siehe inspect_*.json (image/env/binds), Netz vlan20.

Post-Migration (erst NACH Redeploy, wenn Container real auf .20.x)

  • ap1 DNS (bf.lan A-Records): alle gewanderten Hosts .41.X→.20.X nachziehen (MikroMCP manage_dns_entry).
  • CF-Tunnel-Ingress (70d6a4cf…, remote-managed): NAS-Origins .41→.20 repointen — hass(→schon .20.105?), gitea .41.157→.20.157, grafana .109, pve-mcp .173, vw .127, ds-bf, portainer … SSoT: docs/cf-tunnel-mapping.md. Vorher pve→VLAN20-Reachability smoke-testen.
  • omada-mcp (~/.claude.json + Stack ep2/ep14): OMADA_URL .41.175→.20.175.
  • ⚠️ omada-controller Inform-Adresse: adoptierter Omada-Switch sw2 kennt Controller unter .41.175 → nach Umzug im Controller die Controller/Inform-Adresse auf .20.175 setzen, sonst sw2 „Disconnected". UI wandert auf https://10.40.20.175:8043.
  • CLAUDE.md / memory: .41→.20 Referenzen nachziehen.

CF-Access-Profil für Claude (Thread „ich komm schwer durch den Tunnel")

  • *.emsenhuber.net = CF-Access-App. Policies: christoph (E-Mail-Login), bf4 (non_identity, IP-Bypass = BF-Egress → darum on-LAN offen), claude-mcp (allow, service_token) = mein Profil, policy-seitig bereits vorhanden.
  • Fehlt nur das Secret des claude-mcp-Tokens (liegt nirgends lokal). Ohne CF-Access-Client-Id/Secret-Header komme ich nur über den BF-IP-Bypass durch.
  • Offen (braucht Freigabe): Service-Token claude-mcp rotieren → neues Secret → ablegen in mcp/cloudflare/.env (chmod 600) als CF_ACCESS_CLIENT_ID / CF_ACCESS_CLIENT_SECRET. Rotieren invalidiert das alte Secret (das aber ohnehin verloren ist).
  • hass, vw-App = bypass everyone → hass/vw sind öffentlich (nur HA-Login davor). Separat bewerten.