воскресенье, 24 апреля 2016 г.

ZRescue: ZFS restore, часть II

Для восстановления резервных копий, полученных утилитой, описанной в предыдущей части, используется столь же простая утилита:

 #!/sbin/sh  
   
 #  
 # ZRescue - ZFS Backup/Restore  
 #  
 # ZFS archives uses for bare-metal restore  
 # and systems cloning.  
 #  
 # Archive names can be incremental, like that:  
 # [hostname].[pool|pool%dataset].n.zfs<.gz>, n=0,1,2...  
 # Note: Do not rename archive files! Filenames will  
 #    use to recovery purposes.  
 #  
 # Version 2.0 (C) 2009,2016 Y.Voinov  
 #  
 # If you not specify pool/dataset name in command line,  
 # it will be read from archive name.  
 #  
 #pragma ident "@(#)zfsrestore.sh  2.0  10/22/09 YV"  
 #  
   
 #############  
 # Variables #  
 #############  
   
 # Environment variables   
 PATH=/usr/local/bin:/sbin:/usr/bin:$PATH  
   
 # Copyright and version  
 PKG_NAME="ZRescue"  
 PKG_VERSION="2.0"  
 COPY="Copyright (C) 2009, Yuri Voinov"  
   
 # Archiver name  
 ARC_NAME="gzip"  
 # Archive file suffix  
 ARC_SUFFIX=".gz"  
 # GZip default compression level  
 COMP_LEVEL="9"  
 # Default archive extension  
 ext="zfs"  
   
 # OS utilities   
 BASENAME=`which basename`  
 CAT=`which cat`  
 CUT=`which cut`  
 DATE=`which date`  
 ECHO=`which echo`  
 EXPR=`which expr`  
 GETOPT=`which getopt`  
 FILE=`which file`  
 GREP=`which grep`  
 GZIP=`which gzip`  
 HOSTNAME=`which hostname`  
 ID=`which id`  
 PRINTF=`which printf`  
 SED=`which sed`  
 SSH=`which ssh`  
 UNAME=`which uname`  
 WHOAMI=`which whoami`  
 ZFS=`which zfs`  
   
 OS_VER=`$UNAME -r|$CUT -f2 -d"."`  
 OS_NAME=`$UNAME -s|$CUT -f1 -d" "`  
 OS_FULL=`$UNAME -sr`  
   
 # System name  
 system=`$HOSTNAME`  
   
 # Snapshots extension by default   
 # Correct it if these snapshots already exists in the system  
 SNAP_EXT="$system""_""$PKG_NAME""-""$PKG_VERSION""_snapshot"  
   
 ###############  
 # Subroutines #  
 ###############  
   
 check_os ()  
 {  
  # Check OS  
  $PRINTF "Checking OS... "  
  if [ "$OS_NAME" = "SunOS" -a "$OS_VER" -lt "10" ]; then  
  $ECHO "ERROR: Unsupported OS: $OS_FULL"  
  $ECHO "Exiting..."  
  exit 1  
  else  
  $ECHO "$OS_FULL"  
  fi  
 }  
   
 check_root ()  
 {  
  # Check if user root  
  $PRINTF "Checking super-user... "  
  if [ -f /usr/xpg4/bin/id ]; then  
  WHO=`/usr/xpg4/bin/id -n -u`  
  elif [ "`$ID | $CUT -f1 -d" "`" = "uid=0(root)" ]; then  
  WHO="root"  
  else  
  WHO=$WHOAMI  
  fi  
   
  if [ ! "$WHO" = "root" ]; then  
  $ECHO  
  $ECHO "ERROR: You must be super-user to run this script."  
  exit 1  
  fi  
  $ECHO "$WHO"  
 }  
   
 check_fs_exists ()  
 {  
  # Check filesystem exists  
  arg_fs=$1  
   
  ret=`$ZFS list -H -o name $arg_fs > /dev/null 2>&1; $ECHO $?`  
  if [ "$ret" != "0" ]; then  
  $ECHO "ERROR: ZFS pool/dataset $arg_fs does not exist."  
  $ECHO "    Please specify another ZFS."  
  $ECHO "Exiting..."  
  exit 1  
  fi  
 }  
   
 check_hostname ()  
 {  
  # Check if backup hostname is the same target hostname  
  arg_backup_hostname=$1  
  arg_remote_hostname=$2  
    
  if [ "$arg_backup_hostname" != "$arg_remote_hostname" ]; then  
  $ECHO "WARNING: Backup was taken from $arg_backup_hostname host,"  
  $ECHO "     target host is $arg_remote_hostname."  
  fi  
 }  
   
 copyright_and_version ()  
 {  
  # Print package and copyright info  
  $ECHO  
  $ECHO "$PKG_NAME $PKG_VERSION $COPY"  
  $ECHO  
 }  
   
 usage_note ()  
 {  
  copyright_and_version  
  $ECHO "Usage: `$BASENAME $0` [-v] [-n] [-r] [-f] [/mntpoint/archive|local fs] [pool|dataset] [host]"  
  $ECHO  
  $ECHO "Note: Compression will use if GZip installed,"  
  $ECHO "   both for local archives or remote streams transfer."  
  $ECHO "Beware: GZip must be installed in both nodes in case of remote backup."  
  exit 1  
 }  
   
 archive_type ()  
 {  
  # Check archive type using extension and header check  
  arg_file=$1  
   
  if [ ! -z "`$ECHO $arg_file | $GREP $ARC_SUFFIX$`" -a \  
    ! -z "`$FILE "$arg_file" | $GREP $ARC_NAME`" ]; then  
  $ECHO "$ARC_NAME"  
  elif [ ! -z "`$ECHO $arg_file | $GREP $ext$`" -a \  
     ! -z "`$FILE "$arg_file" | $GREP $ext`" ]; then  
  $ECHO "$ext"  
  else  
  $ECHO "unknown"  
  fi  
 }  
   
 archive_exists ()  
 {  
  # Check archive exist and it readable  
  arg_arc=$1  
   
  # First check archive exists and readable  
  if [ ! -f "$arg_arc" -a ! -r "$arg_arc" ]; then  
  $ECHO "ERROR: Archive $arg_arc does not exist"  
  $ECHO "    or you haven't permissions to read."  
  $ECHO "Exiting..."  
  exit 1  
  fi  
  # Second we'll check archive type  
  if [ "`archive_type $arg_arc`" = "$ARC_NAME" -a \  
    "`archive_type $arg_arc`" = " $ext" ]; then  
  $ECHO "ERROR: Archive $arg_arc it has wrong type."  
  $ECHO "Exiting..."  
  exit 1  
  fi   
 }  
   
 destroy_fs ()  
 {  
  # Destroy filesystem(s) recursively  
  arg_fs=$1  
   
  $ZFS destroy -r "$arg_fs" > /dev/null 2>&1  
   
  # Check exit code  
  if [ "`$ECHO $?`" != "0" ]; then  
  $ECHO "WARNING: Filesystem $arg_fs does not exists."  
  fi  
 }  
   
 create_snap ()  
 {  
  # Create snapshot recursively  
  arg_filesystem=$1  
   
  $ZFS snapshot -r "$arg_filesystem@$SNAP_EXT"  
 }  
   
 zfs_receive ()  
 {  
  # Receive filesystem(s) from archive  
  arg_fs=$1  # Archive or fs to receive  
  arg_filesys=$2 # Target fs  
  arg_remote_host=$3 # Remote host  
   
  # Verbose output flag set in interactive mode  
  if [ "$verbose" = "1" ]; then  
  verb="v"  
  fi  
   
  # When use compression, restore fs with archiver  
  if [ "$remote_mode" != "1" -a "$remote_file_archive" != "1" ]; then  
  if [ "$compress" = "1" -a "`archive_type $arg_fs`" = "$ARC_NAME" ]; then  
   # Send from local file with archiver  
   $GZIP -d -c $arg_fs | $ZFS receive -dF"$verb" "$arg_filesys"  
  elif [ "`archive_type $arg_file`" = "$ext" ]; then  
   # Send from local file without archiver  
   $ZFS receive -dF"$verb" "$arg_filesys" < "$arg_fs"  
  fi  
  elif [ "$remote_mode" = "1" -a "$remote_file_archive" != "1" ]; then  
  if [ "$compress" = "1" ]; then  
   # Send to remote from local fs with compression  
   create_snap $arg_fs  
   $ZFS send -R$verb $arg_fs@$SNAP_EXT|$GZIP -$COMP_LEVEL -|\  
   $SSH $arg_remote_host "/bin/gzip -c -d -|/sbin/zfs receive -dF$verb $arg_filesys"  
  else  
   # Send to remote from local fs without compression  
   create_snap $arg_fs  
   $ZFS send -R$verb $arg_fs@$SNAP_EXT|\  
   $SSH $arg_remote_host "/sbin/zfs receive -dF$verb $arg_filesys"  
  fi  
  # Destroy local and remote snapshots  
  $SSH $arg_remote_host "/sbin/zfs destroy -r `/sbin/zfs list -H -o name -t snapshot|\  
  /bin/grep $arg_filesys@$SNAP_EXT`> /dev/null 2>&1"  
  destroy_fs $arg_fs@$SNAP_EXT  
  elif [ "$remote_mode" = "1" -a "$remote_file_archive" = "1" -a "`archive_type $arg_fs`" = "$ARC_NAME" ]; then  
  check_hostname `$ECHO "$arg_fs"|$CUT -f1 -d"."` $arg_remote_host  
  if [ "$compress" = "1" ]; then  
   # Send to remote from local archive with compression  
   $CAT $arg_fs|$SSH $arg_remote_host "/bin/gzip -c -d -|/sbin/zfs receive -dF$verb $arg_filesys"  
  else  
   # Send to remote from local archive without compression  
   $CAT $arg_fs|$SSH $arg_remote_host "/sbin/zfs receive -dF$verb $arg_filesys"  
  fi  
  # Destroy remote snapshots  
  $SSH $arg_remote_host "/sbin/zfs destroy -r `/sbin/zfs list -H -o name -t snapshot|\  
  /bin/grep $arg_filesys@$SNAP_EXT`> /dev/null 2>&1"  
  fi  
 }  
   
 ##############  
 # Main block #  
 ##############  
   
 # Checking OS  
 check_os  
   
 # Checking root  
 check_root  
   
 # Check command-line arguments  
 if [ "x$*" = "x" ]; then  
  # If arguments list empty, show usage note  
  usage_note  
 else  
  arg_list=$*  
  # Parse command line  
  set -- `$GETOPT fFrRnNvVhH: $arg_list` || {  
  usage_note  
  }  
   
  # Read arguments  
  for i in $arg_list  
  do  
   case $i in  
   -f|-F) remote_file_archive="1";;  
   -r|-R) remote_mode="1";;  
   -n|-N) compress_off="1";;  
   -v|-V) verbose="1";;  
   -h|-H|\?) usage_note;;  
   *) shift  
     archive=$1  
     filesystem=$2;  
     remote_host=$3  
     break;;  
   esac  
   shift  
  done  
   
  # Remove trailing --  
  shift `$EXPR $OPTIND - 1`  
 fi  
   
 # Check filesystem exists  
 check_fs_exists $filesystem  
   
 $ECHO "*** BEGIN: ZFS restore for $filesystem at `$DATE`."  
   
 # Check archiver  
 if [ ! -f "$GZIP" -a ! -x "$GZIP" -o "$compress_off" = "1" ]; then  
  $ECHO "INFO: Compression will NOT be used. GZip not found or compression disabled."  
  compress="0"  
 elif [ -f "$GZIP" -a -x "$GZIP" -a "$compress_off" != "1" ]; then  
  $ECHO "INFO: Data will be compressed with gzip -$COMP_LEVEL."  
  compress="1"  
 fi  
   
 # Check archive file exists and correct type when non-remote operation  
 if [ "$remote_mode" != "1" -o "x$remote_file_archive" != "x" ]; then  
  # If filesystem not specified, let's get it from file name  
  if [ "x$filesystem" = "x" ]; then  
  # Set initial archive file name  
  # Replase slashes with % if dataset specified in non-remote mode  
  filesystem=`$ECHO "$archive" | $CUT -f2 -d"." | $SED -e 's/%/\//g' | $CUT -f1 -d"/"`  
  fi  
  archive_exists $archive  
 fi  
   
 # First destroy all snapshots recursively  
 destroy_fs "$filesystem@$SNAP_EXT"  
   
 # Restore ZFS pool/dataset  
 zfs_receive $archive $filesystem $remote_host  
   
 # Finally destroy all snapshots recursively  
 destroy_fs "$filesystem@$SNAP_EXT"  
   
 $ECHO "*** DONE: ZFS restore for $filesystem at `$DATE`."  

Для чего это было написано:

 Восстановление  архивов  или некомпрессированных стримов  
 или  с удаленного сервера выполняется   при   помощи  
 команды  zfsrestore  .  Хотя действия, необходимые для  
 восстановления, достаточно просты, тем не менее при их  
 выполнении в спешке возможны ошибки, с целью минимизации  
 которых и был написан данный пакет.  
   
 Команда  подобна  команде  zfsbackup,  и  также может  
 выполняться в локальном режиме:  
   
 # zfsrestore [-v] [-n ] /mntpt/arc [local_fs]  
   
 где  первым  аргументом  (обязательным) указывается имя  
 компрессированного или некомпрессированного (распакованного)  
 стрима (возможно с абсолютным или относительным путем),  
 второй аргумент указывает целевой пул/датасет (пул либо  
 файловая система должны существовать на момент выполнения  
 восстановления), в который будет выполняться восстановление.  
   
 Второй аргумент (ZFS-пул/датасет) не является обязательным.  
 Если  он  опущен,  имя целевого  пула  либо  датасета  
 извлекается из имени файла архива,   например,   для  
 архива   с   именем server5.data%work2.0.zfs.gz   имя  
 пула  будет  data соответственно.  
   
 или вы можете выполнять восстановление в удаленном режиме:  
   
 # zfsrestores [-v] [-r] [-f] /mntpt/arc|local_fs \  
                [remote_fs] [user@]host  
   
 Remote restore can be done from local ZFS filesystem (option  
 -r, backed up previously by zfsbackup or manual zfs send  
 command) or from compressed or uncompressed (option -n)  
 archive (o, created by zfsbackup (locally or remote).  
   
 Удаленное  восстановление может выполняться с локальной  
 файловой системы ZFS (опция -r, созданной ранее командой  
 zfsbackup или ручным выполнением команды zfs send) или из  
 компрессированного  либо некомпрессированного (опция -n)  
 архива (опция -f), созданного zfsbackup (локально либо  
 удаленно).  
   
 В  случае,  если  для  восстановления  указывается  
 компрессированный стрим, на целевой машине должен быть  
 установлен архиватор (GZip), в случае его недоступности  
 следует  распаковать  стрим  вручную  на другой машине  
 и выполнять восстановление уже с некомпрессированного стрима  
 (его расширение должно быть .zfs).  
   
 При необходимости выборочного восстановления рекомендуется  
 предварительно  создать  целевой  датасет,  в  который  
 и  выполнять  восстановление стрима. При восстановлении  
 bare-metal указыватся предварительно воссозданый ZFS-пул  
 с именем, совпадающим с именем архивированного пула либо  
 датасета.  
   
 Если  восстановление выполняется в совпадающую файловую  
 систему  с  имеющимися данными (непустую), производится  
 перезапись  существующих  данных  и  дозапись  данных,  
 отсутствующих в целевой файловой системе.  

Если     при     выполнении    команды   zfsrestore    точка
монтирования  /usr недоступна, выполнение команды невозможно
и необходимо выполнять

Ручное восстановление
---------------------

Ручное  восстановление  архивов  выполняется в случаях, если
по   какой-либо   причине   невозможно   выполнение  команды
zfsrestore    (система   в  single-user при корневой системе
на   UFS   в   случае  нахождения  точки  монтирования  /usr
на  недоступном  слайсе,  восстановление  корневого пула при
загрузке с внешнего носителя итп.).

Процедура  ручного  восстановления достаточно проста, однако
требует некоторого внимания администратора.

Если  целевой  ZFS-пул  отсутствует,  его  требуется создать
до  начала  восстановления  данных. Затем следует убедиться,
что  пул  исправен  командой  zpool  status  <целевой  пул>.
В  случае  восстановления  данных  на  произвольный  датасет
(с  целью выборочного восстановления данных) он также должен
быть создан до начала восстановления.

Пример восстановления целого ZFS-пула:

# zpool create data c0t0d0s6
# gzcat server5.data.0.zfs.gz | zfs receive -vdF data

После  подобной  операции  никаких  дополнительных  действий
не   требуется,  восстановление  на  самый  верхний  уровень
иерархии выполняется в одно действие.

Пример восстановления датасета:

# gzcat server5.data%work2.0.zfs.gz | zfs receive -vdF data
# zfs destroy -r data/work2@snapshot

При   восстановлении   датасета   целевой   пул,  в  котором
находилась   (находится)  восстанавливаемая  система,  также
должен   существовать   до   начала   восстановления.  После
восстановления   датасета   необходимо   рекурсивно  удалить
снапшот   верхнего   уровня,   восстановленный   из  архива,
поскольку  в  данном  случае  он остается после операции zfs
receive в наборе файловых систем.

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

# zfs receive -vdF data < server5.data%work2.0.zfs.gz

Вы  также  можете  выполнить  данную  процедуру  через сеть,
с использованием SSH Или любого другого подходящего сетевого
сервиса для передачи стримов.  

Обратите внимание - мы говорим о восстановлении некорневых (non-root) пулов. Восстановление root pool - чуть более сложная задача, требующая несколько большего количества телодвижений:

 Восстановление root pool  
 ------------------------  
   
 Процедура восстановления root pool  в случае, например,  
 полного отказа системного диска требует несколько большего  
 внимания администратора и должна выполняться вручную.  
   
 При замене поврежденного системного диска выполняется ряд  
 обязательных процедур, связанных с заменой дисков. Вместе  
 с тем, при наличии удаленной резервной копии корневого пула  
 процедура  восстановления  позволяет сравнительно быстро  
 привести систему в загружаемое состояние.  
   
 Прежде всего, для восстановления корневого пула необходима  
 его резервная копия - в виде архива (компрессированного или  
 нет) или сохраненная в виде файловой системы на резервном  
 сервере.  
   
 Последующие действия описаны в виде алгоритма ниже.  
   
 1. Необходимо загрузить восстанавливаемую систему с внешнего  
 носителя - DVD, CD, USB или сети:  
   
 ok boot cdrom  
   
 или  
   
 ok boot net  
   
 2.  На резервном сервере, содержащем бэкап (архив или  
 файловую  систему) необходимо сконфигурировать удаленный  
 доступ для R-утилит с восстанавливаемой машины и запустить  
 соответствующие сервисы:  
   
 backup_server# netservices open  
 backup_server# vi /.rhosts  
 backup_server# vi /etc/hosts_equiv  
   
 При наличии сконфигурированного IPF необходимо разрешить  
 доступ на сервер восстановления c восстанавливаемой машины.  
   
 3. При физическом отказе и замене системного диска новый  
 диск необходимо пометить и разбить на слайсы. Метка диска  
 должна иметь тип SMI.  
   
 4. На восстанавливаемой машине пересоздается корневой пул на  
 новом диске, например:  
   
 # zpool create -f -o failmode=continue -R /a \  
  -m legacy rpool c0t0d0s0  
   
 5. На новый диск устанавливается загрузчик:  
   
 # installboot -F zfs /usr/platform/`uname -i`/lib/fs/zfs/bootblk \  
                      /dev/rdsk/c0t0d0s0  
   
 или  
   
 # installgrub /boot/grub/stage1 /boot/grub/stage2 \  
                 /dev/rdsk/c0t0d0s0  
   
 6.  Далее  выполняется  прием  рекурсивного  снапшота  
 и восстановление содержимого пула с удаленного резервного  
 сервера:  
   
 # rsh backup_server zfs send backup_data/rpool|zfs receive -Fdv rpool  
   
 либо из компрессированного архива, находящегося на удаленном  
 резервном сервере:  
   
 # rsh backup_server gzip -c -d pegasus.rpool.0.zfs.gz| zfs receive -Fdv rpool  
   
 Можно  также  выполнить  восстановление  root  pool  
 непосредственно с локальной архивной копии, находящейся,  
 например, на USB-устройстве (неархивированный стрим, для  
 компрессированного архива требуется предварительно выполнить  
 декомпрессию ввиду возможной недоступности gzip в miniroot  
 загрузочного дистрибутива):  
   
 # cd /usbmount  
 # cat pegasus.rpool.0.zfs|zfs receive -dFv rpool  
   
 Операция восстановления содержимого пула может потребовать  
 некоторого времени.  
   
 7. Необходимо убедиться, что корневой пул был успешно  
 восстановлен в полном объеме командой zfs list.  
   
 8. Необходимо установить свойство bootfs корневого пула  
 в правильное (оригинальное) значение:  
   
 # zpool set bootfs=rpool/ROOT/s10x_u8wos_08a rpool  
   
 Пересоздание  dump и swap разделов не требуется, если  
 резервная  копия корневого пула создавалась при помощи  
 zfsbackup.  Рекурсивный снапшот корневого пула содержит  
 данные разделы и они будут восстановлены автоматически.  
   
 9. Перезагрузить систему с восстановленного пула:  
   
 # init 6   
   
 Замечание: Вместо пересоздания пула и его восстановления  
 можно  также  использовать  (начиная  с  версии 10/09)  
 функциональность  zflash  и  выполнить  восстановление  
 средствами JumpStart flash install с некомпрессированного  
 стрима (архива), созданного утилитой zfsbackup.  
   

Однако, ничего невозможного. :)

Что касается совместимости (а мне точно известно, что до сих пор эксплуатируются весьма старые версии Solaris):

 Пакет работает на Solaris 10 10/08 и выше и OpenSolaris  
 начиная с релиза 2008.11.  
   
 Пакет поддерживает создание архивов ZFS и их восстановление  
 начиная с Solaris 10 8/07 (поддерживаются только некорневые  
 пулы ZFS), начиная с релиза 10/08 поддерживаются также  
 корневые ZFS-пулы (ZFS root pools).  
   

Техническая поддержка

Автор не оказывает техническую поддержку ни на какой основе. Вы используете предоставленный код на свой собственный страх и риск.

Лицензия

Вы можете модифицировать код как вам желательно, однако обязаны сохранить копирайты первоначального автора.

Скачать

Вы можете скачать вышеописанные утилиты в виде скриптов плюс man-страницы к ним в формате man, плюс процитированные readme с краткими руководствами по применению здесь.