Полную резервную копию сервера снимать не имеет смысла, а стоит снимать резервные копии только самого важного, что может пригодиться при настройке сервера с нуля или для восстановления после ошибок. В случае аварий, когда бывает нужно восстановить что-то из резервной копии, меньше всего хочется бороться с какой-то системой резервного копирования, поэтому вместо какой-то готовой системы лучше ограничиться скриптом. При небольшом количестве серверов и небольшом объёме резервных копий скрипт, снимающий резервные копии последовательно, успевает полностью отработать за несколько часов.
Поэтому и в тот раз я решил воспользоваться тем же подходом. Единственное дополнение, которое захотелось сделать - это класть резервные копии сразу в два географически разнесённых хранилища. Одна беда - блог я тогда ещё не вёл и не имел привычки выкладывать куда-то полезные наработки, поэтому скрипт на новом рабочем месте пришлось писать заново. Сейчас подумал, что неплохо всё-таки привести этот скрипт в презентабельный вид и написать о нём статью.
Управление архивом
Начнём с того, что нужно организовать резервные копии в хранилище таким образом, чтобы можно было легко найти нужную резервную копию и удалять устаревшие резервные копии. Файлы будем именовать по схеме YYYYMMDD_filename. При добавлении новой резервной копии filename нужно будет удостовериться, что новый файл не пуст. Если с файлом всё в порядке, то можно снабдить его префиксом YYYYMMDD_, а затем найти и удалить устаревшие резервные копии этого файла. Для выполнения этих функций в скрипте предусмотрены переменная KEEP_DAYS и функция с не самым удачным названием clear_old, которая берёт на себя описанные задачи:KEEP_DAYS=14 clear_old() { # $1 - backup filename # Если файл с указанным именем не существует if [ ! -f "$1" ] then return 0 fi size=`du --bytes "$1" | cut -f1` # Если размер меньше 512 байт if [ $size -lt 512 ] then # Удаляем сам файл резервной, а старые резервные копии не трогаем rm $1 return 1 else # Переименовываем новый файл резервной копии mv $1 `date "+%Y%m%d"`_$1 # Удаляем предыдущие файлы старше KEEP_DAYS дней find . -name \*$1 -mtime +$KEEP_DAYS -delete return 0 fi }Чтобы добавить файл в архив, нужно вызвать функцию clear_old и передать ей имя добавляемого в архив файла.
Резервное копирование локальных файлов
Начнём с простого. Резервные копии нужно снимать не только с других компьютеров, но и с самого компьютера, на котором будет работать скрипт. В моём случае в разное время это был сервер FreeBSD и виртуальная машина с Linux. В дальнейшем скрипт был перемещён на выделенную виртуальную машину с Linux, которая выполняла только функции резервного копирования. Острая необходимость снимать резервные копии самой виртуальной машины, где работал скрипт резервного копирования, отпала, но функции для этого уже были разработаны, поэтому на всякий случай резервные копии локальных файлов продолжают сниматься.В случае с FreeBSD функция резервного копирования файлов выглядела следующим образом:
BACKUP_USER=rbackup BACKUP_GROUP=rbackup freebsd_files_local() { # $1 - backup filename touch "$1" chown $BACKUP_USER:$BACKUP_GROUP "$1" chmod 0600 "$1" tar -cjf - -T- --exclude '*.sql.gz' --exclude 'etc/zabbix/xbackup/*' <<END 2>/dev/null > "$1" /etc/ /usr/local/etc/ /root/ /usr/home/ /usr/local/www/ END clear_old "$1" if [ $? -ne 0 ] then echo "Backing up local system to file $1 failed" fi }Функция принимает один аргумент - имя файла, в котором нужно сохранить резервную копию файлов. Создаваемый файл архива будет иметь формат .tbz (или .tar.bz2).
Перед созданием архива сначала создаётся пустой файл, владельцем которого становится пользователь, указанный в переменной BACKUP_USER, группой-владельцем становится группа, указанная в переменной BACKUP_GROUP, права доступа к файлу выставляются таким образом, что читать и писать его может только пользователь, указанный в переменной BACKUP_USER. Это делается для того, чтобы предотвратить чтение файла резервной копии посторонними пользователями в процессе его создания.
В архив помещаются файлы из каталогов /etc/, /usr/local/etc/, /usr/local/www/, /root/ и /usr/home/, кроме файлов с расширением .sql.gz и файлов из каталога /etc/zabbix/xbackup/. По-сути, в архив сохраняются файлы конфигурации, файлы из домашних каталогов пользователей и файлы веб-приложений, за исключением файлов с резервными копиями баз данных.
После создания архива для файла вызывается функция clear_old, которая переименовывает файл, снабжая его имя префиксом YYYYMMDD_ с текущей датой, и удаляет устаревшие экземпляры этого файла.
В переменных BACKUP_USER и BACKUP_GROUP выше указаны пользователь и группа с именем rbackup. Чтобы создать их, можно воспользоваться такими командами:
# pw add group rbackup # pw add user rbackup -g rbackup -c "User for backup purposes" -d /usr/home/rbackup -mВ случае с Linux функция резервного копирования файлов была такой:
linux_files_local() { # $1 - backup filename touch "$1" chown $BACKUP_USER:$BACKUP_GROUP "$1" chmod 0600 "$1" tar -cjf - --files-from=- --exclude 'home/*/.pycharm_helpers/*' --exclude 'root/.cpan/*' --exclude 'root/.cache/*' --exclude 'home/*/.cache/*' --exclude 'usr/local/lib/*' <<END 2>/dev/null > "$1" /etc/ /root/ /home/ /usr/local/ /usr/lib/zabbix/ /usr/share/mapnik/ /var/www/ /var/lib/dokuwiki/ END clear_old "$1" if [ $? -ne 0 ] then echo "Backing up local system to file $1 failed" fi }В целом эта функция не отличается от функции для FreeBSD. Резервному копированию подвергаются каталог с файлами конфигурации, домашние каталоги пользователей, каталог веб-приложений, а также каталог /usr/local/, и каталоги с файлами Zabbix и DokuWiki. Из резервного копирования исключаются файлы, создаваемые средой разработки PyCharm (она умеет работать по SSH), каталоги с кэшированными данными, модулями Perl.
Аналогично FreeBSD, в Linux нужно создать пользователя и группу rbackup, который будут использоваться в качестве владельца резервных копий:
# groupadd rbackup # useradd -c "User for backup purposes" -d /home/rbackup -m -g rbackup rbackup
Резервное копирование файлов с удалённых систем
Резервное копирование удалённых файловых систем работает аналогично, с той лишь разницей, что команды резервного копирования запускаются по SSH, а их стандартный вывод сохраняется в файл в локальной файловой системе.SSH_PRIVKEY=/root/.ssh/id_rsa freebsd_files() { # $1 - server ip or dns-name # $2 - server ssh port # $3 - backup filename touch "$3" chown $BACKUP_USER:$BACKUP_GROUP "$3" chmod 0600 "$3" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o Port=$2 -i $SSH_PRIVKEY $BACKUP_USER@$1 "/usr/local/bin/sudo /usr/bin/tar -cjf - -T- --exclude '*.sql.gz' --exclude 'etc/zabbix/xbackup/*'" <<END 2>/dev/null > "$3" /etc/ /usr/local/etc/ /root/ /usr/home/ /usr/local/www/ END clear_old "$3" if [ $? -ne 0 ] then echo "Backing up remote system $1:$2 to file $3 failed" fi }Функции передаются три аргумента:
- IP-адрес или доменное имя удалённой системы, резервную копию файлов с которой нужно снять,
- порт SSH-сервера на этой системе (было время, когда использовались системы с SSH-сервером на нестандартном порту),
- имя файла создаваемого архива.
Для копирования публичного SSH-ключа в домашний каталог пользователя из переменной BACKUP_USER на FreeBSD я пользовался такими командами, которые просто копировал в терминал при настройке нового сервера или виртуальной машины:
# chown rbackup:rbackup /usr/home/rbackup # mkdir /home/rbackup/.ssh # chown rbackup:rbackup /usr/home/rbackup/.ssh # cat <<END > /usr/home/rbackup/.ssh/authorized_keys ТУТ ПУБЛИЧНЫЙ SSH-КЛЮЧ END # chown rbackup:rbackup /usr/home/rbackup/.ssh/authorized_keysДля того, чтобы разрешить пользователю rbackup запускать через sudo команду tar для резервного копирования файлов, я использовал запускал visudo и вставлял такие настройки:
Defaults:rbackup !requiretty rbackup ALL=(root:ALL) NOPASSWD:/usr/bin/tar -cjf - -T- *Для Linux аналогичная функция выглядит следующим образом:
linux_files() { # $1 - server ip or dns-name # $2 - server ssh port # $3 - backup filename touch "$3" chown $BACKUP_USER:$BACKUP_GROUP "$3" chmod 0600 "$3" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o Port=$2 -i $SSH_PRIVKEY $BACKUP_USER@$1 "/usr/bin/sudo /bin/tar -cjf - --files-from=- --exclude 'home/*/.pycharm_helpers/*' --exclude 'root/.cpan/*' --exclude 'root/.cache/*' --exclude 'home/*/.cache/*' --exclude 'usr/local/lib/*'" 2>/dev/null <<END > "$3" /etc/ /root/ /home/ /usr/local/ /usr/lib/zabbix/ /usr/share/mapnik/ /var/www/ /var/lib/dokuwiki/ END clear_old "$3" if [ $? -ne 0 ] then echo "Backing up remote system $1:$2 to file $3 failed" fi }Для настройки публичных SSH-ключей использовались аналогичные команды:
# mkdir /home/rbackup/.ssh # chown rbackup:rbackup /home/rbackup/.ssh # cat <<END > /home/rbackup/.ssh/authorized_keys ТУТ ПУБЛИЧНЫЙ SSH-КЛЮЧ END # chown rbackup:rbackup /home/rbackup/.ssh/authorized_keysДля настройки прав в sudo использовались такие строчки:
Defaults:rbackup !requiretty rbackup ALL=(root:ALL) NOPASSWD:/bin/tar -cjf - --files-from=- *
Резервное копирование удалённой базы данных MySQL
MySQL является сетевым сервером, поэтому резервные копии баз данных можно снимать по сети, не прибегая к помощи SSH. Однако, для того, чтобы не заниматься настройкой фильтрации пакетов, а также не гонять по сети лишний объём данных в открытом виде, резервное копирование баз данных было решено выполнять тоже через SSH.PASSWORD=тут-пароль generic_mysql() { # $1 - server ip or dns-name # $2 - server ssh port # $3 - database name # $4 - backup filename touch "$4" chown $BACKUP_USER:$BACKUP_GROUP "$4" chmod 0600 "$4" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o Port=$2 -i $SSH_PRIVKEY $BACKUP_USER@$1 "mysqldump --single-transaction -u$BACKUP_USER -p$PASSWORD $3 | bzip2" 2>/dev/null > "$4" clear_old "$4" if [ $? -ne 0 ] then echo "Backing up $3 database from system $1:$2 to file $4 failed" fi }Функция принимает 4 аргумента:
- IP-адрес или доменное имя удалённой системы, резервную копию файлов с которой нужно снять,
- порт SSH-сервера на этой системе (было время, когда использовались системы с SSH-сервером на нестандартном порту),
- имя базы данных, резервную копию которой нужно снять,
- имя файла создаваемого архива.
Для снятия резервной копии используется пользователь с тем же именем, который является владельцем архивов и используется для подключения к удалённым системам по SSH. Его имя настроено в переменной BACKUP_USER. А для аутентификации этого пользователя используется пароль, указанный в переменной PASSWORD. Понятно, что этот пользователь должен создан и ему должны быть предоставлены права доступа к указанным базам данных. Для этого можно воспользоваться такими запросами:
CREATE USER 'rbackup'@'localhost' IDENTIFIED BY 'тут-пароль'; FLUSH PRIVILEGES; GRANT SHOW DATABASES, SELECT, LOCK TABLES, RELOAD ON *.* TO 'rbackup'@'localhost'; FLUSH PRIVILEGES;Т.к. в функции не используется каких-то специфичных путей к файлам и нет необходимости указывать полный путь к команде, выполняемой через sudo, то эта функция пригодна для использования и с FreeBSD и с Linux.
Резервное копирование удалённой базы данных PostgreSQL
Функция резервного копирования базы данных PostgreSQL в целом аналогична функции для резервного копирования базы данных MySQL. Для снятия резервной копии используется утилита pg_dump:generic_pgsql() { # $1 - server ip or dns-name # $2 - server ssh port # $3 - database name # $4 - backup filename touch "$4" chown $BACKUP_USER:$BACKUP_GROUP "$4" chmod 0600 "$4" ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o Port=$2 -i $SSH_PRIVKEY $BACKUP_USER@$1 "env PGPASSWORD=$PASSWORD pg_dump -U$BACKUP_USER $3 | bzip2" > "$4" 2>/dev/null clear_old "$4" if [ $? -ne 0 ] then echo "Backing up $3 database from system $1:$2 to file $4 failed" fi }Для снятия резервной копии нужно создать пользователя, указанного в переменной BACKUP_USER, и предоставить ему права доступа ко всем базам данных, резервные копии которых будет необходимо снимать. Войдя в систему под пользователем postgres, создаём пользователя для резервного копирования и вводим его пароль в процессе его создания:
$ createuser -D -R -I -S -P rbackupТеперь нужно подключиться к каждой из баз данных при помощи команды psql -d база-данных и выполнить следующие запросы:
GRANT SELECT ON ALL TABLES IN SCHEMA public TO rbackup; GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO rbackup;
Группировка действий по резервному копированию
Для снятия всех необходимых резервных копий с одного сервера можно создавать функции следующего вида:backup_server() { linux_files 'server.domain.tld' 22 'server.tbz' generic_mysql 'server.domain.tld' 22 'mysql' 'server_mysql.sql.bz2' generic_mysql 'server.domain.tld' 22 'base' 'server_base.sql.bz2' generic_pgsql 'server.domain.tld' 22 'database' 'server_database.sql.bz2' }
Выполнение резервного копирования
Теперь в скрипте имеются функции для снятия резервных копий каждого сервера. Осталось только создать каталог для хранения резервных копий и выполнить резервное копирование. Для этого создадим каталог и выставим права доступа к нему:# mkdir /backups # chown rbackup:rbackup /backups # chmod u=rwx,g=rx,o= /backupsТеперь впишем в скрипт команды перехода в каталог с резервными копиями, на всякий случай поменяем маску создаваемых файлов и последовательно вызовем функции резервного копирования всех требуемых серверов:
cd /backups umask 0077 backup_server1 backup_server2
Резервное копирование резервных копий
Не стоит складывать все яйца в одну корзину. На случай, если с резервными копиями в основном месте хранения что-нибудь случится, можно выполнить резервное копирование резервных копий на другой сервер. Для этого на другом сервере создаётся аналогичный каталог, в который при помощи rsync синхронизируются изменения из каталога на основном сервере:/usr/bin/rsync -a --delete-after -e "ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o Port=22 -i $SSH_PRIVKEY" /backups/ $BACKUP_USER@backup-server.domain.tld.:/backups/Один процесс rsync запускается локально, один процесс rsync запускается удалённо по SSH. Оба процесса обмениваются друг с другом информацией через SSH. Взаимодействуя друг с другом, они копируют недостающие файлы, обновляют фрагменты изменившихся файлов, удаляют файлы, ставшие не нужными.
Отладочная информация
Для того, чтобы иметь представление о времени начала и завершения тех или иных этапов резервного копирования, можно добавлять в функции или в тело скрипта отладочные сообщения с отметками времени. Например, в свой скрипт я вставил отладочные сообщения, фиксирующие моменты начала и завершения обновления основного архива и моменты начала и завершения синхронизации резервного архива:echo "BACKUP FINISHED: "`date "+%Y-%m-%d %H:%M:%S"` # тут резервное копирование серверов echo "BACKUP STARTED: "`date "+%Y-%m-%d %H:%M:%S"` echo "RSYNC STARTED: "`date "+%Y-%m-%d %H:%M:%S"` # тут вызов rsync echo "RSYNC FINISHED: "`date "+%Y-%m-%d %H:%M:%S"`
2 комментария:
https://github.com/non7top/scripts/blob/master/backup_new
используется rdiff-backup (другие варианты тоже есть), умеет mysql и postgres
Еще про это стоит упомянуть https://www.psc.edu/hpn-ssh
Позволяет добиться 100мб/с на 1гбит канале, ссш без патчей нервно курит в сторонке со скоростью 20мб/с на том же самом канале
Отправить комментарий