[LINUX] NAS-Backup mit PHP und Rsync

Es ist ein PHP-Skript, das unter der Voraussetzung geschrieben wurde, dass Spiegelungs- und Generierungsverwaltungssicherungen mit rsync für die folgende Umgebung durchgeführt werden, in der unabhängige Festplatten für die Netzwerkfreigabe und -sicherung auf einem mit Xubuntu und Samba erstellten NAS bereitgestellt werden. Ich denke, es kann angewendet werden, auch wenn die Konfiguration etwas anders ist.

** Laufwerk für NAS ** Gleich unterhalb der Route / data /… Für Netzlaufwerke Erstellen Sie ein Verzeichnis mit dem Namen. Montieren Sie dies /home/nas/ Befestigt auf /home/nas/data/ Wird als NAS-Netzwerklaufwerk verwendet, indem Samba auf ein freigegebenes Verzeichnis gesetzt wird.

** Sicherungslaufwerk ** Gleich unterhalb der Route / data /… Zum Spiegeln / generation /… Für die Sicherung des Generierungsmanagements Erstellen Sie ein Verzeichnis mit dem Namen. Montieren Sie dies /home/nas_backup/ Durch Montage auf /home/nas_backup/data/ Das Spiegelungsziel von / home / nas / data /, /home/nas_backup/generation/ Ist das Generierungssicherungsziel von / home / nas_backup / data /.

Es mag schwierig sein, das Bild mit Buchstaben zu erfassen, aber in der Abbildung sieht es so aus. blockimage.jpg

Beachten Sie, dass Generierungsverwaltungssicherungen die Option --link-dest von rsync verwenden, die Hardlinks nutzt. Daher muss das Ziellaufwerk ein Dateisystem sein, das Hardlinks problemlos verarbeiten kann, sodass es jedes Mal voll ist, wenn eine Generierungsverwaltungssicherung durchgeführt wird. Es wird viel Festplattenverbrauch und Verarbeitungszeit in Anspruch nehmen, was einer Sicherung entspricht. In meinem Fall verwende ich ext4.


Skript

mirroring.php Dieses Skript verwendet das freigegebene NAS-Verzeichnis als Sicherungsquelle und spiegelt das Sicherungsziellaufwerk mit der Option --delete von rsync.

mirroring.php


<?php
/**
 *Rsync-Spiegelung
 */

//Quellverzeichnis spiegeln
define('SOURCE_DIR', '/home/nas/data/');

//Zielverzeichnis spiegeln
define('BACKUP_DIR', '/home/nas_backup/data/');

//Andere Beispiele für rsync-Optionen: '--exclude=/temp/ --exclude=/*.bak';
define('OTHER_OPTIONS', '');

/**
 *
 */

set_time_limit(0);
date_default_timezone_set('Asia/Tokyo');

//Verzeichnis zum Speichern temporärer Dateien
define('TEMP_DIR', (file_exists('/dev/shm/') ? '/dev/shm/.' : '/var/tmp/.'). md5(__DIR__));
if(!file_exists(TEMP_DIR)) {
    mkdir(TEMP_DIR);
    chmod(TEMP_DIR, 0700);
}

$tempFile = TEMP_DIR. '/mirroring.tmp';
$temps = getTmpFile($tempFile);

//Begrenzungskorrektur jedes Verzeichnisnamens
$sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/');
$backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/');

//Beenden Sie, wenn keine Sicherungsquelle / kein Sicherungsziel vorhanden ist
if(!file_exists($sourceDir) || strpos($backupDir, ':') === false && !file_exists($backupDir)) {
    print "The source '{$sourceDir}' or backup '{$backupDir}' destination directory does not exist.\n";
    exit;
}

//Überprüfen Sie die Verwendung der Sicherungsquellendiskette. Wenn sich gegenüber dem vorherigen Zeitpunkt nichts geändert hat, beenden Sie den Vorgang, ohne etwas zu tun
//Die Blockgröße darf sich jedoch beim Umbenennen oder Aktualisieren einer kleinen Größe nicht ändern.
//Wenn seit der letzten Spiegelung mehr als 1 Stunde vergangen ist, wird die Spiegelung unabhängig von der Änderung der Blockgröße durchgeführt.
exec("df {$sourceDir}", $ret);
$usedSize = (preg_split('/\s+/', $ret[1]))[2];
$prevUsedSize = isset($temps['prev_used_size']) ? (time() - filemtime($tempFile) < 3600 ? $temps['prev_used_size'] : 0) : 0;
if($usedSize == $prevUsedSize) exit;

//Dateiname sperren
$lockFilename = TEMP_DIR. '/backup.lock';

//Wenn die Sperrdatei vorhanden ist, wird davon ausgegangen, dass der gleichnamige Prozess ausgeführt wird und endet.
if(file_exists($lockFilename)) {
    print "A process with the same name is running.\n";
    exit;
} else {
    //Dateierstellung sperren
    if(!@file_put_contents($lockFilename, 'Process is running.')) {
        print "Could not create `$lockFilename`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n";
        exit;
    }
    chmod($lockFilename, 0600);
}

//Informationsaktualisierung in tmp-Datei gespeichert
//Bei der Spiegelung die Anzahl der in der Sicherungsquelle verwendeten Blöcke
$temps['prev_used_size'] = $usedSize;
setTmpFile($tempFile, $temps);

$updateDirList = getUpdataDirList($sourceDir);
if(!$updateDirList) {
    $updateDirList[] = $sourceDir;
}

foreach($updateDirList as $dir) {
    $path = str_replace($sourceDir, '', $dir);
    //Befehl rsync
    $command = implode(" ", [
            'rsync -avH',
            '--delete',
            OTHER_OPTIONS,
            '"'. preg_replace('|/+$|', '/', ($sourceDir. $path. '/')). '"',
            '"'. preg_replace('|/+$|', '/', ($backupDir. $path. '/')). '"',
        ]);
    print "$command\n";
    exec($command);
}

//Sperrdatei löschen
unlink($lockFilename);

exit;

/**
 *
 */

//Holen Sie sich tmp-Datei
function getTmpFile($fn) {
    if(file_exists($fn)) {
        $tmp = file_get_contents($fn);
        return(json_decode($tmp, true));
    }
    return [];
}

//tmp datei speichern
function setTmpFile($fn, $temps) {
    if(getTmpFile($fn) != json_encode($temps)) {
        if(!@file_put_contents($fn, json_encode($temps))) {
            print "Could not create `$fn`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n";
            exit;
        }
        chmod($fn, 0600);
    }
}

//Update-Verzeichnis abrufen
function getUpdataDirList($sourceDir) {
    $duFile = TEMP_DIR. '/prev_du.txt';
    $prevDirList = duToArray($duFile);

    exec("du {$sourceDir} > {$duFile}");
    chmod($duFile, 0600);
    $dirList = duToArray($duFile);

    $tmpArr = [];
    foreach($dirList as $k => $v) {
        if(isset($prevDirList[$k]) && $prevDirList[$k] != $v) $tmpArr[$k] = $v;
    }
    unset($prevDirList, $dirList);

    $retArr = $tmpArr;
    foreach($tmpArr as $k => $v) {
        foreach($tmpArr as $k_ => $v_) {
            if($k == $k_) continue;
            if(isset($retArr[$k]) && strpos($k_, $k) === 0) unset($retArr[$k]);
        }
    }
    return array_keys($retArr);
}

//Konvertieren Sie das Ergebnis des Befehls du in ein Array
function duToArray($duFile) {
    $retArr = [];
    if(file_exists($duFile)) {
        if($fp = @fopen($duFile, 'r')) {
            while(($l = fgets($fp)) !== false) {
                $l = trim($l);
                if(!$l) continue;
                $l = explode("\t", $l);
                $retArr[$l[1]] = $l[0];
            }
            fclose($fp);
        }
    }
    return $retArr;
}

Wenn sich die Kapazität der Sicherungsquellendiskette seit der letzten Ausführung nicht geändert hat, wird rsync nicht ausgeführt und endet. Daher denke ich, dass die Last nicht extrem hoch wird, selbst wenn sie häufig ausgeführt wird, sondern in der Umgebung Bitte entsprechend einstellen. Die Kapazitätsprüfung verwendet den Befehl df und kann keine Aktualisierungen erkennen, die die Blockgröße nicht ändern, z. B. Änderungen des Dateinamens oder kleine Größen. Wenn also seit der letzten Ausführung mehr als 1 Stunde vergangen ist, hat sich die Kapazität der Sicherungsquellendiskette nicht geändert. Aber ich versuche, rsync auszuführen.

** Haupteinstellungselemente **

//Quellverzeichnis spiegeln
define('SOURCE_DIR', '/home/nas/data/');

Geben Sie das Verzeichnis an, das die Spiegelungsquelle sein soll.

//Zielverzeichnis spiegeln
define('BACKUP_DIR', '/home/nas_backup/data/');

Geben Sie das zu spiegelnde Verzeichnis an. Sie können auch die Fernbedienung angeben, einschließlich "Benutzername @ Hostname:" usw. am Anfang.

define('BACKUP_DIR', 'username@hostname:/home/username/data/');

Wenn remote angegeben ist, [Authentifizierung mit öffentlichem Schlüssel] ohne Kennwort (https://akebi.jp/temp/ssh-keygen.html), damit beim Anmelden bei der Remote während der automatischen Ausführung mit cron nicht auf die Eingabe des Kennworts gewartet werden muss ) Muss entsprechend eingestellt werden.


generation.php Dies ist ein Skript, das das Generierungsverwaltungsverzeichnis basierend auf dem gespiegelten Verzeichnis mit der Option --link-dest von rsync sichert. Wenn sich das Sicherungslaufwerk an einem anderen Remotestandort als dem NAS selbst befindet, installieren Sie dieses Skript auch auf der Remoteseite.

generation.php


<?php
/**
 *rsync-Generationssicherung
 */

//Quellverzeichnis sichern
define('SOURCE_DIR', '/home/nas_backup/data/');

//Zielverzeichnis sichern
define('BACKUP_DIR', '/home/nas_backup/generation/');

//Andere Beispiele für rsync-Optionen: '--exclude=/temp/ --exclude=/*.bak';
define('OTHER_OPTIONS', '');

//Anzahl der Backup-Generationen
define('BACKUP_GENERATION', 200);

//Schwellenwert für die Festplattenkapazität zum Löschen alter Sicherungen(%)
//Wenn es 0 ist, wird die Festplattenkapazität nicht überprüft.
define('THRESHOLD', 95);

/**
 *
 */

set_time_limit(0);
date_default_timezone_set('Asia/Tokyo');

//Verzeichnis zum Speichern temporärer Dateien
define('TEMP_DIR', (file_exists('/dev/shm/') ? '/dev/shm/.' : '/var/tmp/.'). md5(__DIR__));
if(!file_exists(TEMP_DIR)) {
    mkdir(TEMP_DIR);
    chmod(TEMP_DIR, 0700);
}

//Begrenzungskorrektur jedes Verzeichnisnamens
$sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/');
$backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/');

//Beenden Sie, wenn keine Sicherungsquelle / kein Sicherungsziel vorhanden ist
if(!file_exists($sourceDir) || !file_exists($backupDir)) {
    print "The source '{$sourceDir}' or backup '{$backupDir}' destination directory does not exist.\n";
    exit;
}

$nowDate = date('Y-m-d_Hi');

//Dateiname sperren
$lockFilename = TEMP_DIR. '/backup.lock';

//Wenn die Sperrdatei vorhanden ist, wird davon ausgegangen, dass der gleichnamige Prozess ausgeführt wird und bis zu 2 Minuten wartet. Wenn er während dieser Zeit nicht freigegeben wird, wird er beendet.
$time = time();
while(file_exists($lockFilename)) {
    sleep(1);
    if($time + 120 < time()) {
        print "A process with the same name is running.\n";
        exit;
    }
}
//Dateierstellung sperren
if(!@file_put_contents($lockFilename, 'Process is running.')) {
    print "Could not create `$lockFilename`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n";
    exit;
}
chmod($lockFilename, 0600);

//Sichern Sie den Namen des Verzeichnisses
$backupList = getBackupList($backupDir);

//Dünne alte Backups aus
$processed = [];
foreach($backupList as $backupName) {
    if(!preg_match('/^(\d{4})-(\d\d)-(\d\d)_(\d\d)(\d\d)/', $backupName, $m) || isset($processed[$backupName])) continue;
    list($year, $month, $day, $hour, $minute) = array_slice($m, 1);
    $fDate = "$year-$month-$day $hour:$minute";

    //Wenn mehr als ein Monat vergangen ist, löschen Sie die anderen als den letzten des Monats
    if(time() >= strtotime("$fDate +1 month")) {
        $pickup = [];
        foreach($backupList as $tmp) {
            if(substr($tmp, 0, 7) == "{$year}-{$month}" && substr($tmp, 0, 10) <= "{$year}-{$month}-{$day}") $pickup[] = $tmp;
        }
        rsort($pickup);
        foreach(array_slice($pickup, 1) as $tmp) {
            deleteBackup($backupDir, $tmp, $processed);
        }
    }
    //Wenn mehr als ein Tag vergangen ist, löschen Sie die anderen als den letzten des Tages
    elseif(time() >= strtotime("$fDate +1 day")) {
        $pickup = [];
        foreach($backupList as $tmp) {
            if(substr($tmp, 0, 10) == "{$year}-{$month}-{$day}" && $tmp <= $backupName) $pickup[] = $tmp;
        }
        rsort($pickup);
        foreach(array_slice($pickup, 1) as $tmp) {
            deleteBackup($backupDir, $tmp, $processed);
        }
    }
}
//Ermitteln Sie den Namen des gesicherten Verzeichnisses erneut
$backupList = getBackupList($backupDir);

//Löschen Sie alte Sicherungen, bis die Festplattennutzung unter den angegebenen Prozentsatz fällt
sort($backupList);
while(THRESHOLD && checkPercentage($backupDir) && count($backupList) > 1) {
    $command = "rm -rf {$backupDir}{$backupList[0]}";
    array_shift($backupList);
    print "$command\n";
    exec($command);
}

//Wenn Sie eine vorhandene Generationssicherung haben
if(count($backupList)) {
    rsort($backupList);
    //Löschen Sie Sicherungen, die die Anzahl der gespeicherten Generationen der ältesten überschreiten
    if(count($backupList) >= BACKUP_GENERATION) {
        $delNames = array_slice($backupList, BACKUP_GENERATION -1);
        foreach($delNames as $del) {
            $command = "rm -rf {$backupDir}{$del}";
            print "$command\n";
            exec($command);
        }
    }
}

//Neuer Name des Sicherungsverzeichnisses
$backupName = "{$nowDate}/";

//Befehl rsync
$command = implode(" ", [
        "rsync -avH",
        OTHER_OPTIONS,
        "--link-dest={$sourceDir}",
        $sourceDir,
        sprintf("%s%s", $backupDir, $backupName),
    ]);
print "$command\n";
exec($command);

//Ermitteln Sie den Namen des gesicherten Verzeichnisses erneut
$backupList = getBackupList($backupDir);
//Holen Sie sich nur das Protokoll, das sich von der Sicherung vor einer Generation unterscheidet
if(count($backupList) > 1) {
    rsort($backupList);
    $command = "rsync -avHn --delete --exclude=/_rsync.log {$backupDir}{$backupList[0]}/ {$backupDir}{$backupList[1]}/ > {$backupDir}_rsync.log";
    exec($command);
    exec("mv {$backupDir}_rsync.log {$backupDir}{$backupList[0]}");
}

//Sperrdatei löschen
unlink($lockFilename);

exit;

/**
 *
 */

//Namen des vorhandenen Sicherungsverzeichnisses abrufen
function getBackupList($backupDir) {
    $backupList = [];
    if($dir = opendir($backupDir)) {
        while($fn = readdir($dir)) {
            if(preg_match('/^\w{4}-\w{2}-\w{2}_\w{4,6}$/', $fn) && is_dir("{$backupDir}{$fn}")) {
                $backupList[] = $fn;
            }
        }
        closedir($dir);
    }
    return $backupList;
}

//Backup löschen
function deleteBackup($backupDir, $str, &$processed) {
    if(isset($processed[$str])) return;
    if(file_exists("{$backupDir}{$str}")) {
        $command = "rm -rf {$backupDir}{$str}";
        print"$command\n";
        exec($command);
        $processed[$str] = 1;
    }
}

//Überprüfung der Festplattennutzung
function checkPercentage($backupDir) {
    exec("df {$backupDir}", $ret);
    if(!isset($ret[1])) return false;
    if(preg_match('/(\d+)\%/', $ret[1], $ret)) {
        if($ret[1] >= THRESHOLD) return true;
    }
    return false;
}

Erstellen Sie ein Verzeichnis, das nach dem Ausführungsdatum und der Ausführungszeit benannt ist, und führen Sie zu diesem Zeitpunkt eine Sicherungskopie darin. Bei Verwendung der Option --link-dest von rsync werden nur neu hinzugefügte oder geänderte Dateien als Entität gespeichert, und andere Dateien fügen nur feste Links hinzu, sodass der Speicherplatzverbrauch und die Verarbeitungszeit erhöht werden. Obwohl es einer Sicherung ähnlich ist, entspricht jede erstellte Sicherung einer vollständigen Sicherung. Sicherungen, die älter als 1 Tag sind, werden gelöscht, wobei nur die endgültige Version des Tages übrig bleibt. Sicherungen, die älter als 1 Monat sind, werden gelöscht, wobei die endgültige Version des Monats übrig bleibt, und alte Sicherungen, bis die von THRESHOLD angegebene Festplattennutzung erreicht ist. Mit diesem Skript wird auch eine Verarbeitung wie das Löschen aus ausgeführt.

Um Hardlinks effektiv nutzen zu können, ist es üblich, vor einer Generation ein Backup für --link-dest anzugeben. In diesem Fall ist $ sourceDir selbst Teil des bereits gespiegelten Backups. Es wird in link-dest angegeben. Auf diese Weise können Sie gleichzeitig Platz sparen und die Verarbeitungsgeschwindigkeit reduzieren.

** Haupteinstellungselemente **

//Quellverzeichnis sichern
define('SOURCE_DIR', '/home/nas_backup/data/');

Geben Sie das Spiegelungszielverzeichnis in mirror.php an.

//Zielverzeichnis sichern
define('BACKUP_DIR', '/home/nas_backup/generation/');

Geben Sie das Sicherungsziel für die Generierungsverwaltungssicherung an. Unter diesem Verzeichnis YYYY-MM-DD_HHMM Im Format wird ein Verzeichnis erstellt, in dem Sicherungen jeder Generation gespeichert werden. Wenn Sie die Option --link-dest von rsync verwenden, wird für eine Datei, die sich nicht ändert, ein fester Link anstelle einer tatsächlichen Datei erstellt, sodass nicht mehr Speicherplatz als erforderlich belegt wird.

//Anzahl der Backup-Generationen
define('BACKUP_GENERATION', 200);

Geben Sie die Anzahl der Generationen an, die Sie speichern möchten. Wenn die Anzahl der Generierungssicherungen diesen Wert überschreitet, werden die ältesten Sicherungen gelöscht. Aufgrund des Gleichgewichts zwischen Ausdünnungsverarbeitung und Löschverarbeitung aufgrund der Festplattenkapazität kann das Löschen jedoch durchgeführt werden, bevor die hier angegebene Anzahl erreicht ist.

//Schwellenwert für die Festplattenkapazität zum Löschen alter Sicherungen(%)
//Wenn es 0 ist, wird die Festplattenkapazität nicht überprüft.
define('THRESHOLD', 95);

Überprüfen Sie die Festplattennutzung (%) des Sicherungsziels mit dem Befehl df. Wenn dieser Wert erreicht ist, löschen Sie die älteste Sicherung in der angegebenen Reihenfolge, bis sie unter den Wert fällt. Wenn es 0 ist, wird der Löschvorgang nicht ausgeführt, aber selbst wenn nicht genügend freier Speicherplatz auf dem Sicherungsziel vorhanden ist, wird keine Verarbeitung wie das Unterdrücken der Ausführung von rsync ausgeführt.


Beispiel für eine Crontab-Konfiguration

# rsync mirroring
* * * * * php /Skriptinstallationspfad/mirroring.php &> /dev/null
* * * * * sleep 30; php /Skriptinstallationspfad/mirroring.php &> /dev/null

# rsync generation backup
0 */6 * * * php /Skriptinstallationspfad/generation.php &> /dev/null

Im obigen Beispiel wird die Spiegelung im ersten halben Block alle 30 Sekunden durchgeführt, und im zweiten halben Block wird alle 6 Stunden eine Sicherung des Generierungsmanagements durchgeführt.

Recommended Posts

NAS-Backup mit PHP und Rsync
Mit und ohne WSGI
Bei mir cp und Subprocess
Programmieren mit Python und Tkinter
Ver- und Entschlüsselung mit Python
Arbeiten Sie mit tkinter und Maus
Super Auflösung mit SRGAN und ESRGAN
Group_by mit sqlalchemy und sum
Python mit Pyenv und Venv
Mit mir, NER und Flair
Funktioniert mit Python und R.