Автоматический бэкап с помощью снапшотов lvm и tar (lvm snapshots tar backup)

Автор gardarea51, 03 сентября 2014, 13:28:37

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

gardarea51

Буквалньно на днях переписал свой скрипт бэкапа томов lvm, в том числе системного тома, и решил поделиться, вдруг кому-то пригодится. На качество кода ни в коем образе не претендую.
#!/bin/bash

#backup_level может принимать 2 значения: full  или diff (для запуска полного или инкрементного бэкапа)
backup_level=$1

#Для работы скрипта необходимо задать как минимум следующие параметры (если просто создавать снапшоты):
#vg - имя физ группы томов, тома которой будут обрабатываться
#lv_names - список имен обрабатываемых томов
#work_dir - "рабочая директория" скрипта, место его непосредственного расположения
#dir_for_mount - директория, в которую будут монтироваться снапшоты томов
#backups_dir - директория, которая будет принимать архивируемые данные (если исп. архивирование, шаг 2)
#Важно! Размер диска для хранения снапшотов должен превышать размер самого большого архивируемого тома!
#(имеется в виду фактический размер)

#Задание переменных
vg="/dev/vgmd0"
lv_names="root data"
work_dir="/root/executable_scripts"
dir_for_mount="/mnt/snapshots"
backups_dir="/mnt/backups"

created=`date +%d.%m.%y`
taR="tar -cpz --totals --ignore-failed-read --one-file-system"
#exclude_dir="--exclude=run/* --exclude=var/run/* --exclude=tmp/* \
              #--exclude=proc/* --exclude=sys/* --exclude=mnt/*"

#Вычисление необходимых переменных на основе заданных в шапке скрипта
#vg - группа физ томов где: lv - том, snap - снапшот тома, lv_name - имя тома, snap_name - имя снапшота
for lv_name in $lv_names ; do
  lv=$vg/$lv_name
  snap_name=snap_$lv_name
  snap=$vg/$snap_name
  snap_mountpoint=$dir_for_mount/$snap_name
 
  #Функция, вычисляющая свободное место в $backups_dir (в Gb) и сохраняющая это значение в $free_size
  get_free_size () {
    #free_size=2
    free_size=$(df -B G | grep $backups_dir | awk '{print $4}' | sed 's/G//')
  }
  #Функция, вычисляющая требуемое для бэкапа место (в Gb) и сохраняющая это значение в $snap_size 
  get_required_size () {
    snap_size=$(df -B G | grep $snap_mountpoint | awk '{print $3}' | sed 's/G//')
    required_size=$[($snap_size*110)/100]
  }
  #Функия, сохраняющая имя самого старого файла из $backups_dir в переменной $file_for_delete
  get_file_for_delete () {
    file_for_delete="$(ls -1rtd $backups_dir/* | head -1)"
  }

  #ШАГ 1 - создание снапшотов и их монтирование в заданную директорию ---------------------------------
  echo "`date +"%d.%m.%Y %T"` Запуск процедуры резервного копирования..."
  echo "Создание снапшота для $lv и его монтирование в каталог $snap_mountpoint"
  #Проверка на существование старого снапшота, если он есть - удаление (возможно с отмонтированием)
  if [ -e $snap ] ; then
    if /bin/mount | grep $snap_name > /dev/null 2>&1 ; then
umount -l $snap
    fi
    lvremove -f $snap
  fi
  #Создание снапшота и его монтирование в также создаваемый каталог $snap_mountpoint (если его нет)
  sync && lvcreate -L 20G -s -n $snap_name $lv
  if [ ! -d $snap_mountpoint ] ; then
    mkdir $snap_mountpoint
  fi
  if /bin/mount -o ro $snap $snap_mountpoint ; then
    echo "  Монтирование снапшота произведено"
  fi

  #ШАГ 2 - Запуск процесса архивирования в зависимости от значения переменной backup_level-------------
  #Вычисления свободного места, если его меньше, чем под полный бэкап - удаление старых файлов
  get_free_size ; get_required_size
  if [ "$free_size" -lt "$required_size" ] ; then
    echo "WARNING! Недостаточно места для сохранения бэкапа: ${free_size}Gb < ${snap_size}Gb, запуск очистки..."
    while [ "$free_size" -lt "$required_size" ] ; do
      #Удаление самого старого файла (или каталога) в $backups_dir
      get_file_for_delete > /dev/null 2>&1
      if [ -n "$file_for_delete" ] ; then
        echo "удаляется $file_for_delete"
        рм -рф "$file_for_delete"
        get_free_size
      else
echo "ERROR! Файлы для удаления отсутствуют, но места по прежнему недостаточно!"
curl -d "text=УрФУ: ошибка создания нового бэкапа!" \
http://sms.ru/sms/send\?api_id=blablabla > /dev/null 2>&1
        exit 0;
      fi
    done
  fi
  #Указание расположения файлов-описаний инкрементального бэкапа tar (в директории $work_dir)
  full_file=$work_dir/.$snap_name'_full'
  diff_file=$work_dir/.$snap_name'_diff'
  if [ "$backup_level" = "full" ] ; then
    echo "Выбрано полное архивирование данных (ежемесячный бэкап)..."
    echo "Свободное место на диске для бэкапов: ${free_size}Gb, архивируется: ${snap_size}Gb"
    #Проверка наличия файла-описания для инкрементного бэкапа, если есть - его удаление
    if [ -e $full_file ] ; then
      rm $full_file
    fi
    #Создание бэкапа
    cd $snap_mountpoint
    $taR --listed-incremental=$full_file -f $backups_dir/$lv_name'_'$created'_'full.tar.gz * #$exclude_dir
    echo "`date +"%d.%m.%Y %T"` Архивирование завершено..."
    cd - > /dev/null 2>&1
  elif [ "$backup_level" = "diff" ] ; then
    echo "Выбрано инкрементальное архивирование данных (ежедневный бэкап)..."
    echo "Свободное место на диске для бэкапов: ${free_size}Gb, архивируется: ${snap_size}Gb"
    #Проверка наличия месячного файла-описания для инкрементных бэкапов, если есть - его замена
    if [ -e $full_file ] ; then
      cp $full_file $diff_file
      #Создание бэкапа
      echo "`date +"%d.%m.%Y %T"` Запуск архивирования данных..."
      cd $snap_mountpoint
      $taR --listed-incremental=$diff_file -f $backups_dir/$lv_name'_'$created'_'diff.tar.gz * #$exclude_dir
      echo "`date +"%d.%m.%Y %T"` Архивирование завершено..."
      cd - > /dev/null 2>&1
    else echo "ERROR! Инкрементальный бэкап невозможен, т.к. не был выполнен полный бэкап"
    fi
  else echo "ERROR! Неверно указан тип бэкапа, архивирование данных отменено!"
  fi

  #ШАГ 3 - Отмонтирование и удаление снапшотов---------------------------------------------------------
  echo "Отмонтирование и удаление снапошота $snap через 5 секунд"
  umount -l $snap && sleep 5 && lvremove -f $snap #&& rm -r $snap_mountpoint
  #Установка прав на созданные архивы в $backups_dir
  chown -R :admins $backups_dir/ ; chmod -R g+x $backups_dir/
  echo "`date +"%d.%m.%Y %T"` Процедура резервного копирования завершена."
  echo "------------------------------------------END------------------------------------------------"
done


А теперь немного объяснений. Скрипт по крону у меня вызывается так:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
1 0 1    * *             root /root/executable_scripts/exec_backup.v2 full >> /var/log/backups.log 2>&1
1 0 2-31 * *             root /root/executable_scripts/exec_backup.v2 diff >> /var/log/backups.log 2>&1


Для работы скрипта нужно задать 5 параметров в шапке скрипта (там же есть описание этих параметров, я не буду повторять это здесь):
#Задание переменных
vg="/dev/vgmd0"
lv_names="root data"
work_dir="/root/executable_scripts"
dir_for_mount="/mnt/snapshots"
backups_dir="/mnt/backups"


Условно скрипт состоит из выполнения 3-х шагов:
1) Создание и монтирование снапшота
2) Архивирование данных, в моем случае с помощью tar
3) Отмонтирование и удаление снапшота
Если выкинуть из скрипта шаг 2 и используемые в нем переменные, то можно использовать свой способ бэкапа, так как бэкап выполняется именно в этом шаге. К примеру здесь можно использовать dd или что-то другое.

Опишу немного шаг2 с использованием tar.
Первым делом вычисляется свободное место на разделе для хранения бэкапов и фактически занятое место в созданном снапшоте. Если копируемый снапшот (+ 10% его размера) не войдет на раздел для бэкапов, то место нужно выделить методом удаления самых старых файлов в из директории с бэкапами. Старые файлы удаляются по одному до тех пор, пока на разделе для бэкапов не освободится достаточное количество места. Если тут что-то пошло не так: выводится сообщение об ошибке и отправляется смс (поменяйте на свой вариант!)

После того как выделено необходимое количество места - начинается сам процесс создания бэкапов. Два варианта: полный бэкап или же дифференцированный. Используется опция tar --listed-incremental. Если вас заинтересует скрипт, но вы не понимаете логику работы этого блока скрипта - не стесняясь спрашивайте, я постараюсь ответить.

Сам переброс бэкапов в другое место в скрипте никак не описывается. Они просто сохраняются локально. Однако, никто не мешяет в $backups_dir монтировать сетевой ресурс samba или nfs. У меня сделано проще, $backups_dir расшарен с помощью samba и я каждый день с утра вручную забираю бэкапы к себе через сетевую папку. Точно также можно поставить отдельный ПК, который будет по крону забирать с сетевой папки новые бэкапы. В общем-то на этом все. Надеюсь кто-то найдет это полезным для себя.

ps: процесс восстановления системы из такого бэкапа я как то описывал здесь.

Vadim.Pechorin

Каким образом осуществляется восстановление из резервной копии?

gardarea51

А где-то тут есть и такая моя тема..
А вот и она: https://debianforum.ru/index.php?topic=4189.msg36621#msg36621

ps: кстати ссылка была еще внизу хаутушки )