From 15b351fa2565f635d9c90aed3e32bf9f59fdf661 Mon Sep 17 00:00:00 2001 From: Micha Date: Tue, 16 Jun 2026 10:51:16 +0200 Subject: [PATCH] @ fix(immich): ML-Egress-Netz fuer Modell-Download immich_machine_learning hing nur in immich_default (internal: true) -> kein DNS/Egress, /cache leer, Logs "Failed to resolve huggingface.co". Container healthy, aber Smart Search + Gesichtserkennung faktisch tot. Fix: dediziertes nicht-internes Netz immich_egress nur an ML + explizites dns 1.1.1.1/8.8.8.8 (DNS-Regel docs/WORKFLOW.md). DB/Redis bleiben in immich_default isoliert (P3). Bewusst nicht frontend_net (unauth. ML-API). Doku: Architektur-Zielbild (Netze + ML-Zeile), SERVICE_CATALOG, DECISIONS-ADR. Co-Authored-By: Claude Opus 4.8 @ --- HOMELAB_ARCHITECTURE_MASTER_V2.md | 5 +++-- apps/immich/docker-compose.yml | 16 ++++++++++++++++ docs/DECISIONS.md | 28 ++++++++++++++++++++++++++++ docs/SERVICE_CATALOG.md | 4 ++-- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/HOMELAB_ARCHITECTURE_MASTER_V2.md b/HOMELAB_ARCHITECTURE_MASTER_V2.md index e2dde20..a60812f 100644 --- a/HOMELAB_ARCHITECTURE_MASTER_V2.md +++ b/HOMELAB_ARCHITECTURE_MASTER_V2.md @@ -88,7 +88,8 @@ Jeder produktive Container nutzt `restart: unless-stopped`, außer eine Ausnahme | `backend_net` | bridge, `internal: true` | interne App-/DB-/Cache-Kommunikation | Standard | | `dns_net` | bridge | Resolver-Schicht: AdGuard Home + Unbound | bleibt | | `mealie_internal` | bridge, `internal: true` | internes Netz nur für `mealie` + `mealie-postgres` | ✅ umgesetzt | -| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz | ✅ umgesetzt | +| `immich_default` | Compose-intern, `internal: true` | internes Immich-Netz (Server, Postgres, Redis, ML) | ✅ umgesetzt | +| `immich_egress` | Compose-intern, bridge (nicht `internal`) | Outbound-only fuer `immich_machine_learning` (Modell-Download huggingface) | ✅ umgesetzt | | `nextcloud_internal` | bridge, `internal: true` | internes Netz nur fuer `nextcloud` + `nextcloud-postgres` + `nextcloud-redis` | ✅ vorbereitet | | `monitoring_net` | Compose-intern, bridge | zentraler Observability-Stack fuer Prometheus, Loki, Grafana, Promtail, Exporter und InfluxDB | Zielzustand | | `monitoring_influx_lan` | Compose-intern, bridge | nicht-oeffentliches Zusatznetz nur fuer Docker Host-Port-Publishing von InfluxDB 8181 | Zielzustand | @@ -276,7 +277,7 @@ Legende Status: | `ntfy` | ✅ | `frontend_net` | Traefik | aktiv via `ntfy.kaleschke.info`, Git-Stack | — | | `gitea` | ✅ | `frontend_net` | Traefik + SSH-Port 222 | Web via Traefik, SSH direkt gebunden | — | | `immich_server` | ✅ | `immich_default`, `frontend_net` | Traefik | aktiv via `immich.kaleschke.info` | — | -| `immich_machine_learning` | ✅ | `immich_default` | intern | bleibt intern | — | +| `immich_machine_learning` | ✅ | `immich_default`, `immich_egress` | intern (keine Traefik-Route) | `immich_default` fuer Server-Erreichbarkeit + dediziertes `immich_egress` (nicht-internal) fuer einmaligen Modell-Download (CLIP/buffalo_l) nach `model-cache`; bewusst nicht `frontend_net`, da unauth. ML-API | — | | `nextcloud` | ✅ | `frontend_net`, `nextcloud_internal` | Traefik | aktiv via `cloud.kaleschke.info`, nativer Nextcloud-Login, WebDAV/CardDAV faehig | CalDAV/CardDAV-Redirect via Traefik-Labels | | `homeassistant` | ✅ | `frontend_net`, `smarthome_net` | Traefik via `home.kaleschke.info`, native HA-Auth | Home Assistant Container im GitOps-Stack `smart-home/`; kein HAOS, kein Supervised; Fach-YAML kommt aus `smart-home-kalli`, `.storage` bleibt in `/mnt/user/appdata/homeassistant`; Komodo-Stack und Gitea-Webhook aktiv; HA-native Backup-Erzeugung, Restore-Probe, HA-MQTT-Integration, SolarEdge Local und Energy Dashboard am 2026-06-13 erfolgreich | Tibber, Energie-Kosten, spaeter Energie-Automationen | | `plex` | ✅ | `host` | Traefik via `plex.kaleschke.info` + Plex native Auth; LAN direkt `:32400` | Compose-Stack unter `host-services/plex/`; Host-Netz bleibt fuer Discovery / Plex GDM dokumentierte Ausnahme; Traefik routet per File-Provider-Ausnahme auf `http://192.168.178.58:32400`, weil Docker-Labels Host-Netz-Container aus Traefik heraus auf `127.0.0.1` routen wuerden; kein direkter WAN-Port 32400 und Plex Remote Access bleibt aus; Server geclaimt von `Xeridos`; Smart-TVs (Schlafzimmer, Wohnzimmer) ueber WLAN-LAN per mDNS | — | diff --git a/apps/immich/docker-compose.yml b/apps/immich/docker-compose.yml index 33c6b25..bb91d6c 100644 --- a/apps/immich/docker-compose.yml +++ b/apps/immich/docker-compose.yml @@ -48,7 +48,18 @@ services: volumes: - model-cache:/cache networks: + # immich_default (internal) = Erreichbarkeit durch immich-server. + # immich_egress (nicht-internal) = Outbound zu huggingface, damit ML die + # Modelle (CLIP ViT-B-32, buffalo_l) einmalig nach model-cache laedt. + # Ohne dieses Netz scheitert der Modell-Download an der DNS-Aufloesung + # (immich_default ist internal: true) -> Smart Search/Gesichtserkennung tot. - immich_default + - immich_egress + dns: + # Egress-Netz braucht externe Aufloesung (huggingface.co); explizit nach + # docs/WORKFLOW.md "DNS-Regeln fuer Container", analog traefik/ddns-updater. + - 1.1.1.1 + - 8.8.8.8 security_opt: - no-new-privileges:true @@ -86,5 +97,10 @@ networks: name: immich_default internal: true driver: bridge + immich_egress: + # Bewusst NICHT internal: nur fuer den ML-Modell-Download (Outbound). + # Nur immich_machine_learning haengt hier; DB/Redis bleiben in immich_default. + name: immich_egress + driver: bridge frontend_net: external: true diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index c8db351..7aebdba 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -11,6 +11,34 @@ in `HOMELAB_ARCHITECTURE_MASTER_V2.md` §13, `docs/MASTER_TODO.md` (Geparkt), --- +## 2026-06-16 - Immich ML bekommt dediziertes Egress-Netz (Modell-Download) + +**Entscheidung:** `immich_machine_learning` haengt zusaetzlich zu `immich_default` +(`internal: true`) in einem neuen, bewusst **nicht**-internen Compose-Netz +`immich_egress` und bekommt explizit `dns: 1.1.1.1/8.8.8.8`. Damit kann ML die +Modelle (CLIP `ViT-B-32__openai`, Gesichtserkennung `buffalo_l`) einmalig von +huggingface nach `model-cache` laden. DB und Redis bleiben unveraendert in +`immich_default` isoliert. + +**Kontext:** Am 2026-06-16 live gemessen: ML lief zwar `healthy`, aber +`/cache` war 0 B und die Logs zeigten in Schleife +`NameResolutionError: Failed to resolve 'huggingface.co'`. Ursache: ML hing nur +im `internal: true`-Netz `immich_default` ohne Egress/DNS. Folge: Smart Search +und Gesichtserkennung waren faktisch tot, trotz gesundem Container (Healthcheck +prueft nur den HTTP-Endpoint, nicht die Modellverfuegbarkeit). Das Zielbild §7.4 +sagte vorher "bleibt intern" und widersprach damit dem eigenen Einordnungsschema +§6 Schritt 2 ("braucht Internet -> nicht-internal"). + +**Alternativen:** (a) ML an `frontend_net` haengen — verworfen, weil die +unauthentifizierte ML-API dann im geteilten Web-Netz erreichbar waere. +(b) `immich_default` auf nicht-internal umstellen — verworfen, weil dann auch +Postgres/Redis Outbound-Internet bekaemen (Least-Privilege-Verlust). Das +dediziertes Egress-Netz isoliert den Internetbedarf auf genau ML. + +**Review-Trigger:** Immich-Update, das ML-Modelle anders bezieht; Wunsch nach +vollstaendigem Air-Gap (dann Modelle offline vorseeden statt Egress); oder wenn +ML weitere Modelle braucht (Egress bleibt dafuer noetig). + ## 2026-06-13 - Wetter-/Langzeitarchiv: HA schreibt nach InfluxDB 3 Core **Entscheidung:** Home Assistant schreibt die Ecowitt-Sensoren diff --git a/docs/SERVICE_CATALOG.md b/docs/SERVICE_CATALOG.md index 5328b05..9779ef2 100644 --- a/docs/SERVICE_CATALOG.md +++ b/docs/SERVICE_CATALOG.md @@ -12,7 +12,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und |---|---|---|---|---|---|---|---|---| | `traefik` | zentraler Reverse Proxy, TLS, Docker-Label-Routing | `traefik/docker-compose.yml`, `traefik/dynamic/*` | `https://traefik.kaleschke.info` | Docker socket, Cloudflare DNS API, `frontend_net`, `backend_net` | `/mnt/user/appdata/traefik/dynamic`, `/mnt/user/appdata/traefik/letsencrypt` | Tier 1, Share/Borg | ja, eigene Dashboard-Route mit Authelia | Host-Ports 80/443 sind zentrale Ausnahme; dynamic configs werden nicht automatisch von Komodo deployed | | `adguard` | DNS-Server / LAN DNS | `host-services/Adguard/docker-compose.yml` | LAN-Port `53`, Admin `100.80.98.33:8082` | `dns_net`, `frontend_net`, Unbound | `/mnt/user/appdata/adguard/conf`, `/mnt/user/appdata/adguard/work` | Tier 1, config relevant | nein | Direkter DNS-Port 53 bleibt; Admin-Port ist bewusst ohne Traefik/2FA, aber auf Tailscale-IP begrenzt (Operator-Entscheidung 2026-05-26) | -| `unbound` | Upstream DNS Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert | +| `unbound` | DNSSEC-validierender Forwarding-Resolver fuer AdGuard | `apps/unbound/docker-compose.yml` | intern | `dns_net` | `/mnt/user/appdata/unbound/config` | rebuildbar / config relevant | nein | intern isoliert; forwardet per DoT zu Cloudflare, keine Root-Rekursion | | `tailscale` | VPN/Remote-Zugang, Subnet-Router | **Natives Unraid-Plugin** `tailscale.plg` (nicht repo-/Komodo-verwaltet) | Tailscale | Host-Netz (`tailscale1`) | `/boot/config/plugins/tailscale/state` (im Flash-Backup) | Tier 1, State relevant | nein | Subnet-Router `192.168.178.0/24`; redundanter Docker-Stack `host-services/tailscale/` am 2026-06-06 entfernt | | `gitea` | Git-Server / origin fuer GitOps | `core/gitea/docker-compose.yml` | `https://git.kaleschke.info`, SSH `222` | Traefik, `frontend_net`, externe DNS-Resolver fuer GitHub-Push-Mirror | `/mnt/user/services/gitea/data` | Tier 1, `gitea.sqlite.dump` + Share; privater GitHub-Push-Mirror fuer Repo-Bootstrap | ja | SSH-Port 222 direkte Host-Port-Ausnahme; Push-Mirror nach `michaelkaleschke-spec/homelab-infra` reduziert das DR-Bootstrap-Risiko | @@ -40,7 +40,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und | `immich_server` | Foto-/Video-App | `apps/immich/docker-compose.yml` | `https://immich.kaleschke.info` | Immich Postgres, Immich Redis, ML, Traefik | `/mnt/user/photos/immich`, `/mnt/user/photos/family_archive` | Tier 2, Borg + `immich.dump` | ja | native App-Auth; externes Fotoarchiv gemountet | | `immich_postgres` | Immich-Datenbank | `apps/immich/docker-compose.yml` | intern | `immich_default` | `/mnt/user/appdata/immich_postgres_vectorchord`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/immich-postgres-pgvecto-rs`, `immich_postgres_password.txt` | Dump `immich.dump`; Restore braucht ein Image mit VectorChord/pgvector | nein | PG14 bleibt bewusst; Immich-DB-Image `ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0`; nie ins `frontend_net` | | `immich_redis` | Immich Cache | `apps/immich/docker-compose.yml` | intern | `immich_default` | kein kritischer Pfad dokumentiert | rebuildbar | nein | Redis 8.8; Architektur nennt anonymes Volume -> named volume als offenes Thema | -| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default` | `model-cache` | rebuildbar | nein | intern-only | +| `immich_machine_learning` | Immich ML | `apps/immich/docker-compose.yml` | intern | `immich_default`, `immich_egress` | `model-cache` | rebuildbar | nein | keine Traefik-Route; `immich_egress` (nicht-internal) nur fuer Modell-Download zu huggingface, sonst scheitert Smart Search/Gesichtserkennung an DNS | | `mealie` | Rezeptverwaltung | `apps/mealie/docker-compose.yml` | `https://mealie.kaleschke.info` | `mealie-postgres`, Traefik | `/mnt/user/appdata/mealie/data` | Tier 2, Borg + `mealie.dump` | ja | App + DB in internem Netz getrennt | | `mealie-postgres` | Mealie-Datenbank | `apps/mealie/docker-compose.yml` | intern | `mealie_internal` | `/mnt/user/appdata/mealie/postgres18`, archivierter Rollback-Altstand `/mnt/user/appdata/_archive/pg18-immich-rollback-volumes-20260602/mealie-postgres17`, `mealie_postgres_password.txt` | Dump `mealie.dump` | nein | interne DB; PostgreSQL 18 | | `mail-archiver` | Mail-Archivierung | `apps/mail-archiver/docker-compose.yml` | `https://mail.kaleschke.info` | PostgreSQL 18, Internet/IMAP, Traefik, Authelia | `/mnt/user/appdata/mailarchiver/data-protection-keys` | Tier 2, `postgresql17-mailarchiver.dump` | ja + Authelia | Hybrid-Dienst: `frontend_net` fuer Internet, `backend_net` fuer DB; App-eigene Auth bleibt zusaetzliche Schutzschicht; Dump-Dateiname behaelt den historischen Cluster-Namen |