Application: "Bacula Director 5.2/7.4".
Система резервного копирования "Bacula" принципиально не обладает функционалом автоматического удаления "томов" резервных копий. Изначально предполагается, что хранение реализовано на магнитных лентах "стримеров", которые заменяются в соответствии с расписанием и перезаписываются - но не удаляются. Вручную можно изменить расписание задания резервного копирования, с усечением количества "томов" (сопоставленных с кассетой магнитной ленты) и высвобождением невостребованных, после чего зачистить сведения о таковых - но файлы это не удаляет.
Задача: в связи с переходом на методику резервного копирования по принципу "одно задание - один том" организовать автоматизированное удаление "томов" резервных копий, срок хранения который превысил указанный в настройках задания резервного копирования "Bacula".
В описании каждого задания резервного копирования, устаревшие "тома" результатов которой которой мы хотели бы автоматически зачищать, включаем следующий блок конфигурации:
# vi /etc/bacula/client.d/example.net.conf
....
Job {
Name = "example.net"
....
# Подключаем конфигурацию для автоматизации зачистки устаревших резервных копий
# (это включение должно быть одним из последних в описании задания)
@/etc/bacula/includes/purge.conf
}
Job {
Name = "example.net"
....
# Подключаем конфигурацию для автоматизации зачистки устаревших резервных копий
# (это включение должно быть одним из последних в описании задания)
@/etc/bacula/includes/purge.conf
}
Подход к зачистке устаревших "томов" единообразный, и сводится к вызову соответствующего скрипта после успешного завершения процедуры резервного копирования:
# vi /etc/bacula/includes/purge.conf
# Запускаем скрипт зачистки устаревших томов после каждого исполнения задания
RunScript {
RunsWhen = After; RunsOnFailure = no; RunsOnClient = no;
Command = "/etc/bacula/scripts/purge-vol.sh %n"
}
RunScript {
RunsWhen = After; RunsOnFailure = no; RunsOnClient = no;
Command = "/etc/bacula/scripts/purge-vol.sh %n"
}
Логика работы скрипта "purge-vol.sh" будет опираться на подаваемое ему единственным параметром имя задания резервного копирования, от которого мы изначально договорились строить систему именований "томов" резервных копий:
1. Посредством утилиты "bconsole" ищем пул указанного задания резервного копирования и выясняем срок хранения его томов.
2. На основании полученного параметра от текущей вычисляем дату, ранее которой тома будут считаться устаревшими.
3. Посредством утилиты "bconsole" запрашиваем список имён томов, последняя запись в которые была ранее вычисленного порога.
4. Утилите "bconsole" отправляем указания удалить сведения об устаревших томах и их содержимое, по списку.
5. Файлы устаревших томов удаляем из файловой системы, по списку.
2. На основании полученного параметра от текущей вычисляем дату, ранее которой тома будут считаться устаревшими.
3. Посредством утилиты "bconsole" запрашиваем список имён томов, последняя запись в которые была ранее вычисленного порога.
4. Утилите "bconsole" отправляем указания удалить сведения об устаревших томах и их содержимое, по списку.
5. Файлы устаревших томов удаляем из файловой системы, по списку.
Пишем скрипт на привычном мне языке "Bash":
# vi /etc/bacula/scripts/purge-vol.sh && chmod ug+x /etc/bacula/scripts/purge-vol.sh
#!/bin/bash
STORAGE="/mnt"
LOG="/var/log/bacula/purge-vol.log"
JOB=${1}
# Проверяем корректность вводных данных
[ ! "${1}" ] && { echo "Не указано наименование задачи резервного копирования. Операция прервана."; exit 1; }
CHECK=`echo -e "show job=${JOB}\nexit" | bconsole | grep -i -E "^Job:[[:space:]]+name=${JOB}.*Enabled=1"`
if [ -z "${CHECK}" ] ; then
echo "Не обнаружена разрешённая к исполнению задача резервного копирования \"${JOB}\". Операция прервана."; exit 1;
fi
# Вычленяем значение параметра срока хранения резервных копий в "пуле" (в секундах)
VOLRETENTION=`echo -e "sqlquery\nselect VolRetention from Pool where Name = 'pool-${JOB}' limit 1;\n.\nexit" | bconsole | grep '^|' | grep -i -v 'VolRetention' | sed -e 's/[ ]*|[ ]*//g' | sed -e '/^[ \t]*$/d' | sed -e 's/,//g'`
# Проверяем успешность получения срока хранения резервных копий "пула" задания
# (во избежание грубейших ошибок примем за минимально допустимый срок хранения в один месяц)
if [ ! -z "${VOLRETENTION}" ] && [ "${VOLRETENTION}" -gt "2678400" ] ; then
# Вычисляем дату, ранее которой резервная копия будет считаться устаревшей
DATECUT=`date --date="${VOLRETENTION} seconds ago" '+%Y-%m-%d'`
# Запрашиваем перечень устаревших "томов", последняя запись в которые была ранее установленного лимита
DELETELIST=`echo -e "sqlquery\nselect VolumeName from Media where VolumeName like '${JOB}%' and LastWritten < '${DATECUT}' order by VolumeName;\n.\nexit" | bconsole | grep '^|' | grep -i -v ' VolumeName ' | sed -e 's/[ ]*|[ ]*//g' | sed -e '/^[ \t]*$/d'`
# Обрабатываем список "томов" на удаление
if [ ! -z "${DELETELIST}" ] ; then
# Записываем в журнал событий отметку о начале проведения процедуры и далее о ресурсах
echo >> "${LOG}"
echo "$(date +"%Y-%m-%d %H:%M:%S") deleted obsolete volumes:" | tee -a "${LOG}"
# Отправляем "Bacula" указание удалить сведения об указанных "томах" и их содержимое
echo -e "${DELETELIST}" | xargs -I {} echo "purge yes volume={}" | bconsole > /dev/null 2>&1
echo -e "${DELETELIST}" | xargs -I {} echo "delete yes volume={}" | bconsole > /dev/null 2>&1
# Элементарно ищем совпадения по имени тома во всех системах хранения и удаляем файлы "томов" непосредственно с файловой системы
for VOLUME in ${DELETELIST} ; do
FULLNAME=$(find "${STORAGE}/" -type f -iname "${VOLUME}" -exec echo {} \; -quit)
if [ ! -z "${FULLNAME}" ] && [ -f "${FULLNAME}" ] ; then
# Перед удалением выводим в STDOUT и журнал событий сведения о полном имени и размере целевых файлов
ls -lah "${FULLNAME}" | awk '{print " Deleted: " $9 " ("$5")"}' | tee -a "${LOG}"
rm -f "${FULLNAME}"
fi
done
fi
else
echo "Не удалось выяснить срок хранения резервных копий по задаче \"${JOB}\", или таковой не выходит за установленный предел. Операция прервана."; exit 1;
fi
exit ${?}
STORAGE="/mnt"
LOG="/var/log/bacula/purge-vol.log"
JOB=${1}
# Проверяем корректность вводных данных
[ ! "${1}" ] && { echo "Не указано наименование задачи резервного копирования. Операция прервана."; exit 1; }
CHECK=`echo -e "show job=${JOB}\nexit" | bconsole | grep -i -E "^Job:[[:space:]]+name=${JOB}.*Enabled=1"`
if [ -z "${CHECK}" ] ; then
echo "Не обнаружена разрешённая к исполнению задача резервного копирования \"${JOB}\". Операция прервана."; exit 1;
fi
# Вычленяем значение параметра срока хранения резервных копий в "пуле" (в секундах)
VOLRETENTION=`echo -e "sqlquery\nselect VolRetention from Pool where Name = 'pool-${JOB}' limit 1;\n.\nexit" | bconsole | grep '^|' | grep -i -v 'VolRetention' | sed -e 's/[ ]*|[ ]*//g' | sed -e '/^[ \t]*$/d' | sed -e 's/,//g'`
# Проверяем успешность получения срока хранения резервных копий "пула" задания
# (во избежание грубейших ошибок примем за минимально допустимый срок хранения в один месяц)
if [ ! -z "${VOLRETENTION}" ] && [ "${VOLRETENTION}" -gt "2678400" ] ; then
# Вычисляем дату, ранее которой резервная копия будет считаться устаревшей
DATECUT=`date --date="${VOLRETENTION} seconds ago" '+%Y-%m-%d'`
# Запрашиваем перечень устаревших "томов", последняя запись в которые была ранее установленного лимита
DELETELIST=`echo -e "sqlquery\nselect VolumeName from Media where VolumeName like '${JOB}%' and LastWritten < '${DATECUT}' order by VolumeName;\n.\nexit" | bconsole | grep '^|' | grep -i -v ' VolumeName ' | sed -e 's/[ ]*|[ ]*//g' | sed -e '/^[ \t]*$/d'`
# Обрабатываем список "томов" на удаление
if [ ! -z "${DELETELIST}" ] ; then
# Записываем в журнал событий отметку о начале проведения процедуры и далее о ресурсах
echo >> "${LOG}"
echo "$(date +"%Y-%m-%d %H:%M:%S") deleted obsolete volumes:" | tee -a "${LOG}"
# Отправляем "Bacula" указание удалить сведения об указанных "томах" и их содержимое
echo -e "${DELETELIST}" | xargs -I {} echo "purge yes volume={}" | bconsole > /dev/null 2>&1
echo -e "${DELETELIST}" | xargs -I {} echo "delete yes volume={}" | bconsole > /dev/null 2>&1
# Элементарно ищем совпадения по имени тома во всех системах хранения и удаляем файлы "томов" непосредственно с файловой системы
for VOLUME in ${DELETELIST} ; do
FULLNAME=$(find "${STORAGE}/" -type f -iname "${VOLUME}" -exec echo {} \; -quit)
if [ ! -z "${FULLNAME}" ] && [ -f "${FULLNAME}" ] ; then
# Перед удалением выводим в STDOUT и журнал событий сведения о полном имени и размере целевых файлов
ls -lah "${FULLNAME}" | awk '{print " Deleted: " $9 " ("$5")"}' | tee -a "${LOG}"
rm -f "${FULLNAME}"
fi
done
fi
else
echo "Не удалось выяснить срок хранения резервных копий по задаче \"${JOB}\", или таковой не выходит за установленный предел. Операция прервана."; exit 1;
fi
exit ${?}
Ограничиваем доступ, профилактически сразу ко всем конфигурациям сервиса "Bacula":
# chown -R bacula:bacula /etc/bacula
# chmod -R o-rwx /etc/bacula
# chmod -R o-rwx /etc/bacula
На этом всё. Учитывая то, что Bash-скрипт запускается на исполнение в контексте пользователя "bacula", каких-то дополнительных действий аутентификации при обращении к утилите "bconsole" и файловым ресурсам сервиса "Bacula" не требуется.
Есть смысл поглядывать в журнал событий "/var/log/bacula/purge-vol.log" - там будут записи об удалённых файлах, с указанием их размера, или сообщения об ошибках. Усечение и архивирование разросшихся файлов журнала мы наладили ранее, в соответствие с инструкцией по установке сервера "Bacula".