fix(mounts): DIE URSACHE gefunden - die CIFS-Verbindung stirbt mit dem Container
Ampel / ampel (push) Successful in 31s

Der Savepoint v3.17 fuehrte das als "Ursache liegt beim NAS, nicht gefunden".
Gemessen ist es etwas ganz anderes, und es liegt bei uns:

  /proc/fs/cifs/DebugData  ->  Net namespace: 4026532653
  api-Container            ->  net:[4026532653]   DIESELBE
  worker-Container         ->  net:[4026532540]   andere

Die CIFS-Verbindung lebt in der NETZ-NAMESPACE DES API-CONTAINERS - dort wird
sie eingehaengt, weil nur dieser Container CAP_SYS_ADMIN hat. Wird der Container
neu gebaut, stirbt sein Netz-Namespace und mit ihm der Socket. Der Mount steht
danach weiter in /proc/mounts (per rshared auf den Host propagiert) und sieht
vollkommen gesund aus - aber jeder Zugriff laeuft in den CIFS-Timeout.

Damit erklaert sich alles, was vorher widerspruechlich aussah: warum es nach
JEDEM Deploy passiert, warum `mount` Erfolg meldet, warum /proc/mounts genau eine
korrekte Schicht zeigt, und warum nur ein echtes Neu-Verbinden hilft. Das NAS ist
unschuldig (eine Sitzung, Status 1, 630 Credits, Ping 0,47 ms).

Zweiter Fund, der den Rest erklaert: Direkt nach einem frischen Mount antwortete
die Freigabe - und Sekunden spaeter nicht mehr. Das ist ein Wettlauf mit
`umount -l`: lazy heisst, der Abbau passiert spaeter, und faellt er samt
Propagation hinter den neuen Mount, zeigt der Pfad wieder auf die Leiche. Eine
einzige Probe kann das nicht sehen - deshalb prueft `wirklich_erreichbar()`
zweimal mit drei Sekunden Abstand, und zwar sowohl beim Mounten als auch in der
Wache.

Dazu: erste Pruefung der Wache schon nach 10 s statt 60 s. Genau dann ist die
Lage nach einem Deploy kaputt.

Ehrlich offen bleibt die strukturelle Folge: Der api-Container HAELT die
NAS-Verbindung. Startet er mitten in einem Rip neu, verliert auch der Worker sein
Ziel. Das saubere Gegenmittel waere ein Mount auf dem HOST statt im Container -
ein eigener Umbau, und er widerspraeche "Speicherziele ueber das UI einhaengen".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 14:32:51 +02:00
parent 549727f648
commit 4cf7acbb96
3 changed files with 93 additions and 14 deletions
+31 -4
View File
@@ -67,6 +67,29 @@ def ist_erreichbar(name: str) -> bool:
return False
def wirklich_erreichbar(name: str, warten=None) -> bool:
"""Antwortet die Freigabe auch noch DREI SEKUNDEN später? (zweimal geprüft)
Warum zweimal (Befund 26.07.2026): Direkt nach einem frischen `mount`
antwortete die Freigabe reproduzierbar — und Sekunden später lief jeder
Zugriff in die Zeitgrenze. Ursache ist ein Wettlauf beim Aufräumen:
`_stale_mounts_loesen` benutzt `umount -l`, und das ist LAZY — es hängt den
Mount sofort aus der Sicht aus, der eigentliche Abbau passiert später. Fällt
dieser Abbau samt Propagation (rshared) hinter den neuen Mount, zeigt der
Pfad wieder auf die Leiche.
Eine einzige Probe kann das nicht sehen. Zwei mit Abstand schon.
"""
if warten is None:
import time
warten = time.sleep
if not ist_erreichbar(name):
return False
warten(3)
return ist_erreichbar(name)
def schreibtest(pfad: str) -> bool:
"""Berechtigungs-Prüfung: können wir im Ziel wirklich schreiben?
@@ -323,14 +346,18 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
"NAS meist ablehnen."
)
raise RuntimeError(f"mount schlug fehl: {fehler[:300]}{hinweis}")
if ist_erreichbar(name):
# Zweimal mit Abstand prüfen — eine einzige Probe direkt nach dem
# Mount sieht den Wettlauf mit dem lazy umount nicht (siehe
# wirklich_erreichbar).
if wirklich_erreichbar(name):
return schreibtest(ziel)
if versuch == 1:
_lazy_umount(ziel)
raise RuntimeError(
f"{quelle} wurde eingehängt, antwortet aber nicht (zwei Versuche). "
"Läuft die Freigabe? Bei einem NAS im Ruhezustand hilft meist ein "
"erneutes Einhängen über Einstellungen → Speicherziele → Reparieren."
f"{quelle} wurde eingehängt, antwortet aber nicht dauerhaft (zwei "
"Versuche). Läuft die Freigabe? Bei einem NAS im Ruhezustand hilft "
"meist ein erneutes Einhängen über Einstellungen → Speicherziele → "
"Reparieren."
)
finally:
if creds_datei: