Как стать автором
Обновить

Бэкап файлов Windows-сервера своими руками

Время на прочтение11 мин
Количество просмотров46K
Здесь мы рассмотрим, как сделать систему дифференциального бэкапа «из коробки» (ну почти), с привлечением минимального количества внешних модулей, в лучших традициях UNIX-way.
Будем использовать 7za.exe \ 7z, а также UNIX-like утилиту pdate.exe, чтобы со временем нам было работать также удобно, как и в ламповом *NIX, а заменой bash нам будет «простонародный» BAT. Предыстория и подробности — под катом.

Предыстория


Все началось с истории про невнимательного и бесстрашного пользователя, открывающего приходящие по личной почте файлы откройменя.exe на рабочем компьютере. К сожалению, это оказалось не какой-нибудь посредственной малварью, а вполне обычным шифратором. Но, помимо файлов юзера, которые, конечно же, восстановлению не подлежат, оказалась задета одна крошечная, но важная шара, доступ к которой был у всех.

Взглянув на сие зашифрованное непотребство, я с благодарностью вспомнил про то, что каждый день у меня делается бэкап этой (и не только этой) шары встроенными средствами Windows Server 2003 SP2 x64. Но, полистав этот бэкап, я понял, что в плане резервного копирования средствами самой Windows не все так радужно. Во-первых, полный бэкап оказался недоступен, а значит восстановить cold-data (файлы, которые меняются очень редко) вряд ли получится. Во-вторых, восстановление из созданного инкрементального бэкапа оказалось задачей нетривиальной — за каждый шаг получалось восстановить только данные, которые были изменены, и ничего более. Получается, чтобы восстановить хотя бы все измененные данные (раз полный бэкап оказался утерян), то пришлось бы перебирать по очереди все бэкапы — не совсем то, что я ожидал от инкрементального бэкапа в таком случае.

Кто то из вас может сказать — надо было проверять работоспособность бэкапа, и да, так оно и есть. Но тот из вас, кто работает в торговле, может понять, куда может уходить время админа — да-да, они самые, онлайн-кассы.

Крепко задумавшись, я вспомнил свое первое знакомство с системой инкрементального копирования fsbackup за авторством Максима Чиркова www.opennet.ru/dev/fsbackup — гибкость, простота, в то же время обилие возможностей и открытый формат хранения архивов (tar). Жаль, что система разработана под *NIX / Linux. Google также не ответил на мой вопрос про подобную систему под Windows. Самое полезное, что я нашел — это краткий гайд хабровчанина antip0d и пример скрипта для резервного копирования. Именно материал по последней ссылке я и использовал для своего скрипта.

Собираем систему


В первую очередь, скачиваем последнюю стабильную версию. На момент написания это 16.04. Наш бэкап будем хранить в 7z архиве: поддержка многопоточности, шифрованных/многотомных архивов, а скорость извлечения из 7z выше скорости упаковки в 10-20 раз!
UPD: Спасибо хаброжителю Taciturn за поправку — вы также можете использовать 7z.exe, уже установленный в вашей системе. Функциональных различий между 7z и 7za я не выявил.

Нас интересуют:
7za.exe — автономная версия 7-Zip.
7za.dll — библиотека для работы с архивами 7z
7zxa.dll — библиотека для распаковки 7z архивов.
Для 64-битных ОС используем те же файлы из каталога x64.
К сожалению, ссылка из используемого мной материала на утилиту pdate никуда не ведет, единственная найденная мной версия
pdate v1.1 build 2007.12.06
© 2005-2007 Pavel Malakhov 24pm@mail.ru
Ссылка из встроенного мануала pdate ведет туда же, а именно — в никуда.
pm4u.opennet.ru/mysoft/pdate.htm
К счастью, на том же ресурсе есть краткая статья по этой программе, там же ее можно скачать.

Мной была использована следующая структура каталогов:
D:\winfsbackup — корневая директория скрипта и связанных файлов
D:\winfsbackup\7z — библиотеки и исполняемый файл 7za
D:\winfsbackup\backup — место хранения бэкапов (можно переназначить путем правки переменных, как и любые другие используемые файлы)
D:\winfsbackup\lists — списки включаемых и исключаемых файлов. О них расскажу чуть позже
D:\winfsbackup\log — логи
D:\winfsbackup\pdate
D:\winfsbackup\tmp — устанавливает рабочий каталог для временного базового архива
D:\winfsbackup\winfsbackup.bat — сам скрипт.

Логика работы


После обработки переменных скрипт смотрит блок :Main, где указывается логика работы бэкапа — в каком случае должен выполниться новый бэкап, а в каком случае — обновить существующий базовый архив. По умолчанию, новый архив создается в начале месяца, а все файлы из директории backup перемещаются в \backup\old, или если базового архива не существует.
Уже во время написания статьи я понял, что нужно добавить возможность обновления базового архива — упрощенный вариант «полный бэкап раз месяц + дифференциальный бэкапы к нему» целесообразно использовать для файловых обменников размером до ~250 Гб. Для моего файлообменника в 550 Гб с преобладанием мелких файлов скорость бэкапа оказалась неудовлетворительна (почти 55 часов). Справедливости ради стоит сказать, что это не может служить сколь нибудь достоверным замером производительности — в процессе бэкапа выяснилось, что некоторые файлы недоступны (привет chkdsk), а бэкап складывался в раздел удаленного сервера, который тоже был занят операциями дискового ввода-вывода.

:Main
REM Здесь описаны условия, в каком случае будет выполняться полный \ дифференциальный бэкап, либо обновление базового бэкапа.

REM Базовое условие - создание полного бэкапа если он не существует
IF NOT EXIST %baseArch% GOTO BaseArchive

REM полный бэкап раз месяц + дифференциальныt бэкапы к нему
IF %dm% EQU 1 GOTO BaseArchive ELSE GOTO UpdateArchive

REM обновляем базовый архив в 1 день месяца
REM IF %dm% EQU 1 GOTO UpdateBase ELSE GOTO UpdateArchive

REM Ежеквартальный полный бэкап (2, 19, 36 неделя года)
REM IF NOT %wn%.%dw% EQU 02.5 GOTO UpdateArchive
REM IF NOT %wn%.%dw% EQU 19.5 GOTO UpdateArchive
REM IF NOT %wn%.%dw% EQU 36.5 GOTO UpdateArchive

REM Обновляем базовый архив, каждую субботу
REM IF %dw% EQU 6 (GOTO UpdateBase) ELSE (GOTO UpdateArchive)

REM А здесь можно разместить действие, которое выполнится если предыдущие условия не отработают.
REM Я стараюсь избегать подобного поведения
ECHO Warning! No one condition matching, check :Main block of script >> %Log%
GOTO End

Первые 2 условия включены по-умолчанию, и описывают полный бэкап раз месяц + дифференциальные бэкапы. Остальные условия альтернативны, то есть при включении одного нужно выключить другое, или изменить логику работы на свой вкус.

Переменные


dm, dw, wn — соответственно день месяца, день недели и номер недели (в численном выражении).
verboseLevel — режим «говорливости», выдает информацию о том, куда будет записываться архив, и прочее. Полезно, когда вносишь в структуру скрипта серьезные изменения.
tmpDir — место сохранения временного файла. По умолчанию, 7-Zip строит новый базовый файл архива в том же самом каталоге, где и старый базовый файл архива. Определяя этот ключ, вы можете установить рабочий каталог, где будет построен временный базовый файл архива. После того, как временный базовый файл архива построен, он копируется поверх первоначального; затем временный файл удаляется.

Дифференциальный бэкап


По умолчанию, в дифференциальный бэкап помещаются файлы, которых нет в базовом архиве, а также более новые файлы. При желании, это поведение можно изменить. Описания ключей есть в стандартном файле справки 7z.

У такого подхода есть свои плюсы (которые я уже успел оценить) — быстрота разворачивания из бэкапа. В случае полного восстановления разворачиваем последний (или любой другой) имеющийся дифференциальный бэкап, а затем — полный архив, отказываясь от перезаписи имеющихся файлов.

Некоторые опции командной строки


-bsp2 — выводит строку с прогрессом выполнения в STDERR. STDOUT 7z перенаправлен в лог, прогресс, естественно, туда не пишется. Эта команда выводит его в STDERR, для большей информативности.

-ssw — упаковывает файлы, открытые для записи другим приложением. Если этот ключ не установлен, 7-Zip не включает такие файлы в архив.

-slp — крайне полезная опция. Режим больших страниц увеличивает скорость сжатия. Однако, есть пауза в начале сжатия, в то время когда 7-Zip распределяет большие страницы в памяти. Если 7-Zip не может разместить большие страницы, он размещает обычные маленькие страницы. Кроме того, Диспетчер задач не показывает реальное использование памяти программами, если 7-Zip использует большие страницы. Эта особенность работает только на Windows 2003 / XP x64. Также нужно иметь права администратора для вашей системы. Рекомендованный размер оперативной памяти для этой особенности — 3 Гб или больше. если вы используете режим -slp, ваша система Windows может зависнуть на несколько секунд, когда 7-Zip выделяет блоки памяти. Когда Windows пытается выделить большие страницы из оперативной памяти для 7-Zip, Windows может подвесить другие задачи на это время. Это может выглядеть как полное зависание системы, но затем ее работа восстанавливается, и, если распределение прошло успешно, 7-Zip работает быстрее. Не используйте режим -slp, если вы не хотите, чтобы другие задачи быть «подвешены». Кроме того, бессмысленно использовать режим -slp для сжатия небольших наборов данных (менее 100 МБ). Но если вы сжимаете большие наборы данных (300 Мб или более) методом LZMA с большим словарем, вы можете получить увеличение скорости на 5% -10% в режиме -slp.
-mmt=on — устанавливает режим многопоточности. Если у вас многопроцессорная / многоядерная система, вы можете получить увеличение скорости с этим ключом. 7-Zip поддерживает режим многопоточности только для сжатия LZMA/LZMA2 и сжатия/распаковки BZip2.

-ms=off — отключает создание solid-архивов. Качество сжатия при этом, конечно же, падает, однако есть весьма весомые плюсы — вы можете периодически обновлять данные базового архива чтобы уменьшить размер дифференциальный бэкапов, и так как архив не является целостным, не нужно будет его дополнительно «пережимать». Non-solid архив более стоек к повреждениям, и время извлечения из него происходит заметно быстрее.

Include / exclude листы
По умолчанию определено 2 типа списка — список включаемых файлов / директорий (include_general.txt), и 2 списка исключений (exclude_general.txt, exclude_regexp.txt).

Список включения также поддерживает UNC-пути. Для того, чтобы поместить файл / директорию в исключения, путь должен быть относительным.

Например, если директория для бэкапа E:\foo\bar, и мы хотим исключить вложенную директорию E:\foo\bar\somefolder, то в exclude_general.txt мы должны добавить bar\somefolder или bar\somefolder\

Путь без слэша в конце может относиться как к файлу, так и к директории.
В exclude_regexp.txt вносятся исключаемые по regexp файлы, которые просматриваются рекурсивно. * — последовательность произвольных символов,? — любой символ.
7-Zip не использует системный синтаксический анализатор подстановочных знаков, поэтому «любой файл» для 7 Zip это '*', а '*.*' — файл, имеющий расширение.

Ну и наконец, скрипт целиком:

@ ECHO OFF
REM Sources were found on http://sysadminwiki.ru/wiki/Резервное_копирование_в_Windows
CD %~dp0
TITLE winfsbackup
MODE CON: COLS=120 LINES=55
ECHO Setting vars...
REM --- Definition block ---
SET verboseLevel=1
SET tmpDir=D:\winfsbackup\tmp
SET run_7z=D:\winfsbackup\7z\7za.exe
SET run_pdate=D:\winfsbackup\pdate\pdate.exe
FOR /F "usebackq" %%a IN (`%run_pdate% e`) DO (SET dm=%%a)
FOR /F "usebackq" %%a IN (`%run_pdate% u`) DO (SET dw=%%a)
FOR /F "usebackq" %%a IN (`%run_pdate% V`) DO (SET wn=%%a)
SET LogDir=D:\winfsbackup\log
SET Log=%LogDir%\general.log
SET dDir=D:\winfsbackup\backup
SET dlmDir=D:\winfsbackup\backup\old
SET baseArch=%dDir%\general.7z
SET IncludeList=lists\include_general.txt
SET ExcludeList=lists\exclude_general.txt
SET ExcludeRegexp=lists\exclude_regexp.txt
SET updArch_dw=%dDir%\day_general_%dw%.7z
SET updArch_wn=%dDir%\week_general_%wn%.7z

IF %verboseLevel%==0 GOTO Main
ECHO Verbose mode ON!
ECHO Today is %wn% week of year, %dw% day of week.
ECHO Full quarter backup will execute (if enabled) on 2, 19 and 36 week, friday.
ECHO Temporary directory is %tmpDir%
ECHO Now logging into %Log%
ECHO Current backup directory is %dDir%, older backups stored into %dlmDir%

:Main
REM Here discribed conditions - in which case script will make new backup, update older one, etc
REM You are free to change these conditions
REM Make sure you envisaged all possible cases
REM Actions here are not disigned to be active more than 1 at same time, excluding base condition
REM If you want multiple conditions, you should edit it

REM Base condition - full backup will be created if it is not exist
IF NOT EXIST %baseArch% GOTO BaseArchive

REM Command below turns on making full backup at 1'st day of every month, in other days - increments
REM IF %dm% EQU 1 GOTO BaseArchive ELSE GOTO UpdateArchive

REM This option enables updating full backup every month
IF %dm% EQU 1 GOTO UpdateBase ELSE GOTO UpdateArchive

REM Uncomment these 3 commands if you want to run full backup ~every quarter (2, 19, 36 week of year)
REM IF NOT %wn%.%dw% EQU 02.5 GOTO UpdateArchive
REM IF NOT %wn%.%dw% EQU 19.5 GOTO UpdateArchive
REM IF NOT %wn%.%dw% EQU 36.5 GOTO UpdateArchive

REM This option enables rewriting base archive every saturday with new files in order to decrease size of increments
REM IF %dw% EQU 6 (GOTO UpdateBase) ELSE (GOTO UpdateArchive)

REM Here you can place default action if conditions of previous ones were not executed.
%run_pdate% "Z --- \A\c\t\i\o\n \w\a\s\ \n\o\t \s\e\l\e\c\t\e\d\! >> %Log%
ECHO Warning! No one condition matching, check :Main block of script
GOTO End

:BaseArchive
ECHO Clear %dlmDir% and move data of previous month to that dir...
IF NOT EXIST %dlmDir%\nul MKDIR %dlmDir%
DEL /Q %dlmDir%\*
MOVE /Y %dDir%\* %dlmDir% 2> nul

%run_pdate% "====== Y B =======" > %Log%
%run_pdate% "Z --- \S\t\a\r\t \t\o \c\r\e\a\t\e \n\e\w \a\r\c\h\i\v\e" >> %Log%

ECHO Creating new backup %baseArch%
%run_7z% a %baseArch% -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx3 -ms=off >> %Log%
IF %ERRORLEVEL%==0 (
	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \a\r\c\h\i\v\e \s\u\c\c\e\s\s\f\u\l\l\y \c\r\e\a\t\e\d!" >> %Log%
	) ELSE (
		IF %ERRORLEVEL%==1 (
		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
			) ELSE (
				IF %ERRORLEVEL%==2 (
				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
				) ELSE (
					IF %ERRORLEVEL%==7 (
					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
					) ELSE (
						IF %ERRORLEVEL%==8 (
						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
						) ELSE (
							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
						)
					)
				)
			)
		)
	)
)
GOTO End

:UpdateBase
ECHO Refreshing base archive
ECHO ******* ******* *******  >> %Log%
%run_pdate% "Z --- \S\t\a\r\t \t\o \u\p\d\a\t\e \a\r\c\h\i\v\e" >> %Log%
%run_7z% u %baseArch% -up0q1r2x1y2z1w0 -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx5 -ms=off >> %Log%
IF %ERRORLEVEL%==0 (
	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \u\p\d\a\t\e \s\u\c\c\e\s\s\f\u\l\l\y \f\i\n\i\s\h\e\d" >> %Log%
	) ELSE (
		IF %ERRORLEVEL%==1 (
		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
			) ELSE (
				IF %ERRORLEVEL%==2 (
				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
				) ELSE (
					IF %ERRORLEVEL%==7 (
					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
					) ELSE (
						IF %ERRORLEVEL%==8 (
						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
						) ELSE (
							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
						)
					)
				)
			)
		)
	)
)
GOTO End

:UpdateArchive
ECHO Updtaing existing full backup
ECHO ******* ******* *******  >> %Log%
%run_pdate% "Z --- \S\t\a\r\t \t\o \u\p\d\a\t\e \a\r\c\h\i\v\e" >> %Log%
IF %dw%==7 (SET updArch=%updArch_wn%) ELSE SET updArch=%updArch_dw%

REM --- Check files existence ---
IF EXIST %updArch% DEL /Q %updArch%

REM --- Create incremental archive ---
<nul set /p strTemp=Updating %baseArch% to incremental %updArch% archive
ECHO.
%run_7z% u %baseArch% -u- -up0q0r2x0y2z0w0!%updArch% -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx5 -ms=off >> %Log%
IF %ERRORLEVEL%==0 (
	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \u\p\d\a\t\e \s\u\c\c\e\s\s\f\u\l\l\y \f\i\n\i\s\h\e\d" >> %Log%
	) ELSE (
		IF %ERRORLEVEL%==1 (
		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
			) ELSE (
				IF %ERRORLEVEL%==2 (
				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
				) ELSE (
					IF %ERRORLEVEL%==7 (
					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
					) ELSE (
						IF %ERRORLEVEL%==8 (
						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
						) ELSE (
							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
						)
					)
				)
			)
		)
	)
)

:End
ECHO Done!
%run_pdate% "Z --- \D\o\n\e" >> %Log%
ping localhost -w 1000 -n 5 > nul


Вместо окончания


Всегда есть люди, которые против подобных решений, и всеми руками-ногами «за» использование всяких акронисов и прочего «махрового энтерпрайза».

Я же считаю, что решение должно быть соразмерно поставленной задаче, а в моем случае задача — иметь в укромном месте резервную копию файлопомойки, которую можно быстро развернуть — именно этим меня и разочаровал ntbackup.

Собранный пример можно посмотреть на YandexDisk.

Там же — zip-архив для скачивания.
Конструктивная критика, советы, и тем более, тестирование — welcome!
Спасибо за внимание! Всем долгого аптайма, стабильного линка, и конечно, бэкапов под рукой.
Теги:
Хабы:
+8
Комментарии43

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн