Files
proxmox-snapshot-service/README.md
T
duffyduckandClaude Opus 5 e9aeaf9e62 Snapshot-Laeufe gegen cfs-Sperren des Storages absichern
Auf einem echten Host schlugen Snapshots reihenweise mit
"cfs-lock 'storage-NAME' error: got lock request timeout" fehl.

Ursache: 'pvesh create .../snapshot' lief mit dem allgemeinen
Kommando-Zeitlimit von 60s. Genau so lange wartet Proxmox aber auf den
Storage-Lock. Lief der Aufruf in unser Zeitlimit, ging es mit der naechsten
VM weiter, waehrend der Task noch lief - und die naechste VM scheiterte
dann an derselben Sperre. Eine VM konnte so einen ganzen Lauf umwerfen.

* Snapshot-Aktionen laufen jetzt mit dem langen task_timeout statt mit dem
  kurzen Zeitlimit fuer Lesezugriffe.
* Ohne UPID in der Antwort wird ersatzweise gewartet, bis der Gast nicht
  mehr gesperrt ist, statt sofort weiterzumachen.
* Sperr-Fehler gelten als voruebergehend und werden 'retries'-mal mit
  'retry_delay' Abstand wiederholt; echte Fehler wie "storage does not
  support snapshots" nicht.
* Neu: 'pause_between' fuer eine Pause zwischen zwei Gaesten.

Ausserdem: Kommentare hinter einem Wert ("retries = 2  # ...") wurden nicht
abgeschnitten und machten die Konfiguration ungueltig - das eigene
Beispiel war davon betroffen. 'description' bleibt bewusst unangetastet,
damit ein '#' in der Beschreibung erhalten bleibt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 01:54:59 +02:00

318 lines
12 KiB
Markdown

# pvesnap — automatische Snapshots für Proxmox VE
Ein systemd-Dienst, der nach frei definierbaren Zeitplänen Snapshots von VMs und
Containern anlegt und alte Snapshots nach einer einstellbaren Vorhaltezeit wieder
löscht. Alles wird über **eine INI-Datei** gesteuert, die sich auch bequem in einem
**ncurses-Editor** bearbeiten lässt.
* Beliebig viele **Gruppen** — z. B. „diese VMs stündlich, jene täglich, der Rest monatlich“
* **VM-Auswahl** nach VMID, Name (mit `*`-Mustern), Tag, Pool oder einfach „alle“
* **Vorhaltezeit** je Gruppe nach Anzahl *und/oder* Alter
* Jeder Snapshot bekommt eine **Beschreibung**, an der er sich wiedererkennen lässt
* Läuft cluster-weit (alle Nodes) über `pvesh`
* Nur Python-Standardbibliothek — keine zusätzlichen Pakete nötig
---
## Installation
Auf dem Proxmox-Host als root:
```bash
git clone <dieses-repo> pvesnap && cd pvesnap
./install.sh
```
Das Skript legt an:
| Pfad | Inhalt |
|---|---|
| `/usr/lib/pvesnap/` | Programmcode |
| `/usr/local/bin/pvesnap` | Startbefehl |
| `/etc/pvesnap/pvesnap.conf` | Konfiguration (wird bei Updates **nicht** überschrieben) |
| `/var/lib/pvesnap/state.json` | merkt sich die letzten Läufe |
| `/etc/systemd/system/pvesnap.service` | systemd-Unit |
In der mitgelieferten Beispielkonfiguration sind alle Gruppen auf `enabled = no`
gesetzt — es passiert also erst etwas, wenn du eine Gruppe aktivierst.
**Deinstallation:**
```bash
./uninstall.sh # Programm und Dienst entfernen, Konfiguration bleibt
./uninstall.sh --purge # zusätzlich /etc/pvesnap und /var/lib/pvesnap löschen
```
Bereits angelegte Snapshots werden dabei **nie** angerührt.
---
## Konfiguration im ncurses-Editor
```bash
pvesnap config
```
* **Gruppenliste** — `Enter` bearbeiten, `n` neu, `c` kopieren, `d` löschen,
`Leertaste` an/aus, `g` globale Einstellungen, `v` Übersicht welche VM in
welcher Gruppe landet, `s` speichern, `r` Dienst neu laden, `q` Ende
* **`r` — Dienst neu laden**, ohne den Editor zu verlassen: entspricht
`systemctl reload pvesnap`. Ungespeicherte Änderungen werden vorher zum
Speichern angeboten, ein gestoppter Dienst zum Starten, und wenn das Neuladen
scheitert, wird ein Neustart angeboten. Nach `s` fragt der Editor ohnehin
gleich, ob neu geladen werden soll. Oben in der Kopfzeile steht jederzeit, ob
der Dienst läuft. (Braucht root — also `sudo pvesnap config`.)
* **Gruppe bearbeiten** — Feld auswählen, `Enter` ändern, `Leertaste` umschalten.
Ganz unten steht live, auf wie viele Gäste die Auswahl gerade zutrifft.
* **VM-Auswahl** (`v`) — Liste aller VMs und Container mit Node, Status und Tags;
`Leertaste` auswählen, `a` alle, `n` keine, `i` umkehren, `/` filtern,
`Enter` übernehmen.
> Beim Speichern wird die INI-Datei neu geschrieben. Eigene Kommentare und ein
> `[defaults]`-Abschnitt gehen dabei verloren (die Werte bleiben inhaltlich
> erhalten, sie stehen danach in jeder Gruppe einzeln). Eine Sicherung wird als
> `pvesnap.conf.bak` abgelegt.
---
## Konfiguration von Hand
```ini
[global]
prefix = auto # Namenspräfix aller pvesnap-Snapshots
check_interval = 60s # wie oft der Dienst nach Fälligem schaut
state_file = /var/lib/pvesnap/state.json
log_level = INFO
task_timeout = 15m
retries = 2 # Wiederholungen, wenn eine Sperre belegt ist
retry_delay = 60s
pause_between = 0s # Pause zwischen zwei Gästen
run_on_start = no # beim Start sofort einen Durchlauf machen?
dry_run = no # yes = nichts wirklich tun, nur protokollieren
description = pvesnap | Gruppe: {group} | erstellt: {datetime} | Vorhaltezeit: {keep_time}
[defaults] # Vorgaben für alle Gruppen (optional)
skip_stopped = no
[group:stündlich]
interval = 1h
align = yes # an der Uhr ausrichten: 00:00, 01:00, ...
keep_count = 24 # höchstens 24 Snapshots je VM
keep_time = 2d # nichts älter als 2 Tage
tags = stuendlich # alle VMs mit diesem Tag
skip_stopped = yes
[group:täglich]
schedule = daily
at = 02:30
keep_count = 14
keep_time = 21d
names = web-*, db-*
[group:monatlich]
schedule = monthly
day_of_month = 1
at = 04:00
keep_count = 6
all = yes
exclude_tags = nosnap
```
Nach jeder Änderung:
```bash
pvesnap check # Konfiguration prüfen
systemctl reload pvesnap # Dienst übernimmt die Änderung ohne Neustart
```
### Zeitplan
Entweder ein Intervall …
```ini
interval = 30m # 30m, 1h, 6h, 2d12h, 1w ...
align = yes # yes: an der Uhr ausgerichtet (00:00, 00:30, 01:00, ...)
# no: 30 Minuten nach dem letzten Lauf
```
… oder ein fester Termin:
| `schedule` | zusätzliche Angaben | Beispiel |
|---|---|---|
| `hourly` | `minute = 15` | jede Stunde um :15 |
| `daily` | `at = 02:30` | täglich um 02:30 |
| `weekly` | `at`, `day_of_week = so` | sonntags um 03:00 |
| `monthly` | `at`, `day_of_month = 1` | am 1. jedes Monats |
| `yearly` | `at`, `day_of_month`, `month` | einmal jährlich |
`day_of_month = 31` wird in kürzeren Monaten automatisch auf den letzten Tag
gezogen. Läuft der Host zum Termin nicht, wird der Lauf beim nächsten Start
nachgeholt.
### Vorhaltezeit
```ini
keep_count = 24 # höchstens 24 Snapshots je VM und Gruppe (0 = unbegrenzt)
keep_time = 7d # nichts älter als 7 Tage (0 = unbegrenzt)
keep_min = 1 # so viele bleiben in jedem Fall stehen
```
Beides ist kombinierbar — gelöscht wird, was *eine* der beiden Grenzen reißt.
Mindestens eine der beiden Angaben muss gesetzt sein, sonst würde die Zahl der
Snapshots unbegrenzt wachsen (`pvesnap check` weist darauf hin).
### Welche VMs?
Alle Angaben wirken als **ODER**, Ausschlüsse gewinnen immer:
```ini
all = yes # alle VMs und Container
vmids = 100,101,105-110 # nach ID, auch Bereiche
names = web-*, db-0? # nach Name, mit Platzhaltern
tags = prod, wichtig # nach Proxmox-Tag
pools = Kunden # nach Proxmox-Pool
types = qemu # nur VMs (bzw. lxc = nur Container; leer = beides)
exclude_vmids = 999
exclude_names = *-test
exclude_tags = nosnap
```
### Snapshot-Optionen
```ini
vmstate = yes # RAM mitsichern (nur QEMU und nur bei laufender VM)
skip_stopped = yes # gestoppte Gäste überspringen
description = Sicherung von {name} ({vmid}) vom {date}
```
Platzhalter der Beschreibung: `{group}` `{group_slug}` `{vmid}` `{name}` `{node}`
`{type}` `{pool}` `{tags}` `{date}` `{time}` `{datetime}` `{timestamp}`
`{keep_time}` `{keep_count}` `{schedule}`
---
## Namensschema und Sicherheit
```
auto-taeglich-20260730-023000
│ │ │ └── Uhrzeit
│ │ └────────── Datum
│ └──────────────────── Kurzname der Gruppe
└─────────────────────────── prefix aus [global]
```
pvesnap löscht **ausschließlich** Snapshots, deren Name exakt auf dieses Muster
passt und deren Gruppen-Kurzname zur jeweiligen Gruppe gehört. Von Hand oder von
anderen Werkzeugen angelegte Snapshots bleiben garantiert unangetastet.
Umlaute und Sonderzeichen im Gruppennamen werden für den Kurznamen umgeschrieben
(`täglich``taeglich`). Wenn zwei Gruppen denselben Kurznamen ergäben, meldet
das `pvesnap check`.
---
## Befehle
```bash
pvesnap status # Übersicht: Gruppen, letzte und nächste Läufe
pvesnap vms # alle VMs und die Gruppen, in denen sie stecken
pvesnap list # vorhandene pvesnap-Snapshots (-a = auch fremde)
pvesnap check # Konfiguration prüfen
pvesnap config # ncurses-Editor
pvesnap run # jetzt fällige Gruppen ausführen
pvesnap run --force -g täglich # eine Gruppe sofort ausführen
pvesnap run --force --dry-run # Probelauf, ändert nichts
pvesnap prune -g stündlich # nur aufräumen, keine neuen Snapshots
systemctl status pvesnap
systemctl reload pvesnap # Konfiguration neu einlesen (SIGHUP)
journalctl -u pvesnap -f # Protokoll mitlesen
```
Ein manueller `pvesnap run` ist auch möglich, während der Dienst läuft — beide
teilen sich eine Sperre und kommen sich nicht in die Quere.
Mit `-c /pfad/zur.conf` lässt sich jederzeit eine andere Konfiguration verwenden,
z. B. zum Ausprobieren.
---
## Hinweise aus der Praxis
* **Snapshot ist kein Backup.** Snapshots liegen auf demselben Storage wie die VM.
Für echte Sicherungen zusätzlich `vzdump` / Proxmox Backup Server verwenden.
* **Speicherplatz:** Jeder Snapshot hält alte Blöcke fest. Bei schreibfreudigen
VMs lieber kurze Vorhaltezeiten wählen und den Storage im Auge behalten.
* **Nicht jedes Storage kann Snapshots** — LVM-thick und Verzeichnis-Storage mit
`raw`-Images können es nicht. Solche VMs melden einen Fehler im Protokoll; die
übrigen laufen normal weiter.
* **`vmstate = yes`** friert die VM kurz ein und braucht Platz in Höhe des
zugewiesenen RAM. Für regelmäßige Läufe meist unnötig.
* **Gruppe umbenennen:** Der neue Name bekommt einen eigenen Kurznamen. Snapshots
unter dem alten Namen werden dann nicht mehr automatisch aufgeräumt — vorher
aufräumen lassen oder die alten Snapshots von Hand entfernen.
* **Zeitumstellung:** Es wird mit lokaler Zeit gerechnet. Ein täglicher Termin um
02:30 kann in der Umstellungsnacht ausfallen oder doppelt anstehen.
---
## Wenn Snapshots am Storage-Lock scheitern
```
trying to acquire cfs lock 'storage-data' ...
TASK ERROR: cfs-lock 'storage-data' error: got lock request timeout
```
`storage-data` ist dabei kein falsch gelesener Name — in pmxcfs heißen
Storage-Sperren immer `storage-<name>`, gemeint ist also das Storage `data`.
Proxmox nimmt diese Sperre beim Anlegen eines Snapshots und gibt nach 60 s auf,
wenn sie jemand anderes hält.
pvesnap geht damit so um:
* Der Aufruf wartet, bis der Snapshot-Task in Proxmox **wirklich fertig** ist,
bevor die nächste VM drankommt — sonst würden sich die Läufe gegenseitig
aussperren.
* Sperr-Fehler gelten als vorübergehend und werden `retries`-mal mit
`retry_delay` Abstand wiederholt. Echte Fehler (etwa „storage does not support
snapshots") werden **nicht** wiederholt.
* Mit `pause_between = 10s` lässt sich zusätzlich Druck vom Storage nehmen, wenn
viele VMs auf demselben Storage liegen.
Hält die Sperre dauerhaft, liegt die Ursache außerhalb von pvesnap. Diese
Kommandos helfen beim Eingrenzen:
```bash
pvesm status # ist das Storage online und erreichbar?
grep -A6 "^[a-z]*: data" /etc/pve/storage.cfg
pvesh get /cluster/tasks --output-format json | head # hängt noch ein Task?
systemctl status pvestatd pve-cluster
journalctl -u pvestatd -n 50
```
Häufigste Ursachen: ein hängender Backup- oder Replikationsjob auf demselben
Storage, ein nicht erreichbares NFS/CIFS-Storage (dann blockiert `pvestatd`),
oder ein abgebrochener Task, der die Sperre nicht freigegeben hat.
---
## Aufbau
```
pvesnap/
config.py INI lesen und schreiben, Gruppen- und Globaleinstellungen
schedule.py Berechnung des nächsten Termins
naming.py Namensschema und Beschreibungs-Vorlagen
proxmox.py pvesh-Anbindung (Inventar, Snapshots anlegen/löschen)
engine.py Auswahl der Gäste, Anlegen, Aufräumen
daemon.py Hauptschleife, Signale, Sperren
state.py merkt sich die letzten Läufe
tui.py ncurses-Editor
cli.py Kommandozeile
config/pvesnap.conf.example
systemd/pvesnap.service
install.sh uninstall.sh
```