fix(mounts): DIE URSACHE gefunden - die CIFS-Verbindung stirbt mit dem Container
Ampel / ampel (push) Successful in 31s
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:
@@ -183,9 +183,11 @@ def test_mounten_geht_bei_totem_mount_den_reparatur_weg(monkeypatch):
|
||||
import mounts
|
||||
|
||||
ablauf = []
|
||||
antworten = iter([False, True]) # vorher tot, nach dem Mount erreichbar
|
||||
monkeypatch.setattr(mounts.os, "makedirs", lambda *a, **k: None)
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: next(antworten))
|
||||
# Vorher tot (der Vor-Check), nach dem Mount dauerhaft erreichbar. Die
|
||||
# Doppelprobe wird hier gestubbt, damit der Test nicht 3 s echt wartet.
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
|
||||
monkeypatch.setattr(mounts, "wirklich_erreichbar", lambda name: True)
|
||||
monkeypatch.setattr(mounts, "_stale_mounts_loesen",
|
||||
lambda ziel: ablauf.append("loesen"))
|
||||
monkeypatch.setattr(mounts, "schreibtest", lambda p: True)
|
||||
@@ -216,6 +218,7 @@ def test_mounten_prueft_das_ergebnis_und_versucht_es_zweimal(monkeypatch):
|
||||
# nie erreichbar: vorher, nach Versuch 1, nach Versuch 2
|
||||
monkeypatch.setattr(mounts.os, "makedirs", lambda *a, **k: None)
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
|
||||
monkeypatch.setattr(mounts, "wirklich_erreichbar", lambda name: False)
|
||||
monkeypatch.setattr(mounts, "_stale_mounts_loesen", lambda ziel: None)
|
||||
monkeypatch.setattr(mounts, "_lazy_umount", lambda ziel: ablauf.append("lazy"))
|
||||
monkeypatch.setattr(mounts, "schreibtest", lambda p: True)
|
||||
@@ -248,3 +251,33 @@ def test_mounten_laesst_gesunden_mount_in_ruhe(monkeypatch):
|
||||
lambda ziel: (_ for _ in ()).throw(AssertionError("nicht loesen!")))
|
||||
|
||||
assert mounts.mounten("rippy", "cifs", "//nas/rippy") is True
|
||||
|
||||
|
||||
def test_wirklich_erreichbar_prueft_zweimal_mit_abstand(monkeypatch):
|
||||
"""Befund 26.07.2026: Direkt nach einem frischen `mount` antwortete die
|
||||
Freigabe - und Sekunden spaeter lief jeder Zugriff in die Zeitgrenze
|
||||
(Wettlauf mit dem lazy umount, dessen Abbau hinter den neuen Mount fiel).
|
||||
Eine EINZIGE Probe kann das nicht sehen."""
|
||||
import mounts
|
||||
|
||||
antworten = iter([True, False]) # erst ja, dann nein
|
||||
gewartet = []
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: next(antworten))
|
||||
assert mounts.wirklich_erreichbar("rippy", warten=gewartet.append) is False
|
||||
assert gewartet == [3]
|
||||
|
||||
|
||||
def test_wirklich_erreichbar_bei_gesunder_freigabe(monkeypatch):
|
||||
import mounts
|
||||
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: True)
|
||||
assert mounts.wirklich_erreichbar("rippy", warten=lambda s: None) is True
|
||||
|
||||
|
||||
def test_wirklich_erreichbar_spart_das_warten_wenn_schon_die_erste_probe_faellt(monkeypatch):
|
||||
import mounts
|
||||
|
||||
gewartet = []
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
|
||||
assert mounts.wirklich_erreichbar("rippy", warten=gewartet.append) is False
|
||||
assert gewartet == [] # nicht drei Sekunden fuer nichts
|
||||
|
||||
Reference in New Issue
Block a user