Преди има няма месец, бях изправен пред „проблема“ с архивирането, а именно – удобен начин, за архивиране на важната информация. След известно време ровене в Интернет попаднах на една статия Създаване на резервни копия с TAR. Интересно беше не толкова самата статия, колкото един от коментарите към нея, на Георги Андонов, който разясняваше няколко примерни „политики на архивиране“.
Накратко, знаем че с tar могат да се създават инкрементални архиви. Хубав пример за това какво представляват инкременталните архиви е следното:
Имаме една директория с множество файлове в нея. Архивираме я с tar и полученият архив е с големина 10 MB. След време тази директория променя съдържанието си – добавени са нови файлове например. Вместо да я архивираме отново по същият начин, за да имаме актуално резервно копие, ние създаваме инкрементален архив, на база създаденият вече от нас архив. Новият архив, ще съдържа само разликите в периода от създаването на първият архив до създаването на инкременталният архив. Хубавото тук е, че понеже архивираме само новите неща, големината на този втори архив ще зависи само от това колко големи са разликите и ако те са малки, архива също ще е малък.
Въпроса е какво печелим, архивирайки по този начин:
време – когато трябва да архивираме по-малко файлове времето за създаването на архива е по-малко.
дисково пространство – тъй като архивираме само разликите, пространство което ще ни е необходимо да ги запазим е по-малко в сравнение с това да архивираме отново цялата директория.
Недостатък тук е процеса по възстановяване на информацията. Ако възникне проблем, първо трябва да разархивираме първият архив и след това вторият, който ще промени вече разархивираният първи архив, така че да възстановим директорията до състоянието и в което сме я архивирали последно.
Сега вече се намесват и „политиките на архивиране“, те имат за цел да ни предложат стратегия, която да използваме при създаването на инкрементални архиви, така че съотношението между време за архивиране, големина на архивите и време за възстановяване да е максимално удобна за нас. Във въпросният коментар бяха показани няколко примерни стратегии, аз се спрях на първата – Месечна.
Числото в скоби е седмицата от месеца. Горе са показани дните от седмицата, а номерата за всеки ден показват от кое „ниво“ е инкременталният архив.
Ето какво пише авторът на коментара за тази стратегия:
П В С Ч П С Н
(1) 0
(2) 2 2 2 2 1
(3) 3 3 3 3 2
(4) 4 4 4 4 3
(5) 5 5 5 5 4
Относно „Нивата“:
Ниво 0 е пълен архив. Нива 1, 2, 3, … (обикновено до 9) са инкрементални. Те не са точно дефинирани. Използват се за съставяне на „подходяща за нас“ стратегия. Всеки архив от ниво Х съдържа само данните, променени от последния архив на Ниво Х или по-малък от Х.
Пример:
- Понеделник: Архив ниво 0 (Пълен архив)
- Вторник: Архив ниво 3 (Данните променени от Понеделник до сега)
- Сряда: Архив ниво 5 (Данните променени от Вторник до сега)
- Четвъртък: Архив ниво 2 (Данните променени от Понеделник до сега)
Ето и самата стратегия:
- Първият петък на месеца се прави пълен архив.
- Дневните архиви съдържат данните променени в текущия ден.
- Седмичните архиви (Петък) съдържат данните променени в текущата седмица.
- Събота и неделя не се правят архиви
Възстановяване при срив:
Най-лошият случай е „Срив в последния петък на месеца“. Възстановяването на системата изглежда така:
- Архив 0 от първия Петък.
- Архив 1 от втория Петък.
- Архив 2 от третия Петък.
- Архив 3 от четвъртия Петък.
- Архив 5 от последния Понеделник.
- Архив 5 от последния Вторник.
- Архив 5 от последната Сряда.
- Архив 5 от последния Четвъртък.
Данните от последния Петък са загубени.
Предимства и недостатъци:
- Бързо се създават архивите.
- Бавно се възстановяват.
И така, след като реших какво ще използвам за архивиране, реших да напиша кратък php скрипт, който да автоматизира описаното. Резултата е скрипта показан по-долу. Преди всичко, трябва да се променят константите в началото на файла, така че да отговарят на системата, на която ще работи скрипта. Всички константи започващи с BIN_ задават пътя до използваните програми. MYSQL_ROOT_PASSWORD съдържа root паролата за досътп до mysql, използва се за да се включи в архива и копие на базата от данни. Скрипта разчита на това, че имате създадена директорията /var/sqldumps/ в която да съхрани dump-а от базата. BACKUP_INCLUDE сочи до текстов файл в който на всеки нов ред е описана директорията, която искаме да архивираме. По този начин можем да зададем списък от директории и/или файлове, които искаме да се включат в архива ни. BACKUP_EXCLUDE е подобен на BACKUP_INCLUDE, разликата е, че пази пътя до файловете, които не искаме да влизат в архива. Например ако имаме директория, в която искаме да архивираме всичко, с изключение на един файл, вместо да изреждаме всички файлове от директорията в BACKUP_INCLUDE, по-добре ще е да добавим самата директория, а в BACKUP_EXCLUDE да посочим файла, който не искаме да се архивира. BACKUP_LOCATION е директорията в която ще се направи архива.
Примерен BACKUP_INCLUDE файл:
/var/www/vhosts
/var/spool/postfix/virtual
/var/ftpd
/var/log
/var/sqldumps
Първото стартиране на скрипта трябва да се направи ръчно с параметър –init, който ще генерира всички архиви от началото на месеца. След това можете да го извиквате без параметър, най-добре през cron.
backup.php
[geshi lang=php]
define("BIN_MYSQLDUMP", "/usr/local/bin/mysqldump");
define("BIN_TAR", "/usr/local/bin/gtar");
define("BIN_CP", "/bin/cp");
define("BIN_RM", "/bin/rm");
define("MYSQL_ROOT_PASSWORD", "myrootpassword");
define("BACKUP_INCLUDE", "/root/etc/backup.include");
define("BACKUP_EXCLUDE", "/root/etc/backup.exclude");
define("BACKUP_LOCATION", "/var/backup");
function usage(){
echo "Usage: backup.php [--init]\n";
echo "Create backup for today if no parameters are passed.\n";
echo "--init Creates all backups from the beginig of the month until today\n";
exit();
}
function mysql_dump(){
exec(BIN_MYSQLDUMP . " -u root -A -p" . MYSQL_ROOT_PASSWORD . " > /var/sqldumps/mysql_backup.sql“);
}
function copy_snar($snar_old, $snar_new){
exec(BIN_CP . “ /var/backup/$snar_old /var/backup/$snar_new“);
}
function tar_exec($archive_name, $snar_name){
exec(BIN_TAR . “ –create \
–gunzip \
–exclude-from=“ . BACKUP_EXCLUDE . “ \
–files-from=“ . BACKUP_INCLUDE . “ \
–file=“ . BACKUP_LOCATION . „/$archive_name \
–listed-incremental=“ . BACKUP_LOCATION . „/$snar_name“);
}
function rm_exec(){
exec(BIN_RM . “ -rf “ . BACKUP_LOCATION . „/*.snar“);
}
function backup($date = false){
if ($date == false) {
$date = date(‘d’);
}
// get the day of the week
$day = date(‘N’, mktime(0, 0, 1, date(‘m’), $date));
// get the first friday
$first_day_of_the_month = date(‘j’, mktime(0, 0, 0, date(‘m’), 1));
switch (date(‘N’, mktime(0, 0, 1, date(‘m’), $first_day_of_the_month))){
case 1: $number_of_days_to_friday = 4; break;
case 2: $number_of_days_to_friday = 3; break;
case 3: $number_of_days_to_friday = 2; break;
case 4: $number_of_days_to_friday = 1; break;
case 5: $number_of_days_to_friday = 0; break;
case 6: $number_of_days_to_friday = 6; break;
case 7: $number_of_days_to_friday = 5; break;
}
$first_friday = $first_day_of_the_month + $number_of_days_to_friday;
// get the week
$first_week = date(‘W’, mktime(0, 0, 0, date(‘m’), $first_friday));
$todays_week = date(‘W’, mktime(0, 0, 0, date(‘m’), $date));
$week = $todays_week – $first_week + 1;
// callculate the tar level
$level = $week;
if ($day == 5){
$level = $level – 1;
}elseif ($week == 1){
$level = 5;
}
$archive_name = date(‘Ymd’, mktime(0, 0,0 , date(‘m’), $date)) . ‘.’ . $level . ‘.tar.gz’;
$snar_name = $level . ‘.snar’;
// copy snar files only on monday and friday
if ($day == 1) {
$snar_old_name = $level – 2 . ‘.snar’;
copy_snar($snar_old_name, $snar_name);
}else if ($day == 5 && $first_friday != $date) {
$snar_old_name = $level – 1 . ‘.snar’;
copy_snar($snar_old_name, $snar_name);
}else if ($day == 5 && $first_friday == $date) { // remove all snar files
rm_exec();
}
// create archives only in working days
if ($day >= 1 && $day <= 5) {
tar_exec($archive_name, $snar_name);
}
}
// start
if (isset($argv[1]) && $argv[1] == ‘–init’) {
mysql_dump();
$first_day_of_the_month = date(‘j’, mktime(0, 0, 0, date(‘m’), 1));
switch (date(‘N’, mktime(0, 0, 1, date(‘m’), $first_day_of_the_month))){
case 1: $number_of_days_to_friday = 4; break;
case 2: $number_of_days_to_friday = 3; break;
case 3: $number_of_days_to_friday = 2; break;
case 4: $number_of_days_to_friday = 1; break;
case 5: $number_of_days_to_friday = 0; break;
case 6: $number_of_days_to_friday = 6; break;
case 7: $number_of_days_to_friday = 5; break;
}
$first_friday = $first_day_of_the_month + $number_of_days_to_friday;
for ($i = $first_friday; $i <= date(‘j’); $i++){
backup($i);
}
}else if (!isset($argv[1])) {
mysql_dump();
backup();
}else {
usage();
}
[/geshi]