Storage-Unterschiede beim Abkoppeln benennen statt pauschal zu melden

Die Abhaengigkeit vom Quell-Snapshot ist eine Ceph-Eigenheit. Bisher meldete
flatten alles andere pauschal als "kein Ceph-Datentraeger" - das klingt nach
Einschraenkung, ist bei LVM-thin aber das Gegenteil.

Nachgemessen auf local-lvm: ein Thin-Snapshot teilt sich die Bloecke im Pool
(256 MB Nutzdaten liegen dort nur einmal), ist aber trotzdem eigenstaendig -
Quell-Snapshot und Original liessen sich loeschen, waehrend der Klon existiert,
und seine Pruefsumme blieb unveraendert. Dort gibt es also weder geschuetzte
Snapshots noch etwas zu flatten.

  lvmthin   nicht noetig, Klon ist von sich aus unabhaengig
  zfspool   nicht moeglich, ginge nur ueber zfs send | zfs recv
  rbd       noetig fuer den Dauerbetrieb
  lvm dick  PVE kann dort gar keine Snapshots

Der Hinweis in der Zusammenfassung richtet sich jetzt ebenfalls nach dem
Storage-Typ, statt immer zum Flatten zu raten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-07-31 19:41:18 +02:00
co-authored by Claude Opus 5
parent 09dcd4c572
commit 2c85011a08
3 changed files with 84 additions and 11 deletions
+46 -4
View File
@@ -702,10 +702,52 @@ Maschine als verwaist gelten. Ohne Elternteil wird nichts von selbst entfernt,
und der Schutz eines Snapshots wird nur gelöst, wenn wirklich kein Klon mehr und der Schutz eines Snapshots wird nur gelöst, wenn wirklich kein Klon mehr
daran hängt. daran hängt.
Bei anderen Storages sieht es anders aus: **LVM-thin**-Klone sind ohnehin ### Und wenn die Maschine nicht auf Ceph liegt?
unabhängig (der Thin-Pool zählt die Blöcke, das Original darf weg), bei **ZFS**
hängt der Klon am Snapshot und ließe sich nur per `zfs send | zfs recv` lösen — Das ganze Thema „Abhängigkeit vom Quell-Snapshot" ist eine **Ceph-Eigenheit**.
das macht `flatten` nicht und meldet die Datenträger als nicht behandelt. Andere Storages verhalten sich anders:
| Storage | Klon aus Snapshot | Hängt am Quell-Snapshot? | `flatten` |
|---|---|---|---|
| `rbd` (Ceph) | `rbd clone`, sofort | **ja** — Snapshot wird geschützt und unlöschbar | nötig für den Dauerbetrieb |
| `lvmthin` | `lvcreate -s`, sofort | **nein** | nicht nötig |
| `zfspool` | `zfs clone`, sofort | ja | nicht möglich (nur `zfs send \| zfs recv`) |
| `lvm` (dick) | — | — | PVE kann dort gar keine Snapshots |
| `dir`, `nfs`, `cifs` | — | — | kein Klon aus qcow2-Snapshots möglich |
**LVM-thin ist in diesem Punkt sogar angenehmer als Ceph.** Ein Thin-Snapshot
ist ein eigenständiges Volume, das sich mit anderen nur die Blöcke im Pool
teilt — nachgemessen:
```
Belegung im Thin-Pool nach dem Klonen (256 MB Nutzdaten):
data <19.84g 1.26 % ← Pool hält die Daten nur EINMAL
snap_..._wdhtest 1.00g
vm-9998-disk-0 1.00g 25.00 % Klon
vm-9999-disk-0 1.00g 25.00 % Original
Quell-Snapshot löschen, während der Klon existiert → erlaubt
Original löschen → erlaubt
Prüfsumme des Klons danach → unverändert
```
Also: platzsparend geteilt **und** trotzdem frei löschbar. Es gibt dort weder
das Problem mit geschützten Snapshots noch etwas zu flatten — `flatten` meldet
solche Datenträger entsprechend als „nicht nötig".
Der Preis ist ein anderer: `lvmthin` ist **lokaler** Speicher. Die
Wiederherstellung muss auf demselben Node laufen wie das Original;
`pvesnap-recovery` prüft das und lehnt einen anderen Node ab. Mit Ceph ist sie
dagegen auf jedem Node des Clusters startbar.
Und ein Warnhinweis, der für Thin-Pools genauso gilt wie für Ceph: Läuft der
**Thin-Pool** voll, stehen alle Volumes darin. Überprovisionierung im Blick
behalten.
Bei **ZFS** hängt der Klon wie bei Ceph am Snapshot, aber `zfs promote` würde
die Abhängigkeit nur umdrehen statt auflösen. Echte Unabhängigkeit ginge dort
nur über eine Vollkopie per `zfs send | zfs recv` — das macht `flatten` nicht
und sagt es auch so.
### Der Arbeitsspeicher kommt mit ### Der Arbeitsspeicher kommt mit
+32 -3
View File
@@ -256,6 +256,26 @@ def linked_volumes(proxmox, instance):
return linked return linked
def flatten_hint(proxmox, volid):
"""Warum ein Datentraeger nicht abgekoppelt wird - je nach Storage.
Nur Ceph kennt das Problem ueberhaupt: dort haelt ein Klon seinen
Quell-Snapshot fest. Andere Storage-Typen loesen das anders oder gar nicht.
"""
storage = volid.split(":", 1)[0] if ":" in volid else ""
try:
kind = (proxmox._json(["get", "/storage/%s" % storage]) or {}).get("type", "")
except ProxmoxError:
kind = ""
if kind == "lvmthin":
return ("nicht noetig - Thin-Snapshots sind von sich aus unabhaengig; "
"Quell-Snapshot und Original lassen sich jederzeit loeschen")
if kind == "zfspool":
return ("nicht moeglich - ein ZFS-Klon haengt am Snapshot. Loesen ginge "
"nur ueber 'zfs send | zfs recv' in einen neuen Datentraeger")
return "kein Ceph-Datentraeger"
def flatten_cost(proxmox, instance): def flatten_cost(proxmox, instance):
"""Wieviele Bytes ein flatten kopieren muesste. """Wieviele Bytes ein flatten kopieren muesste.
@@ -785,10 +805,19 @@ def plan(proxmox, guest, snapname, spec):
"vergroessern oder verkleinern - entweder --memory " "vergroessern oder verkleinern - entweder --memory "
"weglassen oder --no-resume verwenden.") "weglassen oder --no-resume verwenden.")
if spec.mode == "recovery": if spec.mode == "recovery":
kinds = {_storage_info(proxmox, volid.split(":", 1)[0]).get("type")
for _key, volid, _size in result.disks}
if "rbd" in kinds:
result.notes.append( result.notes.append(
"Die Datentraeger sind Linked Clones - sie haengen am Quell-Snapshot, " "Die Datentraeger sind Linked Clones - auf Ceph haelt ein Klon "
"der dadurch unloeschbar wird. Soll die Maschine dauerhaft laufen, " "seinen Quell-Snapshot fest, der dadurch unloeschbar wird. Soll "
"spaeter mit 'pvesnap-recovery flatten %d' loesen." % result.newid) "die Maschine dauerhaft laufen, spaeter mit "
"'pvesnap-recovery flatten %d' loesen." % result.newid)
elif kinds <= {"lvmthin"}:
result.notes.append(
"Die Datentraeger sind Thin-Snapshots und damit von sich aus "
"unabhaengig - Quell-Snapshot und Original lassen sich jederzeit "
"loeschen. Ein Abkoppeln ist nicht noetig.")
if result.resume: if result.resume:
result.notes.append("Der Arbeitsspeicher (%s) wird kopiert; die Maschine " result.notes.append("Der Arbeitsspeicher (%s) wird kopiert; die Maschine "
"laeuft danach genau dort weiter, wo sie beim Snapshot " "laeuft danach genau dort weiter, wo sie beim Snapshot "
+5 -3
View File
@@ -27,7 +27,8 @@ from .curses_util import (C_DIM, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL, C_WARN
keybar, message, prompt, put, read_key) keybar, message, prompt, put, read_key)
from .proxmox import Proxmox, ProxmoxError, pvesh_available from .proxmox import Proxmox, ProxmoxError, pvesh_available
from .recovery import (EXCHANGE_GUEST_PATH, MKFS, RecoveryError, Spec, create, from .recovery import (EXCHANGE_GUEST_PATH, MKFS, RecoveryError, Spec, create,
destroy, find_orphans, flatten, flatten_cost, human_bytes, destroy, find_orphans, flatten, flatten_cost, flatten_hint,
human_bytes,
linked_volumes, list_instances, plan, pull, release, linked_volumes, list_instances, plan, pull, release,
remove_orphans, start) remove_orphans, start)
from .snapfs import list_snapshots from .snapfs import list_snapshots
@@ -565,7 +566,8 @@ def _flatten(stdscr, proxmox, instance, linked):
+ _plain(progress.lines), "Fehlgeschlagen") + _plain(progress.lines), "Fehlgeschlagen")
return return
lines = ["%d Datentraeger geloest." % len(done), ""] lines = ["%d Datentraeger geloest." % len(done), ""]
lines += ["nicht behandelt (kein Ceph): %s" % volid for volid in other] for volid in other:
lines += _wrap("%s: %s" % (volid, flatten_hint(proxmox, volid)), 74)
if not linked_volumes(proxmox, instance): if not linked_volumes(proxmox, instance):
lines += ["", "%s ist jetzt eigenstaendig. Die Snapshots des Originals " lines += ["", "%s ist jetzt eigenstaendig. Die Snapshots des Originals "
"lassen sich wieder loeschen." % instance.label] "lassen sich wieder loeschen." % instance.label]
@@ -889,7 +891,7 @@ def cmd_flatten(proxmox, args):
for volid in already: for volid in already:
print(" war schon eigenstaendig: %s" % volid) print(" war schon eigenstaendig: %s" % volid)
for volid in other: for volid in other:
print(" nicht behandelt (kein Ceph-Datentraeger): %s" % volid) print(" %s: %s" % (volid, flatten_hint(proxmox, volid)))
if not linked_volumes(proxmox, instance): if not linked_volumes(proxmox, instance):
print("\n%s ist jetzt eigenstaendig. Die Snapshots des Originals lassen " print("\n%s ist jetzt eigenstaendig. Die Snapshots des Originals lassen "
"sich wieder loeschen." % instance.label) "sich wieder loeschen." % instance.label)