Настройка бэкапа на VDS с помощью rsync

Резервное копирование экономит время, нервы и средства, поэтому для себя сейчас использую следующую схему резервного копирования на своих серверах.
Что нужно резервировать, - базы mysql, файлы веб сервера, настройки сервера, файлы пользователей, файлы рута, логи, и т.д., каждый решает сам
Схема такая: каждую ночь по крону создается дамп mysql. Он, а также требуемые каталоги в системе с помощью rsync синхронизируются на удаленный бэкап сервер. Ежедневно на бэкап сервере создается ежедневная архивная копия mysql базы и содержимого копий каталогов которые помещаются в директорию day где хранятся N дней, раз в неделю из day самый новый архивный файл копируется в директорию week где хранится месяц, раз в месяц из week самый новый файл копируется в директорию month.

Такая схема создана для экономии места. Можно хранить все архивы, это потребует соответствующий объем дискового пространства.
Создание бэкапа фиксируется в лог файле, так же отправляется информация на мэйл админу. При копировании информации на бэкап сервер используется ssh, авторизация по ключу и ограничение на разрешенные команды на удаленном хосте(бэкап сервере ).

На основном сервере используем скрипт примерно следующего содержания:

#! /bin/sh
# путь к файлу, содержащему список исключений при резервировании
EXCLUDE=/backup/exclude_path
# каталог для сохранения дампа mysql
DST=/backup/backups
# лог файл
LOG=/var/log/backup/backup.log
# путь к файлу отчета для отправки на почту
MAILROOT=/backup/backups/temp/mail2root.msg
# под каким именем логиниться на бэкап сервер
DSTUSER=backup
# ip адрес бэкап сервера
DSTHOST=xxx.yyy.zzz.qqq
# каталог на бэкап сервере для сохранения бэкапа
DSTPATH=/home/backup/servername
# имя скрипта для log файлов
SCRIPTNAME="files.back.sh(cron)"
# текст для лог файлов
MSGSQL="have created"
MSG2="have backuped to remote host"
# пользователь бд
USER='user'
# пароль для доступа к mysql
PASSWD='passwd'

# функция RSYNC имеет один параметр - каталог, резервную копию которого сохраняем
RSYNC ()
{
rsync -avz --rsync-path="sudo rsync" --exclude-from=$EXCLUDE -e ssh $1 $DSTUSER@$DSTHOST:$DSTPATH
####
}

# функция CREATEMSG используется для проверки результата выполнения предыдущей команды
# и создания\отправки отчета. Имеет один параметр - часть лог сообщения. См. строку с "echo"
# чтобы было понятно
CREATEMSG ()
{
if [ $? = 0 ]
then
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D INFO: $SCRIPTNAME: $1 seccessfully" >> $LOG
echo "$D INFO: $SCRIPTNAME: $1 seccessfully" >> $MAILROOT
else
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D WARNING: $SCRIPTNAME: $1 unseccessfully" >> $LOG
echo "$D WARNING: $SCRIPTNAME: $1 unseccessfully" >> $MAILROOT
fi
}

# создаем запись в логе и файле отчета с фиксацией времени начала
echo Report of rsync backup started on `date` > $LOG
echo Report of rsync backup started on `date` > $MAILROOT

# создание mysql дампа
mysqldump -u $USER -p$PASSWD --all-databases --events --lock-all-tables > $DST/mysql/mysql.all.sql
CREATEMSG "mysql dump $MSGSQL"

# последовательное выполнение RSYNC для копирования на бэкап сервер и CREATEMSG для создания отчета
RSYNC /etc
CREATEMSG "/etc $MSG2"
RSYNC /usr/share/nginx/html
CREATEMSG "/usr/share/nginx/html $MSG2"
RSYNC /var/log
CREATEMSG "/var/log $MSG2"
RSYNC /root
CREATEMSG "/root $MSG2"
RSYNC $DST/mysql
CREATEMSG "mysql dump $MSG2"

## отправка emeil из файла, записанного в $MAILROOT
#
D=`date '+%Y-%m-%d'`
cat $MAILROOT | mail -s "Cron report about backups to remote host $D" root
if [ $? -eq 0 ]
# проверка факта отправки mail
# удаляем лог файл или пишем сообщение в системный лог
then rm -rf $MAILROOT
else echo "$D WARNING: $SCRIPTNAME can not send mail to root" >> /var/log/messages
fi
#

Этот скрипт запускаем по крону ночью.
Чтобы не росли лог файлы настраиваем ротацию в /etc/logrotate.d ( Centos ) или в соответствующем месте для Debian и др.
Файл с исключениями: на всякий случай я исключил из бэкапа файл passwd, shadow, приватные ключи и конфиги из /etc/ssh и /etc/ssl

На бэкап сервере создаем пользователя backup с домашним каталогом /home/backup.
Создаем каталоги для хранения резервных копий:

mkdir -p /home/backup/servername/tgz/mysql
cd /home/backup/servername/tgz/mysql
mkdir day week month
mkdir -p /home/backup/servername/tgz/etc
cd /home/backup/servername/tgz/etc
mkdir day week month

Создаем скрипты для создания ежедневных архивов, еженедельных и ежемесячных.
Скрипт для создания ежедневного резервного архива, запускается по крону каждую ночь, примерно следующего содержания:

#! /bin/bash
# Каталог где хранятся бэкапы сервера servername
WORKDIR=/home/backup/servername
# Каталоги в которые сохраняются ежедневные архивы, их надо создать
DSTDIRM=tgz/mysql/day
DSTDIRE=tgz/etc/day
# Имя скрипта, используется при создании логов
SCRIPTNAME="scriptname.day.sh(cron)"
# Расположение лог файла
LOG=/var/log/backup/backup.log
# Расположение файла из которого отправляем отчет на мэйл
MAILROOT=/home/backup/temp/mailroot.msg
# Сколько дней хранятся ежедневные бэкапы
NUMDAY=15
# email на который слать отчет
EMAIL=you@mail.com

# функция CREATEMSG проверяет результат выполнения последней операции и создает запись в лог файл
# и в файл для отчета по email, имеет один параметр - часть сообщения, см ниже
CREATEMSG ()
{
if [ $? = 0 ]
then
D1=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D1 INFO: $SCRIPTNAME: $1 seccessfully" >> $LOG
echo "$D1 INFO: $SCRIPTNAME: $1 seccessfully" >> $MAILROOT
else
D1=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D1 WARNING: $SCRIPTNAME: $1 unseccessfully" >> $LOG
echo "$D1 WARNING: $SCRIPTNAME: $1 unseccessfully" >> $MAILROOT
fi
}

# Создаем файл для отправки отчета по почте, в нем фиксируем время начала операции
echo Report about creating daily etc and mysql archives started on `date '+%Y-%m-%d_%H:%M:%S'` > $MAILROOT

cd $WORKDIR
D=`date '+%Y.%m.%d'`

# создание ежедневного архива
tar czpf $DSTDIRE/etc.$D.tgz etc
CREATEMSG "Creating tgz archive of backup /etc directory on remote host was"

cd mysql
tar czpf ../$DSTDIRM/mysql.all.$D.tgz mysql.all.sql
CREATEMSG "Creating tgz archive of backup mysql dump on remote host was"

# удаление файлов из каталога с архивами старше чем $NUMDAY days
find $WORKDIR/$DSTDIRE -type f -mtime +$NUMDAY -delete
find $WORKDIR/$DSTDIRM -type f -mtime +$NUMDAY -delete

# получение размера каталога с архивами для создания\отправки отчета
SIZE=`du -h --max-depth=1 $WORKDIR/tgz | tail -n 1 | cut -f 1 -d '/'`
echo "$D INFO: $SCRIPTNAME: size of tgz directory on remote host is $SIZE" >> $LOG
echo "$D INFO: $SCRIPTNAME: size of tgz directory on remote host is $SIZE" >> $MAILROOT

# отправка отчета на мэйл
D=`date '+%Y-%m-%d'`
cat $MAILROOT | mail -s "Cron report about creating tgz archive of backups on remote host $D" $EMAIL
# если удачно - удаляем отчет, если нет - записываем это в системный лог
if [ $? -eq 0 ]
then rm -rf $MAILROOT
else echo "$D1 WARNING: $SCRIPTNAME can not send mail to $EMAIL" >> /var/log/messages
fi

Скрипт для создания еженедельных копий архивов, запускается раз в неделю, ночью (сб-вс), примерно такой:

#! /bin/sh
#
PATH=/bin:/sbin:/usr/bin:/usr/sbin
# Рабочие каталоги для etc и mysql
WORKDIRE=/home/backup/servername/tgz/etc
WORKDIRM=/home/backup/servername/tgz/mysql
# начальный и конечный каталоги
SRCDIR=day
DSTDIR=week
# имя скрипта, используется в логах и email сообщениях
SCRIPTNAME="scriptname.week.(cron)"
# путь к логу и email сообщению
LOG=/var/log/backup/backup.log
MAILROOT=/home/backup/mail2root.msg
# кол-во дней для хранения ф-лов в каталоге DSTDIR
MAXDAYS=32
# email на который слать отчет
EMAIL=you@mail.com
# создание ф-ла для емэйл сообщения с фиксацией времени начала работы скрипта
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "Report about creating weekly archive of backups on remote host started on $D" > /$MAILROOT

# функция для копирования ф-лов из '$SRCDIR' в '$DSTDIR'.
# Использует один параметр - имя рабочего каталога
CPTOWEEK ()
{
cd $1/$SRCDIR
# получение имени самого старого ф-ла в каталоге '$SRCDIR'
OLDER=`ls -tr | tail -n 1`
# копирование самого старого ф-ла из '$SRCDIR' в 'DSTDIR'
cp $1/$SRCDIR/$OLDER $1/$DSTDIR/
# проверка результата выполнения предыдущей команды с записью в лог и файл для email
if [ $? = 0 ]
then
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D INFO: $SCRIPTNAME: $1 have create a weekly backup seccessfully" >> $LOG
echo "$D INFO: $SCRIPTNAME: $1 have create a weekly backup seccessfully" >> $MAILROOT
else
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D WARNING: $SCRIPTNAME: $1 have not create a weekly backup" >> $LOG
echo "$D WARNING: $SCRIPTNAME: $1 have not create a weekly backup" >> $MAILROOT
fi
#
# поиск и удаление файлов старше чем MAXDAYS в '$DSTDIR'
find $1/$DSTDIR -type f -mtime +$MAXDAYS -delete
# получение общего размера файлов в каталоге $DSTDIR для отчета
SIZE=`du -h --max-depth=1 $1/$DSTDIR | tail -n 1 | cut -f 1 -d '/'`
echo "$D INFO: $SCRIPTNAME: size of $1/$DSTDIR directory is $SIZE" >> $LOG
echo "$D INFO: $SCRIPTNAME: size of $1/$DSTDIR directory is $SIZE" >> $MAILROOT
}
# выполнение функции CPTOWEEK для etc и mysql
CPTOWEEK $WORKDIRE
CPTOWEEK $WORKDIRM

# отправка emeil из ф-ла $MAILROOT и создание записи в системном логе если отправка неудачна
D=`date '+%Y-%m-%d'`
cat $MAILROOT | mail -s "Cron report about creating weekly copy of backup archives on remote host $D" $EMAIL
if [ $? -eq 0 ]
# удаление файла с текстом для email сообщения
then rm -rf $MAILROOT
else echo "$D1 WARNING: $SCRIPTNAME can not send mail to $EMAIL" >> /var/log/messages
fi

Скрипт для создания ежемесячных копий архивов, запускается раз в месяц, что-то вроде этого:

#! /bin/sh
#
PATH=/bin:/sbin:/usr/bin:/usr/sbin
# Рабочие каталоги для etc и mysql
WORKDIRE=/home/backup/servername/tgz/etc
WORKDIRM=/home/backup/servername/tgz/mysql
# начальный и конечный каталоги
SRCDIR=week
DSTDIR=month
# имя скрипта, используется в логах и email сообщениях
SCRIPTNAME="scriptname.month.sh(cron)"
# путь к логу и email сообщению
LOG=/var/log/backup/backup.log
MAILROOT=/home/backup/temp/mail2root.msg1
# кол-во дней для хранения ф-лов в каталоге DSTDIR
MAXDAYS=366
# email на который слать отчет
EMAIL=you@mail.com
# создание ф-ла для емэйл сообщения с фиксацией времени начала работы скрипта
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "Report about creating weekly archive of backups on remote host started on $D" > /$MAILROOT

# функция для копирования ф-лов из '$SRCDIR' в '$DSTDIR'.
# Использует один параметр - имя рабочего каталога
CPTOWEEK ()
{
cd $1/$SRCDIR
# получение имени самого старого ф-ла в каталоге '$SRCDIR'
OLDER=`ls -tr | tail -n 1`
# копирование самого старого ф-ла из '$SRCDIR' в 'DSTDIR'
cp $1/$SRCDIR/$OLDER $1/$DSTDIR/
# проверка результата выполнения предыдущей команды с записью в лог и файл для email
if [ $? = 0 ]
then
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D INFO: $SCRIPTNAME: $1 have create a monthly backup seccessfully" >> $LOG
echo "$D INFO: $SCRIPTNAME: $1 have create a monthly backup seccessfully" >> $MAILROOT
else
D=`date '+%Y-%m-%d_%H:%M:%S'`
echo "$D WARNING: $SCRIPTNAME: $1 have not create a monthly backup" >> $LOG
echo "$D WARNING: $SCRIPTNAME: $1 have not create a monthly backup" >> $MAILROOT
fi
#
# поиск и удаление файлов старше чем MAXDAYS в '$DSTDIR'
find $1/$DSTDIR -type f -mtime +$MAXDAYS -delete
# получение общего размера файлов в каталоге $DSTDIR для отчета
SIZE=`du -h --max-depth=1 $1/$DSTDIR | tail -n 1 | cut -f 1 -d '/'`
echo "$D INFO: $SCRIPTNAME: size of $1/$DSTDIR directory is $SIZE" >> $LOG
echo "$D INFO: $SCRIPTNAME: size of $1/$DSTDIR directory is $SIZE" >> $MAILROOT
}
# выполнение ф-ции CPTOWEEK для etc и mysql
CPTOWEEK $WORKDIRE
CPTOWEEK $WORKDIRM

# отправка emeil из ф-ла $MAILROOT и создание записи в системном логе если отправка неудачна
D=`date '+%Y-%m-%d'`
cat $MAILROOT | mail -s "Cron report about creating monthly copy of backup archives on remote host $D" $EMAIL
if [ $? -eq 0 ]
# удаление файла с текстом для email сообщения
then rm -rf $MAILROOT
else echo "$D1 WARNING: $SCRIPTNAME can not send mail to $EMAIL" >> /var/log/messages
fi

Для копирования файлов на бэкап сервер с помощью rsync используется ssh, чтобы не хранить пароль в файле скрипта, используем авторизацию по ключу.
Для этого создаем ключ и копируем его на бэкап сервер:

ssh-keygen -t rsa -b 2048
ssh-copy-id backup@server

что копирует публичный ключ в .ssh/authorized_keys пользователя backup
Для повышения безопасности добавляем ограничения в authorized_keys для этого ключа, в начало строки добавляем
from="aaa.bbb.ccc.ddd",command="/home/backup/valid.rsync" где valid.rsync - файл с правами 500 - команда, разрешенная для выполнения при подключении к бэкап серверу с использованием этого ключа. Это скрипт, проверяющий выполняющуюся через ssh команду и отбрасывающий все кроме 'rsync --server...'( что можно увидеть при выполнении rsync с опциями vvv) Содержимое valid.rsync, что-то вроде этого:

#!/bin/sh

 case "$SSH_ORIGINAL_COMMAND" in
 *\&*)
 echo "Rejected"
 ;;
 *\(*)
 echo "Rejected"
 ;;
 *\{*)
 echo "Rejected"
 ;;
 *\;*)
 echo "Rejected"
 ;;
 *\<*)
 echo "Rejected"
 ;;
 *\`*)
 echo "Rejected"
 ;;
 *\|*)
 echo "Rejected"
 ;;
 rsync\ --server*)
 $SSH_ORIGINAL_COMMAND
 ;;
 *)
 echo "Rejected"
 ;;
 esac

Взято отсюда: http://troy.jdmz.net/rsync/index.html

Если пользователь backup с помощью rsync на бэкап сервере создает бэкап, принимающийся с основного сервера, то он устанавливает владельца и группу backup:backup. Для сохранения исходных владельца и группы, в скрипте запуска rsync на основном сервере используется опция --rsync-path="sudo rsync", что запускает rsync на бэкап сервере через sudo. Для исключения хранения пароля в файле скрипта, пользователю backup на бэкап сервере разрешено выполнять команду sudo rsync без ввода пароля. Для этого в /etc/sudoers:

Cmnd_Alias RSYNC = /usr/bin/rsync
backup        ALL = NOPASSWD: RSYNC

При выполнении скриптов через крон вывод поступает на мэйл пользователю, заданному в настройках заданий cron. Попытался отправлять на /dev/null , т.к. скрипты генерируют отчеты на мэйл самостоятельно, но при моей настройке postfix чтобы это заработало приходится устанавливать append_at_myorigin=no что не рекомендуется документацией. Поэтому в /etc/sysconfig/crond установил CRONDARGS="-m=off" для отключения отправки вывода на мэйл.