DB-Backup per Cronjob bei Domainfactory

Heute möchte ich mal kurz beschreiben, wie ich ein Datenbank-Backup bei meinem Provider domainfactory eingerichtet habe. Ich habe einen Shared-Hosting-Tarif mit SSH-Zugang. Wenn man keinen SSH-Zugang hat, dann klappt der hier beschriebene Vorgang natürlich nicht. Wenn man einen komplett eigenen Server hat, ist das hier auch nicht relevant, denn dann hat man ja die volle Kontrolle über das System.

Das Problem

Prinzipiell sollte das Vorgehen klar sein: Das Programm „mysqldump“ erstellt den Datenbank-Dump, ausgelöst über einen Cronjob. Der Cronjob selber kann auf ein Shell-Script zeigen oder auf ein PHP-Programm. Die Schwierigkeit: Wenn das Backup erstellt wird, sitzt man ja selber nicht vor dem Rechner und kann deswegen das Datenbank-Passwort nicht eintippen. Es ist aber auch keine gute Idee, dieses Passwort einfach in den Scriptaufruf im Cronjob reinzuschreiben (was problemlos ginge), denn dann hat jeder Zugang zur Datenbank, der die Befehlshistorie des Systems oder die laufenden Prozesse anschauen darf. Das Passwort muss also irgendwie anders gelagert werden, und da kommt das Programm „mysql_config_editor“ ins Spiel.

Mit „mysql_config_editor“ kann man den Datenbank-Zugang in einer verschlüsselten Datei namens “.mylogin.cnf“ im eigenen Home-Verzeichnis ablegen. Andere MySQL-Programme können die dort abgelegten Daten dann auslesen. Auf diese Weise steht im Programmaufruf nur ein Verweis auf diese Konfiguration und nicht Nutzername und Passwort selber. Wer die Datei lesen darf, hat dann natürlich wiederum Zugang zur Datenbank. Aber wer meine Dateien lesen darf auf dem Server, hat sowieso Zugang zu allem, denn die Zugangsdaten stehen für WordPress ja auch in einer Konfigurationsdatei.

Vorgehen für das Backup

Zuerst legen wir pro Datenbank eine verschlüsselte Konfiguration an. Dazu einfach über Putty (SSH) aufrufen:

mysql_config_editor set --login-path=appdb_02 --host=mysql5.freudendahl.net --user=db123456_1 --password

Hier müsst ihr folgendes angeben: Einen beliebigen kurzen Namen als „login-path“ (dieser wird dann später im mysqldump-Aufruf stehen), den Servernamen für den MySQL-Server (bei domainfactory bestehend aus MySQL-Version und der Domain) und den Nutzernamen für den Zugriff auf die Datenbank. Letzteres ist auch der Datenbank-Name. Dann natürlich das Passwort noch eingeben. Im Home-Verzeichnis sollte nun die “.mylogin.cnf“-Datei auftauchen. Es ist eine versteckte Datei, deswegen sieht man sie nur mit „ls -lsa“.

Nun brauchen wir einen Ordner für die Backups. Wichtig: Legt diesen Ordner nicht an einer Stelle an, wo er über das Web erreichbar ist! Bei domainfactory sieht die Ordnerstruktur z.B. so aus: Alle meine Dateien befinden sich unterhalb von „/kunden/123456_04711″, alle meine Webseiten-Daten liegen wiederum im Unterorder „/kunden/123456_04711/webseiten“. Der Ordner für die Backups kommt nun auf die oberste erreichbare Ebene neben „webseiten“, also unter „/kunden/123456_04711/backups“. Damit kann sich niemand die dort abgelegten Dateien einfach aus dem Netz herunterladen.

Erstellt euch diesen Ordner und legt darin die Scriptdatei für das Backup ab. Ihr könnt wahlweise ein Shellscript schreiben oder das z.B. mit PHP lösen. Da mir die Befehle zum Durchrotieren der Backups (Wochentag im Dateinamen) in PHP geläufiger sind, habe ich diesen Weg gewählt. Da sich der Ordner außerhalb des Web-Roots befindet, habe ich noch eine php.ini-Datei dazugelegt, da sich bei mir sonst relativ zufällig eine php.ini-Datei von einer meiner Webseiten auswirkte, was nicht gewünscht war. Das Script „db_backup.php“ sieht so aus:

<?php
# Backups erstellen für diese Datenbanken:
$db_configs = array(
	'appdb_01' 	=> 'db123456',
	'appdb_02' 	=> 'db123456_1'
);

# DB-Dump ausführen
foreach ( $db_configs as $config_name => $db_name )
{
	# Kommando ausführen
	$target_file = __DIR__ . "/db_{$db_name}_" . strtolower( date( 'D' ) ) . '.sql.gz';
	$command = "mysqldump --login-path={$config_name} --host=mysql5.freudendahl.net --default-character-set=utf8"
		. "	{$db_name} | gzip > {$target_file}";
	$output = array();
	$return_var = null;
	exec( $command, $output, $return_var );
	echo $output;
	
	# Dateigröße ermitteln
	$bytes = filesize( $target_file );
	$available_sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
    $factor = floor( ( strlen( $bytes ) - 1 ) / 3 );
	$size = sprintf( "%.2f", $bytes / pow( 1024, $factor ) ) . @$available_sizes[$factor];
	
	# Ergebnis loggen	
	echo "Backup done for {$db_name}: {$return_var} (file size: {$size})\n";
	if ( $return_var !== 0 )
	{
		error_log( "Fehler bei Backup für {$db_name}: {$return_var}" );
		exit( 1 );
	}
}
exit( 0 );

In meinem Fall werden hier zwei Datenbanken in jeweils eine Datei gedumpt. Der Dateiname enthält den Wochentag, d.h. am achten Tag wird die Datei des ersten Tages überschrieben. Auf diese Weise hat man mehr als einen Tag rückwärts ein Backup vorliegen, ohne sich die Platte zu sehr zuzumüllen oder manuell Backups entfernen zu müssen.

Anzupassen ist bei euch folgendes: In das Array $db_configs tragt ihr den vorher vergebenen Login-Path ein, gemappt auf den Namen des Datenbank-Nutzers. In dem $command-Aufruf müsst ihr zudem den Namen des MySQL-Servers eintragen. Der Aufruf zippt die Datei im übrigen, damit sie nicht unnötig Platz verbraucht. Entpacken kann man die entstehende .gz-Datei z.B. mit dem Programm 7zip.

Die Lösung für die Domainfactory-Umgebung

Das entstandene Script kann man einfach über SSH ausführen und wird dann ein passendes Backup bekommen. Nun trägt man das Script im Interface von domainfactory als Cronjob ein, testet diesen und stellt fest, dass die erhaltene Datei 0 KB groß ist (bzw. 4 KB wegen Overhead des Zippens). Was ist passiert? Irgendwie klappt die Benutzung des MySQL-Passworts über die “.mylogin.cnf“ nicht.

Die Ursache liegt wohl darin, wie mit diesem „Fake-Cron“ bei domainfactory die Cronjobs ausgeführt werden. Irgendwie laufen die Cronjobs als mein SSH-Nutzer (entstandene Dateien gehören jedenfalls diesem Nutzer), aber nicht so, dass MySQL versteht, in welcher “.mylogin.cnf“-Datei es das Passwort auslesen soll. Genauer kann ich das leider ohne Detailkenntnisse des DF-Systems nicht beschreiben.

Nach ein, zwei Mails mit dem wie immer hilfreichen Support von domainfactory habe ich nun folgendes an der obigen Lösung verändert:

1. Ich habe im DF-Backend nicht mehr direkt die PHP-Datei als Cronjob eingetragen, sondern ein daneben liegendes Shellscript „cronjobs.sh“ mit folgendem Inhalt:

#!/bin/bash
/usr/bin/env -i /usr/local/bin/php7.1.6-cli -c /kunden/123456_04711/backups/php.ini /kunden/123456_04711/backups/db_backup.php

Dieser Aufruf sorgt dafür, dass das Script mit einer frischen Umgebung ohne fremde Umgebungsvariablen aufgerufen wird (wegen dem „-i“). Außerdem verwende ich eine definierte PHP-Version und gebe die vorhin mit angelegte php.ini-Datei explizit an (sicher ist sicher). Letztere kann man sich übrigens über das DF-Backend zusammenklicken, herunterladen und ggf. noch um eigene Einträge z.B. zum Errorlogging ergänzen. Dass hier eine PHP-Version hartcodiert ist, gefällt mir dagegen nicht. Bei Gelegenheit muss ich noch herausfinden, wie ich das durch „PHP7-STABLE“ ersetzen kann.

2. Und jetzt zur wichtigsten Änderung: Das Programm „mysqldump“ lässt sich auch durch die neue Art des Aufrufs nicht dazu bringen, die “.mylogin.cnf“-Datei korrekt auszulesen. Deswegen braucht es im PHP-Script (ganz oben) noch einen Trick: Man kann „mysqldump“ über eine Umgebungsvariable einen konkreten Ort für die Datei mitgeben.

# für die Ausführung via Cron noch den Pfad zur .mylogin.ncf-Datei setzen
putenv( "MYSQL_TEST_LOGIN_FILE=/kunden/123456_04711/.mylogin.cnf" );

Dem Namen und dem dämlichen Handling als Umgebungsvariable anstatt als Aufruf-Parameter nach zu urteilen ist das nur für Testzwecke gedacht. Es funktioniert jedoch und steht auch unter Shared-Hosting-Bedingungen nur dem eigenen Script zur Verfügung. Insofern habe ich damit kein Problem. Wichtig ist aber natürlich, dass auf der cnf-Datei im Home-Verzeichnis niemand außer dem eigenen Benutzer Leserechte hat, sonst könnte das ja auch ein anderer Nutzer auf der gleichen Servermaschine aufrufen!

Und siehe da: Mit diesen Änderungen klappt nun auch der Aufruf als Cronjob! Fortan muss man natürlich auch dran denken, diese Backups ab und zu mal herunterzuladen. Wessen finanzielle Existenz an seiner Webseite hängt, wird das sicher anders lösen. Aber für eine Hobbyseite mit wenigen Beiträgen pro Monat reicht es völlig, einmal im Quartal das Backup auf dem Laptop zu sichern. Die WordPress-Uploads sind dann ebenfalls manuell herunterzuziehen, da dieses Backup nur die Datenbank erfasst.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Bitte beachte die Kommentarregeln: 1) Kein Spam, und bitte höflich bleiben. 2) Ins Namensfeld gehört ein Name. Gerne ein Pseudonym, aber bitte keine Keywords. 3) Keine kommerziellen Links, außer es hat Bezug zum Beitrag. mehr Details...

So, noch mal kurz drüber schauen und dann nichts wie ab damit. Vielen Dank fürs Kommentieren! :-)