Sandbox-Optionen aus der systemd-Unit entfernen
Als Dienst schlug jeder Snapshot mit "cfs-lock 'storage-data' error: got lock request timeout" fehl, waehrend derselbe Aufruf in der Shell funktionierte. Ursache war ProtectSystem=full in unserer eigenen Unit: das haengt /etc im Namespace des Dienstes schreibgeschuetzt ein. pvesh fuehrt die Proxmox-API im eigenen Prozess aus, der Snapshot-Task ist also ein Kindprozess von pvesnap und erbt diese Einschraenkung. pmxcfs legt seine Sperren aber als Verzeichnisse unter /etc/pve/priv/lock/ an - das mkdir scheitert, Proxmox wiederholt es erfolglos und meldet am Ende einen Lock-Timeout statt eines Rechtefehlers. ProtectHome=yes war aus demselben Grund schaedlich: es blendet /root aus, womit die SSH-Schluessel fuer die uebrigen Cluster-Nodes fehlen. * Unit enthaelt keine Sandbox-Optionen mehr, mit Kommentar, warum nicht. * Neu: pvesnap/preflight.py prueft root-Rechte, Schreibzugriff auf /etc/pve und Sichtbarkeit von /root. Der Dienst schreibt das beim Start ins Journal, "pvesnap check" zeigt es ebenfalls an - damit faellt so etwas sofort auf, statt sich als Lock-Timeout zu tarnen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e26d966a8b
commit
1be13ad294
@@ -281,8 +281,30 @@ pvesnap geht damit so um:
|
|||||||
* Mit `pause_between = 10s` lässt sich zusätzlich Druck vom Storage nehmen, wenn
|
* Mit `pause_between = 10s` lässt sich zusätzlich Druck vom Storage nehmen, wenn
|
||||||
viele VMs auf demselben Storage liegen.
|
viele VMs auf demselben Storage liegen.
|
||||||
|
|
||||||
Hält die Sperre dauerhaft, liegt die Ursache außerhalb von pvesnap. Diese
|
### Der Dienst scheitert, von Hand geht es
|
||||||
Kommandos helfen beim Eingrenzen:
|
|
||||||
|
Scheitert **jeder** Lauf des Dienstes am Storage-Lock, während derselbe Befehl
|
||||||
|
in der Shell funktioniert, liegt es an der Umgebung des Dienstes — nicht am
|
||||||
|
Storage. `pvesh` führt die Proxmox-API im eigenen Prozess aus, der Snapshot-Task
|
||||||
|
ist also ein *Kindprozess von pvesnap* und erbt alles, was in der systemd-Unit
|
||||||
|
eingeschränkt wurde.
|
||||||
|
|
||||||
|
Der Klassiker ist `ProtectSystem=` in der Unit: das hängt `/etc` schreibgeschützt
|
||||||
|
ein, und pmxcfs legt seine Sperren als Verzeichnisse unter `/etc/pve/priv/lock/`
|
||||||
|
an. Das `mkdir` scheitert, Proxmox wiederholt es erfolglos — und meldet am Ende
|
||||||
|
einen Lock-Timeout statt eines Rechtefehlers. Prüfen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl show pvesnap -p ProtectSystem -p ProtectHome -p PrivateTmp -p ReadOnlyPaths
|
||||||
|
pvesnap check # meldet so etwas von sich aus
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle drei müssen leer bzw. `no` sein. Die mitgelieferte Unit enthält deshalb
|
||||||
|
bewusst **keine** Sandbox-Optionen.
|
||||||
|
|
||||||
|
### Wenn die Sperre wirklich belegt ist
|
||||||
|
|
||||||
|
Diese Kommandos helfen beim Eingrenzen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pvesm status # ist das Storage online und erreichbar?
|
pvesm status # ist das Storage online und erreichbar?
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .config import DEFAULT_CONFIG_PATH, ConfigError, load_config
|
|||||||
from .daemon import Daemon, SingleInstanceLock
|
from .daemon import Daemon, SingleInstanceLock
|
||||||
from .engine import run_group, select_guests
|
from .engine import run_group, select_guests
|
||||||
from .naming import parse_name
|
from .naming import parse_name
|
||||||
|
from .preflight import check_environment
|
||||||
from .proxmox import Proxmox, ProxmoxError, pvesh_available
|
from .proxmox import Proxmox, ProxmoxError, pvesh_available
|
||||||
from .schedule import describe, next_due
|
from .schedule import describe, next_due
|
||||||
from .state import State
|
from .state import State
|
||||||
@@ -331,6 +332,13 @@ def cmd_check(args):
|
|||||||
for problem in problems:
|
for problem in problems:
|
||||||
print(" - %s" % problem, file=sys.stderr)
|
print(" - %s" % problem, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
environment = check_environment(require_pve=pvesh_available())
|
||||||
|
if environment:
|
||||||
|
print("Hinweise zur Umgebung:", file=sys.stderr)
|
||||||
|
for problem in environment:
|
||||||
|
print(" - %s" % problem, file=sys.stderr)
|
||||||
|
print("", file=sys.stderr)
|
||||||
|
|
||||||
print("Konfiguration in Ordnung: %d Gruppe(n)." % len(config.groups))
|
print("Konfiguration in Ordnung: %d Gruppe(n)." % len(config.groups))
|
||||||
for group in config.groups:
|
for group in config.groups:
|
||||||
print(" - %s: %s, behalte %s / %s%s"
|
print(" - %s: %s, behalte %s / %s%s"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from .config import ConfigError, load_config
|
from .config import ConfigError, load_config
|
||||||
from .engine import run_group
|
from .engine import run_group
|
||||||
|
from .preflight import warn_about_environment
|
||||||
from .proxmox import Proxmox, ProxmoxError
|
from .proxmox import Proxmox, ProxmoxError
|
||||||
from .schedule import next_due
|
from .schedule import next_due
|
||||||
from .state import State
|
from .state import State
|
||||||
@@ -137,6 +138,10 @@ class Daemon:
|
|||||||
len(self.config.groups), globals_.prefix,
|
len(self.config.groups), globals_.prefix,
|
||||||
", TESTLAUF" if globals_.dry_run else "")
|
", TESTLAUF" if globals_.dry_run else "")
|
||||||
|
|
||||||
|
# Der Dienst laeuft trotzdem weiter - aber es soll gleich beim Start im
|
||||||
|
# Journal stehen, warum spaeter jeder Snapshot scheitert.
|
||||||
|
warn_about_environment(log)
|
||||||
|
|
||||||
daemon_lock = SingleInstanceLock(
|
daemon_lock = SingleInstanceLock(
|
||||||
globals_.lock_file + ".daemon",
|
globals_.lock_file + ".daemon",
|
||||||
busy_message="Der pvesnap-Dienst laeuft bereits (%s.daemon)" % globals_.lock_file)
|
busy_message="Der pvesnap-Dienst laeuft bereits (%s.daemon)" % globals_.lock_file)
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
"""Prueft, ob pvesnap ueberhaupt arbeiten kann.
|
||||||
|
|
||||||
|
Hintergrund: pvesh fuehrt die Proxmox-API im eigenen Prozess aus. Der
|
||||||
|
Snapshot-Task ist damit ein Kindprozess von pvesnap und erbt dessen Umgebung -
|
||||||
|
also auch Einschraenkungen aus der systemd-Unit. Eine schreibgeschuetzte /etc
|
||||||
|
faellt dabei nicht als Rechtefehler auf, sondern als
|
||||||
|
"cfs-lock 'storage-XXX' error: got lock request timeout", weil pmxcfs seine
|
||||||
|
Sperren als Verzeichnisse unter /etc/pve/priv/lock/ anlegt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from .proxmox import pvesh_available
|
||||||
|
|
||||||
|
PMXCFS = "/etc/pve"
|
||||||
|
PMXCFS_LOCKDIR = "/etc/pve/priv/lock"
|
||||||
|
|
||||||
|
|
||||||
|
def _read_only(path):
|
||||||
|
"""True, wenn `path` auf einem schreibgeschuetzt eingehaengten Dateisystem liegt."""
|
||||||
|
try:
|
||||||
|
stats = os.statvfs(path)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
return bool(stats.f_flag & getattr(os, "ST_RDONLY", 1))
|
||||||
|
|
||||||
|
|
||||||
|
def check_environment(require_pve=True):
|
||||||
|
"""Liste von Problemen; leer heisst: alles in Ordnung."""
|
||||||
|
problems = []
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
problems.append("pvesnap muss als root laufen (pvesh benoetigt root-Rechte).")
|
||||||
|
|
||||||
|
if require_pve and not pvesh_available():
|
||||||
|
problems.append("'pvesh' nicht gefunden - laeuft pvesnap auf einem "
|
||||||
|
"Proxmox-VE-Host?")
|
||||||
|
|
||||||
|
if not os.path.isdir(PMXCFS):
|
||||||
|
if require_pve:
|
||||||
|
problems.append("%s nicht vorhanden - laeuft pve-cluster?" % PMXCFS)
|
||||||
|
return problems
|
||||||
|
|
||||||
|
if _read_only(PMXCFS) or not os.access(PMXCFS, os.W_OK):
|
||||||
|
problems.append(
|
||||||
|
"%s ist nicht beschreibbar. Snapshots scheitern dann mit "
|
||||||
|
"\"cfs-lock 'storage-...' error: got lock request timeout\", weil "
|
||||||
|
"Proxmox seine Sperren unter %s anlegt. Ursache ist meist eine "
|
||||||
|
"Sandbox-Option in der systemd-Unit (ProtectSystem=, ReadOnlyPaths=) "
|
||||||
|
"- pruefen mit: systemctl show pvesnap -p ProtectSystem -p ProtectHome"
|
||||||
|
% (PMXCFS, PMXCFS_LOCKDIR))
|
||||||
|
|
||||||
|
# ProtectHome=yes ersetzt /root durch ein leeres, unlesbares Verzeichnis.
|
||||||
|
try:
|
||||||
|
empty_root = os.path.isdir("/root") and not os.listdir("/root")
|
||||||
|
except OSError:
|
||||||
|
empty_root = True
|
||||||
|
if empty_root:
|
||||||
|
problems.append("/root ist leer oder nicht lesbar (ProtectHome= in der "
|
||||||
|
"systemd-Unit?). Proxmox erreicht andere Nodes im Cluster "
|
||||||
|
"ueber die SSH-Schluessel in /root/.ssh.")
|
||||||
|
|
||||||
|
return problems
|
||||||
|
|
||||||
|
|
||||||
|
def warn_about_environment(log, require_pve=True):
|
||||||
|
"""Probleme protokollieren; gibt True zurueck, wenn alles passt."""
|
||||||
|
problems = check_environment(require_pve=require_pve)
|
||||||
|
for problem in problems:
|
||||||
|
log.error("Umgebung: %s", problem)
|
||||||
|
return not problems
|
||||||
+16
-10
@@ -14,19 +14,25 @@ Restart=on-failure
|
|||||||
RestartSec=30
|
RestartSec=30
|
||||||
TimeoutStopSec=300
|
TimeoutStopSec=300
|
||||||
|
|
||||||
# pvesh benoetigt root-Rechte.
|
# pvesh fuehrt die API im eigenen Prozess aus - der Snapshot-Task ist also ein
|
||||||
|
# Kindprozess von pvesnap und erbt dessen Umgebung.
|
||||||
User=root
|
User=root
|
||||||
StateDirectory=pvesnap
|
StateDirectory=pvesnap
|
||||||
RuntimeDirectory=pvesnap
|
|
||||||
|
|
||||||
# Moderate Absicherung - der Dienst muss auf pvesh und /etc/pve zugreifen.
|
# ACHTUNG: Hier duerfen KEINE Sandbox-Optionen stehen.
|
||||||
NoNewPrivileges=yes
|
#
|
||||||
PrivateTmp=yes
|
# ProtectSystem= haengt /etc schreibgeschuetzt ein. pmxcfs legt seine
|
||||||
ProtectHome=yes
|
# Sperren aber als Verzeichnisse unter /etc/pve/priv/lock/
|
||||||
ProtectSystem=full
|
# an - das mkdir scheitert dann, Proxmox wiederholt es
|
||||||
ProtectKernelTunables=yes
|
# erfolglos und der Task endet in
|
||||||
ProtectControlGroups=yes
|
# "cfs-lock 'storage-XXX' error: got lock request timeout".
|
||||||
RestrictRealtime=yes
|
# ProtectHome= blendet /root aus - damit fehlen die SSH-Schluessel, mit
|
||||||
|
# denen Proxmox andere Nodes im Cluster erreicht.
|
||||||
|
# PrivateTmp= gibt dem Dienst ein eigenes /tmp, das Proxmox-Werkzeuge
|
||||||
|
# nicht mit den uebrigen pve-Diensten teilen.
|
||||||
|
#
|
||||||
|
# Die Proxmox-eigenen Units (pvedaemon, pvescheduler) verzichten aus denselben
|
||||||
|
# Gruenden auf diese Optionen.
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
Reference in New Issue
Block a user