Doppelte Web-/DB-Backups durch parallele cron.sh-Läufe – fehlende Parallelitätssicherung im Backup-Subsystem

snocer

Gesperrt
Ich hatte reproduzierbar doppelte Web- und DB-Backups (physisch zwei Archive pro Laufzeitfenster auf der HDD).
Es existierte kein doppelter Cron-Eintrag in der root-crontab.

Nach intensiver Analyse ist die Ursache eindeutig:

Ursache​

cron.sh kann parallel laufen, wenn ein Backup-Lauf länger dauert als das Cron-Intervall.

Das Backup-Subsystem von ISPConfig ist nicht gegen parallele cron.sh-Instanzen abgesichert.
Es existiert weder:

  • ein globaler Prozess-Lock innerhalb von cron.sh
  • noch ein Lock pro Backup-Job / Domain / Datenbank
  • noch eine Dedup-Logik im Backup-Plugin
Ergebnis:

Wenn cron.sh erneut startet, während ein vorheriger Lauf noch Web-/DB-Backups erzeugt,
werden identische Backup-Jobs ein weiteres Mal abgearbeitet → doppelte Archive entstehen.

Das ist kein Bedienfehler und kein „Cron doppelt eingetragen“.
Ein einzelner Cron-Eintrag reicht aus, wenn:

  • Backup-Dauer > Cron-Intervall
  • keine wirksame Prozesssperre existiert
Das Verhalten ist damit technisch reproduzierbar.


Reproduktionslogik​

Beispiel:

  • cron.sh läuft alle 5 Minuten
  • Web-Backup dauert 8–15 Minuten
  • keine funktionierende Prozesssperre aktiv
Ablauf:

Minute 00 → cron.sh startet → Backup läuft
Minute 05 → cron.sh startet erneut → erster Lauf noch aktiv
Minute 10 → ggf. dritter Start

Da das Backup-Plugin keinen Re-Entry-Schutz besitzt, werden Jobs mehrfach verarbeitet.


Wichtig: Kein zweiter Cron-Eintrag notwendig​

Es gab zu keinem Zeitpunkt zwei aktive cron.sh-Einträge in der Crontab.

Die Doppelung entstand ausschließlich durch überlappende Prozesse desselben Eintrags.

Das ist entscheidend.


Lösung (technisch korrekt und stabil)​

Es muss sichergestellt werden, dass zu jedem Zeitpunkt nur eine Instanz von cron.sh laufen kann.

Dazu ist ein globaler Lock notwendig, der die gesamte Laufzeit kapselt.

Korrekte Variante mit flock und -c​


*/5 * * * * /usr/bin/flock -n /var/lock/ispconfig-cron.lock -c '/usr/local/ispconfig/server/cron.sh >> /var/log/ispconfig/cron.log 2>&1'


Wichtig:

  • flock muss mit -c verwendet werden
  • das gesamte cron.sh muss innerhalb des Locks laufen
  • der Lock muss für die komplette Laufzeit gehalten werden
  • -n verhindert parallelen Start
Nach dieser Umstellung:

  • Web-Backups laufen exakt einmal
  • DB-Backups laufen exakt einmal
  • keine doppelten Archive mehr
Das Verhalten ist stabil reproduzierbar.


Fazit​

Das Problem liegt nicht in einer falschen Crontab-Konfiguration,
sondern in fehlender Parallelitätssicherung innerhalb des ISPConfig-Backup-Subsystems.


Solange cron.sh ohne globale Prozesssperre läuft und das Backup-Plugin keinen eigenen Re-Entry-Schutz implementiert,
kann es bei realistischen Laufzeiten zu doppelten Backups kommen.

Die saubere Lösung ist eine globale Lock-Klammerung von cron.sh.

PS: Musste das auch mal loswerden weil man hier sonst immer schnell abgewatscht wird, passiert ja nur bei Dir etc. und den Unterstellungen, man würde nur immer an den Core Dateien etwas ändern. Was man natürlich auch dann macht wen die Fehler tatsächlich auch ersichtlich sind und man nicht warten kann oder will, bis das Problem eventuell erkannt und gelöst wird.
 

Till

Administrator
Leider ist deine Lösung komplett falsch und sorgt dafür, dass alle möglichen Systeme in ISPConfig nicht mehr funktionieren. Du hast nicht mal ansatzweise verstanden, wie das Cron-System funktioniert und was es alles macht, dass es parallel läuft und zwingend jede Minute gestartet werden muss und dass jeder Job ein eigenes Locking in der Datenbank hat.


Ausschließlich in der von Dir modifizierten ISPConfig-Version und Installation, nicht aber auf einem Standard-ISPConfig.

Ich hatte reproduzierbar doppelte Web- und DB-Backups (physisch zwei Archive pro Laufzeitfenster auf der HDD).
Es existierte kein doppelter Cron-Eintrag in der root-crontab.

Vielleicht hast Du ja einen 2. Eintrag in /etc/cron.d angelegt? Oder ein 2. Backup-Plugin erstellt das zu einer anderen Zeit läuft oder was auch immer....

PS: Musste das auch mal loswerden weil man hier sonst immer schnell abgewatscht wird, passiert ja nur bei Dir etc. und den Unterstellungen, man würde nur immer an den Core Dateien etwas ändern. Was man natürlich auch dann macht wen die Fehler tatsächlich auch ersichtlich sind und man nicht warten kann oder will, bis das Problem eventuell erkannt und gelöst wird.

Du musstest also Dein Unwissen loswerden und dann anderen Usern Hinweise geben, damit deren Systeme auch nicht mehr richtig funktionieren?
 
Zuletzt bearbeitet:

Till

Administrator
PS: Musste das auch mal loswerden weil man hier sonst immer schnell abgewatscht wird, passiert ja nur bei Dir etc. und den Unterstellungen, man würde nur immer an den Core Dateien etwas ändern. Was man natürlich auch dann macht wen die Fehler tatsächlich auch ersichtlich sind und man nicht warten kann oder will, bis das Problem eventuell erkannt und gelöst wird.

Weißt Du was, wir machen es wie folgt. Ich setze jetzt ein neues ISPConfig-System unter Debian 13 mit dem Auto Installer auf, erstelle eine Website, E-Mail-Domain und stelle ein nächtliches Backup ein. Wenn ich dann 2 Backups pro Nacht erhalte, bekommst Du von mir eine Entschuldigung hier in Forum und ich sehe mir, woran es liegt.
 

snocer

Gesperrt

Der eigentliche Kern​

Wenn – wie gesagt – jeder Job ein eigenes Locking in der Datenbank besitzt,
dann dürfte eine parallele Ausführung von cron.sh niemals dazu führen,
dass identische Backup-Jobs ein zweites Mal physisch verarbeitet werden.

Denn genau dafür existiert Job-Locking.

Die beobachtete Realität war jedoch:
  • Überlappende cron.sh-Läufe führten zur erneuten Erstellung identischer Backup-Archive.
Das bedeutet entweder:
  1. Das Job-Locking greift in diesem Szenario nicht wie erwartet, oder
  2. Das Backup-Subsystem ist nicht vollständig re-entrant-sicher.
Das ist die technische Fragestellung.


Zum Test mit einer frischen Installation​

Ein sauber installiertes System mit kurzen Laufzeiten wird dieses Verhalten unter Umständen nicht zeigen.

Entscheidend ist jedoch nicht der Normalfall,
sondern das Verhalten bei:
  • Backup-Dauer > Cron-Intervall
  • parallelem Start von cron.sh
Wenn das interne Locking vollständig korrekt implementiert ist,
dürfte auch in diesem Szenario kein zweites Backup-Archiv entstehen.

Genau das ist der Prüfpunkt.


Ich stelle nicht das gesamte Cron-System infrage.
Ich stelle die konkrete Frage:

Warum konnten bei parallelen cron.sh-Läufen identische Backup-Archive erzeugt werden,
obwohl laut Aussage jeder Job ein eigenes Datenbank-Locking besitzt?

Das ist keine emotionale Diskussion, sondern eine technische.
 

Till

Administrator
Hier mal eine detaillierte Analyse des Codes und Deines Vorschlags:

Ich habe den gesamten Code-Pfad vom cron.sh ueber cron.php, die cronjob-Basisklasse bis hin zur backup.inc.php im Detail analysiert. Die Behauptung, dass es keine Parallelitaetssicherung im Backup-Subsystem gibt, ist nicht korrekt. ISPConfig verfuegt ueber vier unabhaengige Schutzschichten gegen doppelte Backup-Laeufe:


1. Prozess-Lock in cron.php (PID-basierte Lockdatei)
=====================================================

cron.php erstellt beim Start eine Lockdatei (.ispconfig_cron_lock) mit der eigenen Prozess-ID. Bevor ein neuer cron.php-Prozess startet, prueft er:

- Existiert die Lockdatei?
- Laeuft der darin referenzierte Prozess noch?
- Handelt es sich tatsaechlich um cron.php (Pruefung ueber /proc/<pid>/cmdline)?

Nur wenn der alte Prozess nicht mehr existiert oder es sich nicht um cron.php handelt, wird fortgefahren. Andernfalls beendet sich die neue Instanz sofort mit exit.

Relevanter Code: server/cron.php, Zeilen 35-63


2. Datenbank-basiertes Job-Locking in der cronjob-Basisklasse
==============================================================

Jeder einzelne Cronjob (einschliesslich cronjob_backup) hat einen eigenen Eintrag in der Tabelle sys_cron mit einem running-Flag. Die Methode onBeforeRun() prueft vor der Ausfuehrung:

- Ist running = 1? -> Job wird NICHT ausgefuehrt (return false)
- Ist die geplante Laufzeit noch nicht erreicht? -> Job wird NICHT ausgefuehrt

Erst wenn beide Bedingungen erfuellt sind, wird running = 1 gesetzt (via REPLACE INTO, also atomar) und der Job gestartet. Nach Abschluss setzt onCompleted() das Flag zurueck auf running = 0.

Selbst wenn also zwei cron.php-Prozesse gleichzeitig laufen wuerden (was Schicht 1 bereits verhindert), wuerde der zweite Prozess den Backup-Job nicht erneut starten, da running = 1 in der Datenbank steht.

Relevanter Code: server/lib/classes/cronjob.inc.php, Zeilen 106-170 und 193-198


3. Zeitplan-basierte Steuerung (next_run)
==========================================

Bevor ein Job ausgefuehrt wird, vergleicht onBeforeRun() die geplante naechste Laufzeit (next_run) mit der aktuellen Zeit. Nach einem erfolgreichen Lauf wird next_run auf den naechsten geplanten Zeitpunkt vorgeschoben. Ein zweiter Aufruf wuerde next_run bereits in der Zukunft vorfinden und den Job ueberspringen.

Relevanter Code: server/lib/classes/cronjob.inc.php, Zeilen 141-168


4. Taeglicher Zeitplan fuer Backups
====================================

Der Backup-Cronjob ist standardmaessig auf einmal taeglich geplant (Mitternacht, bzw. die konfigurierte backup_time). Auch wenn cron.sh jede Minute ausgefuehrt wird, loest der Zeitplan-Mechanismus den Backup-Job nur einmal pro Tag aus.

Relevanter Code: server/lib/classes/cronjob.inc.php, Zeilen 57-68


Zusammenfassung
===============

| Schicht | Mechanismus | Ort |
|---------|--------------------------------|-------------------------------|
| 1 | PID-basierte Lockdatei | cron.php, Zeilen 35-63 |
| 2 | DB running-Flag (sys_cron) | cronjob.inc.php, Zeile 138 |
| 3 | next_run Zeitplan-Pruefung | cronjob.inc.php, Zeilen 141-148 |
| 4 | Taeglicher Cron-Zeitplan | cronjob.inc.php, Zeilen 57-68 |


Zum vorgeschlagenen flock-Wrapper
=================================

Der Vorschlag, cron.sh mit flock zu wrappen, ist kontraproduktiv. cron.sh ist absichtlich so konzipiert, dass es jede Minute aufgerufen wird. Jeder einzelne Job in server/lib/classes/cron.d/ verwaltet seinen eigenen Zeitplan und sein eigenes Locking intern ueber die Datenbank. Ein globaler flock wuerde verhindern, dass andere zeitkritische Jobs (z.B. DNS-Updates, Mail-Konfiguration) rechtzeitig ausgefuehrt werden, wenn ein langlaufender Backup-Job noch aktiv ist.

Im aktuellen Release wurden keine Aenderungen an cron.php, cronjob.inc.php oder 500-backup.inc.php vorgenommen, die das Locking oder die Zeitplansteuerung betreffen. Die letzten Aenderungen an diesen Dateien waren ausschliesslich kleinere Korrekturen (Tippfehler, PHP 8.x Warnungen, Borg-Exit-Code-Behandlung).

Doppelte Backups koennen in der Regel nur auftreten, wenn der Code modifiziert wurde, die sys_cron-Tabelle manuell manipuliert wurde, oder die Lockdatei geloescht wird, waehrend cron.php laeuft. ISPConfig hat eine sehr große Installationsbasis und dieses Problem wird von keinem anderen Benutzer berichtet, was statistisch stark gegen einen Fehler in ISPConfig spricht.
 

snocer

Gesperrt
Technische Klarstellung zum Thema doppelte Backups

In meinem System existieren keine doppelten sys_cron-Einträge.
Die Tabelle sys_cron enthält pro Backup-Job genau eine Zeile (PRIMARY KEY auf name).

Die doppelte Ausführung konnte daher nicht durch doppelte Job-Definitionen entstehen.

Eine Analyse des Codes zeigt:
  • server.php nutzt .ispconfig_lock
  • cron.php nutzt .ispconfig_cron_lock

Es existieren Log-Einträge vom Typ:

Remove Lock: /usr/local/ispconfig/server/temp/.ispconfig_lock

Das bedeutet, dass Lockdateien aktiv entfernt werden.

Da beide Mechanismen dateibasiert sind und getrennt arbeiten, kann bei zeitlicher Überlappung von server.sh und cron.sh Parallelität entstehen, wenn ein Lock entfernt wird, während noch Prozesse laufen.

Das erklärt technisch nachvollziehbar:
  • keine doppelten Cron-Einträge
  • keine doppelten sys_cron-Einträge
  • aber physisch doppelte Backup-Archive

Die temporäre Serialisierung von cron.sh mittels flock hat das Verhalten stabilisiert, was auf eine Parallelitätsursache hinweist.

Diese Beobachtung steht nicht im Widerspruch zur Existenz interner Lock-Mechanismen, sondern zeigt, dass die Implementierung in bestimmten Konstellationen nicht vollständig isolierend wirkt.
 

Till

Administrator
Wie ich sehe, verwendest Du eine sehr schwache KI ohne Vollzugriff auf den Code, daher halluziniert sie so vor sich hin. Alleine schon der Vergleich server.sh zu cron.sh zeigt dies, denn server.sh kann ja nicht einmal zeitbasierte Backups ausführen und tut dies auch nicht.

Ich beende die Sache jetzt mal.
 

Till

Administrator
Ich habe dann doch nochmal ein paar Token investiert um Dir eine detaillierte codeflowbasierte Antwort zu geben:

---------------------------------------------------------------------------------------------------------------------

Deine Analyse ist leider technisch nicht korrekt. Die Schlussfolgerungen scheinen auf Annahmen zu basieren, die der tatsaechliche Code nicht stuetzt.


1. server.php und cron.php sind vollstaendig voneinander unabhaengig
=====================================================================

Ja, es gibt zwei verschiedene Lockdateien:
- server.php nutzt .ispconfig_lock
- cron.php nutzt .ispconfig_cron_lock

Diese beiden Locks haben keinerlei Wechselwirkung. Sie pruefen sich nicht gegenseitig, sie entfernen sich nicht gegenseitig, und sie muessen das auch nicht. Jedes System entfernt ausschliesslich seinen eigenen Lock beim Beenden:
- server.php Zeile 223: unlink(.ispconfig_lock)
- cron.php Zeile 128: unlink(.ispconfig_cron_lock)

Die Behauptung, dass "bei zeitlicher Ueberlappung von server.sh und cron.sh Parallelitaet entstehen kann, wenn ein Lock entfernt wird, waehrend noch Prozesse laufen", ist daher nicht zutreffend. Das Entfernen von .ispconfig_lock durch server.php hat keinerlei Einfluss auf den Cron-Lock oder umgekehrt.


2. server.php kann KEINE zeitgesteuerten (auto) Backups ausfuehren
===================================================================

Das ist der entscheidende Punkt: Zeitgesteuerte Backups laufen AUSSCHLIESSLICH ueber cron.php.

Der Code-Pfad ist:
cron.sh -> cron.php -> cronjob_backup -> backup::run_all_backups()

server.php hat keinen Code-Pfad zu backup::run_all_backups(). Das backup_plugin, das von server.php geladen wird, registriert nur folgende Aktionen:
- backup_download (Backup herunterladen)
- backup_restore (Backup wiederherstellen)
- backup_delete (Backup loeschen)
- backup_web_files (manuelles Web-Backup)
- backup_database (manuelles DB-Backup)

Die beiden Backup-Erstellungs-Aktionen (backup_web_files und backup_database) werden ausschliesslich ueber die sys_remoteaction-Tabelle ausgeloest - also nur wenn ein Benutzer in der Weboberflaeche manuell auf "Backup erstellen" klickt. Und selbst dann wird der Backup-Typ explizit als "manual" gesetzt (backup_plugin.inc.php, Zeilen 172/175), nicht als "auto".

Zusammengefasst: Selbst wenn server.php und cron.php gleichzeitig laufen (was voellig normal und beabsichtigt ist), kann server.php kein zeitgesteuertes Backup ausloesen. Es gibt schlicht keinen Code-Pfad dafuer.


3. Die bestehenden Schutzmechanismen zusammengefasst
=====================================================

Fuer die zeitgesteuerten Backups (die einzigen, die hier relevant sind):

a) cron.php hat seinen eigenen PID-basierten Prozess-Lock (.ispconfig_cron_lock)
-> Verhindert parallele cron.php-Instanzen

b) cronjob_backup hat einen DB-Lock (sys_cron Tabelle, running-Flag)
-> Selbst bei hypothetisch parallelen cron.php wuerde der zweite den Job nicht starten

c) next_run wird nach dem Start auf den naechsten Termin vorgeschoben
-> Ein zweiter Aufruf wuerde den Zeitpunkt als "noch nicht erreicht" bewerten

d) Der Backup-Zeitplan ist einmal taeglich (Mitternacht oder konfigurierte Zeit)
-> Nur zu diesem Zeitpunkt wird der Job ueberhaupt ausgeloest

Diese vier Schichten arbeiten unabhaengig voneinander und verhindern zuverlaessig doppelte Backup-Laeufe.


4. Zur flock-Loesung
=====================

Das Wrapping von cron.sh mit flock ist kontraproduktiv. cron.sh wird absichtlich jede Minute aufgerufen. Jeder Job verwaltet seinen eigenen Zeitplan ueber die Datenbank. Ein globaler flock wuerde verhindern, dass andere zeitkritische Jobs (DNS-Updates, Mail-Konfiguration, Monitoring, etc.) rechtzeitig ausgefuehrt werden, wenn ein langlaufender Backup-Job noch aktiv ist.

Wenn dein System tatsaechlich doppelte Backup-Archive erzeugt, liegt die Ursache mit an Sicherheit grenzender Wahrscheinlichkeit nicht im ISPConfig-Code, sondern in einer Modifikation des Codes oder der Systemumgebung. ISPConfig hat mehrere hunderttausend aktive Installationen und dieses Problem wird von keinem anderen Benutzer berichtet.
 

Till

Administrator
Wie im vorherigen Beitrag erlaeutert, kann server.php keine zeitgesteuerten Backups ausloesen. Der Code-Pfad existiert schlicht nicht. server.php verarbeitet Konfigurationsaenderungen ueber sys_datalog und Benutzer-Aktionen ueber sys_remoteaction. Backup-Aktionen in sys_remoteaction sind ausschliesslich manuelle Backups (ausgeloest ueber die Weboberflaeche oder API) und erzeugen Dateien mit dem Praefix "manual-".

Die Theorie, dass sich die Lock-Dateien .ispconfig_lock und .ispconfig_cron_lock gegenseitig beeinflussen, ist nicht korrekt - jedes System verwaltet ausschliesslich seinen eigenen Lock und prueft den anderen nicht.

Fuer die tatsaechliche Ursache deiner doppelten Backups gibt es folgende moegliche Erklaerungen:


1. Doppelter Cron-Eintrag fuer cron.sh
========================================

Wenn cron.sh sowohl in der crontab als auch in /etc/cron.d/ eingetragen ist, wuerden beide Instanzen nahezu gleichzeitig (im selben Sekundenbruchteil) gestartet. Der PID-basierte Lockfile-Mechanismus in cron.php arbeitet nach dem Prinzip "pruefen, dann setzen" (check-then-act). Bei einem Start im selben Moment koennten beide Prozesse die Lockdatei als nicht vorhanden lesen, bevor einer von beiden sie erstellt hat. In der Folge wuerden beide Prozesse fortfahren. Da das running-Flag in der sys_cron-Tabelle ebenfalls ueber eine separate Lese- und Schreiboperation (SELECT gefolgt von REPLACE INTO) gesetzt wird, besteht auch dort ein minimales Zeitfenster, in dem beide Prozesse running=0 lesen koennten, bevor einer den Wert auf 1 setzt. In diesem (und nur in diesem) Fall wuerden tatsaechlich zwei identische Backup-Laeufe entstehen.

Pruefe bitte:
- crontab -l (als root)
- ls -la /etc/cron.d/ (nach Eintraegen die cron.sh oder server.sh aufrufen)
- Ob es dort doppelte Eintraege gibt

flock wuerde in genau diesem Szenario helfen, weil es die zeitgleichen Starts serialisiert. Es ist aber die falsche Loesung - der richtige Fix ist, den doppelten Eintrag zu entfernen.


2. Dupliziertes Backup-Plugin in cron.d/
==========================================

Falls du den ISPConfig-Code modifiziert und dabei eine Kopie oder Variante des Backup-Plugins in server/lib/classes/cron.d/ erstellt hast (z.B. eine Datei wie 500-backup_custom.inc.php), wuerde cron.php beide Plugins laden. Da jede Kopie einen eigenen Klassennamen hat, erhaelt jede auch ihren eigenen sys_cron-Eintrag mit eigenem running-Flag und eigenem Zeitplan. Beide wuerden voellig unabhaengig voneinander Backups ausfuehren. Das wuerde auch erklaeren, warum in sys_cron "keine doppelten Eintraege" zu finden sind (sie haben unterschiedliche Namen) und trotzdem doppelte Archive entstehen.

Pruefe bitte:
- ls -la /usr/local/ispconfig/server/lib/classes/cron.d/*backup*
- Es sollte dort nur 500-backup.inc.php, 500-backup_mail.inc.php und 500-sysbackup.inc.php geben


3. Automatische Eintraege in sys_remoteaction
===============================================

Falls du per Script, API oder eigenen Code automatisch Eintraege vom Typ backup_web_files oder backup_database in die Tabelle sys_remoteaction einfuegst, wuerde server.php diese als manuelle Backups ausfuehren - zusaetzlich zum zeitgesteuerten Backup von cron.php. Das Ergebnis waeren doppelte Archive, wobei eines den Praefix "manual-" im Dateinamen tragen wuerde.

Pruefe bitte:
- Ob deine doppelten Backup-Dateien unterschiedliche Praefixe haben (mit und ohne "manual-")
- SELECT * FROM sys_remoteaction WHERE action_type LIKE 'backup%' ORDER BY tstamp DESC LIMIT 20;


Zusammenfassung
===============

Alle drei Szenarien erklaeren die beobachteten Symptome vollstaendig ohne einen Fehler im ISPConfig-Code. In jedem Fall liegt die Ursache ausserhalb des Standard-Setups. Die Ergebnisse der oben genannten Pruefungen wuerden die genaue Ursache eindeutig identifizieren.
 

Werbung

Top