Sauvegarde NAS avec php et rsync

Il s'agit d'un script php écrit sur le principe que la mise en miroir et la sauvegarde de gestion de la génération sont effectuées avec rsync pour l'environnement suivant où des disques durs indépendants pour le partage et la sauvegarde du réseau sont montés sur un NAS construit avec Xubuntu et Samba. Je pense que cela peut s'appliquer même si la configuration est légèrement différente.

** Drive pour NAS ** Juste en dessous de la route / data /… Pour les lecteurs réseau Créez un répertoire appelé. Montez ceci /home/nas/ Monté sur /home/nas/data/ Utilisé comme lecteur réseau NAS en définissant samba sur un répertoire partagé.

** Lecteur de sauvegarde ** Juste en dessous de la route / data /… Pour la mise en miroir / generation /… Pour la sauvegarde de la gestion de la génération Créez un répertoire appelé. Montez ceci /home/nas_backup/ En montant sur /home/nas_backup/data/ La destination de mise en miroir de / home / nas / data /, /home/nas_backup/generation/ Est la destination de sauvegarde de génération de / home / nas_backup / data /.

Il peut être difficile de saisir l'image avec des lettres, mais cela ressemble à ceci sur la figure. blockimage.jpg

Veuillez noter que la sauvegarde de la gestion de la génération utilise l'option --link-dest de rsync qui utilise des liens physiques, de sorte que le lecteur cible doit être un système de fichiers capable de gérer les liens physiques sans problème, il sera donc complet à chaque sauvegarde de gestion de génération Cela prendra une consommation de disque et un temps de traitement équivalents à la sauvegarde. Dans mon cas, j'utilise ext4.


scénario

mirroring.php Ce script utilise le répertoire partagé du NAS comme source de sauvegarde et reflète le lecteur de destination de sauvegarde à l'aide de l'option --delete de rsync.

mirroring.php


<?php
/**
 *mise en miroir rsync
 */

//Mise en miroir du répertoire source
define('SOURCE_DIR', '/home/nas/data/');

//Répertoire de destination de la mise en miroir
define('BACKUP_DIR', '/home/nas_backup/data/');

//Autres exemples d'options rsync: '--exclude=/temp/ --exclude=/*.bak';
define('OTHER_OPTIONS', '');

/**
 *
 */

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

//Répertoire de sauvegarde des fichiers temporaires
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);

//Correction de la délimitation de chaque nom de répertoire
$sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/');
$backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/');

//Terminer s'il n'y a pas de source de sauvegarde / destination de sauvegarde
if(!file_exists($sourceDir) || strpos($backupDir, ':') === false && !file_exists($backupDir)) {
    print "The source '{$sourceDir}' or backup '{$backupDir}' destination directory does not exist.\n";
    exit;
}

//Vérifiez l'utilisation du disque source de sauvegarde, et s'il n'y a pas de changement par rapport à l'heure précédente, terminez sans rien faire
//Cependant, la taille du bloc peut ne pas changer lors du changement de nom ou de la mise à jour d'une petite taille.
//Si plus d'une heure s'est écoulée depuis la dernière mise en miroir, la mise en miroir sera effectuée indépendamment du changement de taille de bloc.
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;

//Verrouiller le nom du fichier
$lockFilename = TEMP_DIR. '/backup.lock';

//Si le fichier de verrouillage existe, on considère que le processus portant le même nom est en cours d'exécution et se termine.
if(file_exists($lockFilename)) {
    print "A process with the same name is running.\n";
    exit;
} else {
    //Verrouiller la création de fichiers
    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);
}

//Mise à jour des informations enregistrée dans le fichier tmp
//Dans le cas de la mise en miroir, le nombre de blocs utilisés dans la source de sauvegarde
$temps['prev_used_size'] = $usedSize;
setTmpFile($tempFile, $temps);

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

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

//Supprimer le fichier de verrouillage
unlink($lockFilename);

exit;

/**
 *
 */

//Obtenir le fichier tmp
function getTmpFile($fn) {
    if(file_exists($fn)) {
        $tmp = file_get_contents($fn);
        return(json_decode($tmp, true));
    }
    return [];
}

//enregistrement du fichier tmp
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);
    }
}

//Obtenir le répertoire de mise à jour
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);
}

//Convertir le résultat de la commande du en tableau
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;
}

Si la capacité du disque source de sauvegarde n'a pas changé depuis la dernière exécution, rsync n'est pas effectué et il se termine, donc je pense que la charge ne deviendra pas extrêmement élevée même si elle est exécutée fréquemment, mais l'environnement autour de cela Veuillez ajuster en fonction de. Le contrôle de capacité utilise la commande df et ne peut pas détecter les mises à jour qui ne modifient pas la taille du bloc, telles que les changements de nom de fichier ou de petite taille, donc si plus d'une heure s'est écoulée depuis la dernière exécution, la capacité du disque source de sauvegarde n'a pas changé. Mais j'essaye d'exécuter rsync.

** Principaux éléments de réglage **

//Mise en miroir du répertoire source
define('SOURCE_DIR', '/home/nas/data/');

Spécifiez le répertoire comme source de mise en miroir.

//Répertoire de destination de la mise en miroir
define('BACKUP_DIR', '/home/nas_backup/data/');

Spécifiez le répertoire à mettre en miroir. Vous pouvez également spécifier la télécommande en incluant "nom d'utilisateur @ nom d'hôte:" etc. au début.

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

Lorsque remote est spécifié, [Public key authentication login] sans mot de passe (https://akebi.jp/temp/ssh-keygen.html) afin qu'il n'y ait pas d'attente pour la saisie du mot de passe lors de la connexion à la télécommande pendant l'exécution automatique avec cron. ) Doit être réglé de manière appropriée.


generation.php Il s'agit d'un script qui sauvegarde le répertoire de gestion de génération basé sur le répertoire en miroir à l'aide de l'option --link-dest de rsync. Si le lecteur de sauvegarde se trouve sur un emplacement distant autre que le NAS lui-même, installez également ce script du côté distant.

generation.php


<?php
/**
 *sauvegarde de la génération rsync
 */

//Répertoire source de la sauvegarde
define('SOURCE_DIR', '/home/nas_backup/data/');

//Répertoire de destination de la sauvegarde
define('BACKUP_DIR', '/home/nas_backup/generation/');

//Autres exemples d'options rsync: '--exclude=/temp/ --exclude=/*.bak';
define('OTHER_OPTIONS', '');

//Nombre de générations de sauvegarde
define('BACKUP_GENERATION', 200);

//Seuil de capacité du disque pour supprimer les anciennes sauvegardes(%)
//S'il est égal à 0, la capacité du disque n'est pas vérifiée.
define('THRESHOLD', 95);

/**
 *
 */

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

//Répertoire de sauvegarde des fichiers temporaires
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);
}

//Correction de la délimitation de chaque nom de répertoire
$sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/');
$backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/');

//Terminer s'il n'y a pas de source de sauvegarde / destination de sauvegarde
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');

//Verrouiller le nom du fichier
$lockFilename = TEMP_DIR. '/backup.lock';

//Si le fichier de verrouillage existe, il est considéré que le processus du même nom est en cours d'exécution et attend jusqu'à 2 minutes, et s'il n'est pas libéré pendant ce temps, il se termine.
$time = time();
while(file_exists($lockFilename)) {
    sleep(1);
    if($time + 120 < time()) {
        print "A process with the same name is running.\n";
        exit;
    }
}
//Verrouiller la création de fichiers
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);

//Obtenir le nom du répertoire sauvegardé
$backupList = getBackupList($backupDir);

//Affiner les anciennes sauvegardes
$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";

    //Si plus d'un mois s'est écoulé, supprimez les autres que le dernier du mois
    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);
        }
    }
    //Si plus d'un jour s'est écoulé, supprimez les autres que le dernier de la journée
    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);
        }
    }
}
//Réacquérir le nom du répertoire sauvegardé
$backupList = getBackupList($backupDir);

//Supprimez les anciennes sauvegardes jusqu'à ce que l'utilisation du disque tombe en dessous du pourcentage spécifié
sort($backupList);
while(THRESHOLD && checkPercentage($backupDir) && count($backupList) > 1) {
    $command = "rm -rf {$backupDir}{$backupList[0]}";
    array_shift($backupList);
    print "$command\n";
    exec($command);
}

//Si vous disposez d'une sauvegarde de génération existante
if(count($backupList)) {
    rsort($backupList);
    //Supprimer les sauvegardes qui dépassent le nombre de générations enregistrées parmi les plus anciennes
    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);
        }
    }
}

//Nouveau nom du répertoire de sauvegarde
$backupName = "{$nowDate}/";

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

//Réacquérir le nom du répertoire sauvegardé
$backupList = getBackupList($backupDir);
//Obtenez uniquement le journal à la différence de la sauvegarde d'il y a une génération
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]}");
}

//Supprimer le fichier de verrouillage
unlink($lockFilename);

exit;

/**
 *
 */

//Obtenir le nom du répertoire de sauvegarde existant
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;
}

//Supprimer la sauvegarde
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;
    }
}

//Vérification de l'utilisation du disque
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;
}

Créez un répertoire nommé d'après la date et l'heure d'exécution et conservez-y une sauvegarde à ce moment-là. En utilisant l'option --link-dest de rsync, seuls les fichiers nouvellement ajoutés ou modifiés sont enregistrés en tant qu'entité, et les autres fichiers ajoutent uniquement des liens physiques, donc la consommation d'espace disque et le temps de traitement sont incrémentés. Bien qu'elle soit similaire à une sauvegarde, chaque sauvegarde créée équivaut à une sauvegarde complète. Les sauvegardes de plus d'un jour sont supprimées en ne laissant que la version finale du jour, les sauvegardes de plus d'un mois sont supprimées en quittant la version finale du mois, et les anciennes sauvegardes jusqu'à ce que l'utilisation du disque spécifiée par THRESHOLD soit atteinte. Le traitement tel que la suppression de est également effectué avec ce script.

Afin d'utiliser efficacement les liens physiques, il est courant de spécifier une sauvegarde d'il y a une génération pour --link-dest, mais dans ce cas, $ sourceDir lui-même fait partie de la sauvegarde déjà mise en miroir, alors ici-- Il est spécifié dans link-dest. En faisant cela, vous pouvez économiser de l'espace et réduire la vitesse de traitement en même temps.

** Principaux éléments de réglage **

//Répertoire source de la sauvegarde
define('SOURCE_DIR', '/home/nas_backup/data/');

Spécifiez le répertoire de destination de la mise en miroir dans mirroring.php.

//Répertoire de destination de la sauvegarde
define('BACKUP_DIR', '/home/nas_backup/generation/');

Spécifiez la destination de sauvegarde de la gestion de la génération. Sous ce répertoire YYYY-MM-DD_HHMM Un répertoire est créé au format et les sauvegardes de chaque génération y sont enregistrées. En utilisant l'option --link-dest de rsync, un fichier qui ne change pas créera un lien dur au lieu d'un fichier réel, donc il ne consommera pas plus d'espace disque que nécessaire.

//Nombre de générations de sauvegarde
define('BACKUP_GENERATION', 200);

Spécifiez le nombre de générations que vous souhaitez enregistrer. Si le nombre de sauvegardes de génération dépasse cette valeur, les sauvegardes les plus anciennes seront supprimées, mais en raison de l'équilibre entre le traitement de réduction et le traitement de suppression en raison de la capacité du disque, la suppression peut être effectuée avant que le nombre spécifié ici ne soit atteint.

//Seuil de capacité du disque pour supprimer les anciennes sauvegardes(%)
//S'il est égal à 0, la capacité du disque n'est pas vérifiée.
define('THRESHOLD', 95);

Vérifiez l'utilisation du disque (%) de la destination de sauvegarde avec la commande df, et si cette valeur est atteinte, supprimez la sauvegarde la plus ancienne dans l'ordre jusqu'à ce qu'elle tombe en dessous de la valeur. S'il est à 0, le processus de suppression ne sera pas exécuté, mais même s'il n'y a pas assez d'espace libre sur la destination de sauvegarde, le traitement tel que la suppression de l'exécution de rsync ne sera pas effectué.


exemple de configuration crontab

# rsync mirroring
* * * * * php /Chemin d'installation du script/mirroring.php &> /dev/null
* * * * * sleep 30; php /Chemin d'installation du script/mirroring.php &> /dev/null

# rsync generation backup
0 */6 * * * php /Chemin d'installation du script/generation.php &> /dev/null

Dans l'exemple ci-dessus, la mise en miroir est effectuée toutes les 30 secondes dans le premier demi-bloc et la sauvegarde de gestion de la génération est effectuée toutes les 6 heures dans le second demi-bloc.

Recommended Posts

Sauvegarde NAS avec php et rsync
Avec et sans WSGI
Avec moi, cp et sous-processus
Programmation avec Python et Tkinter
Chiffrement et déchiffrement avec Python
Travailler avec le tkinter et la souris
Super résolution avec SRGAN et ESRGAN
Group_by avec sqlalchemy et sum
python avec pyenv et venv
Avec moi, NER et Flair
Fonctionne avec Python et R