Известное правило эксплуатационщика гласит: "резервных копий не бывает много". Следуя ему я обычно сохраняю данные двумя способами: посредством централизованной гибко настраиваемой системы вроде "Acronis/Bacula/Veeam" и локально по расписанию скриптами Bash.
Тема работы со специализированными программными комплексами обширная, но здесь не об этом. Каждый сколь либо активно работающий сервер я прежде всего бэкаплю простейшим архивированием важных файлов, а также выгрузкой полного среза "баз данных" - отчего даже на сервере с простым набором сервисов каждый день появляется минимум два новых файла с резервными копиями. На практике разного рода типа бэкапов бывает больше, количество файлов растёт, и очень скоро в полный рост встаёт вопрос об их ротации или прореживании, если история изменения данных важна.
Мой подход к организации хранения резервных копий базируется на соблюдении следующих правил:
1. Все файлы резервных копий содержат в именах дату создания таковых в формате "YYYY-MM-DD".
2. При определении даты создания файла опираемся на указанное в имени такового, а не на метку времени файловой системы.
3. Периодически (раз в два-три дня) запускаем процедуру прореживание файлов резервных копий.
4. Всегда оставляем нетронутым резервные копии за последнюю неделю.
5. Оставляем по одному набору файлов в месяц (обычно за последний день).
2. При определении даты создания файла опираемся на указанное в имени такового, а не на метку времени файловой системы.
3. Периодически (раз в два-три дня) запускаем процедуру прореживание файлов резервных копий.
4. Всегда оставляем нетронутым резервные копии за последнюю неделю.
5. Оставляем по одному набору файлов в месяц (обычно за последний день).
В итоге у меня в распоряжении всегда имеются свежие резервные копии недельного периода активной работы, и по одному набору копий на каждый прошедший месяц. Совсем старые бэкапы, вылёживающиеся более года, можно удалять вручную, в рамках процедур профилактического обслуживания.
Пишем скрипт прореживания резервных копий.
Мне нравится использовать встроенное в несущие операционные системы программное обеспечение. "Bash" есть везде и всегда, так что выбор очевиден. Готовим место для скрипта и пишем таковой:
# mkdir -p /usr/local/etc/backup
# vi /usr/local/etc/backup/backups-thinning.sh
# chmod ugo+x /usr/local/etc/backup/backups-thinning.sh
# vi /usr/local/etc/backup/backups-thinning.sh
# chmod ugo+x /usr/local/etc/backup/backups-thinning.sh
#!/bin/bash
# Месторасположение директорий с резервными копиями.
DPATH="/var/backups/web"
# Месторасположение журнала событий.
LOG="/var/log/backups-thinning.log"
# # Прореживаем бэкапы за прошедший месяц (за исключением ближайшей недели), оставляя по одному последнему в месяце. # #
# # Опираемся на имена файлов, содержащие даты в формате "YYYY-MM-DD", а не на метку времени создания таковых в файловой системе! # #
# Получаем перечень всех дат формата "YYYY-MM-DD", содержащихся в именах файлов,
# сортируя даты с исключением дубликатов, и выводим список в массив переменных.
ADAYS=( $(/usr/bin/find ${DPATH} -maxdepth 1 -type f | grep -E -o -z "[0-9]{4}-[0-9]{2}-[0-9]{2}" | sort -u -z | xargs --null -I {} echo {}) )
# Проходим (опираясь на "индексы") по массиву дат, нормализуя перечень.
for I in "${!ADAYS[@]}"; do
# Пропускаем строки, которые не содержат корректных дат формата "YYYY-MM-DD".
date +%Y-%m-%d -d "${ADAYS[$I]}" > /dev/null 2>&1; [ ${?} -ne 0 ] && { unset ADAYS[$I]; continue; }
# Пропускаем строки, содержащие даты ближе недели к текущей (они не подлежат удалению).
[ $(date +%s -d "${ADAYS[$I]}") -gt $(date +%s -d "7 day ago") ] && { unset ADAYS[$I]; continue; }
done
unset I
# Вычленяем из перечня дней список упомянутых сочетаний "год-месяц" в формате "YYYY-MM".
AMONTHS=( $(printf "%s\n" "${ADAYS[@]}" | grep -E -o "[0-9]{4}-[0-9]{2}" | sort -u) )
# Фиксируем время начала процедуры прореживания.
echo >> ${LOG}; echo "Thinning start at $(date +%Y-%m-%dT%H:%M):" >> ${LOG}
# Перебираем все месяцы, содержащиеся в именах файлов бэкапов.
for I in "${!AMONTHS[@]}"; do
# Выбираем все упоминаемые дни обрабатываемого месяца, вычёркивая последний из списка дней обрабатываемого месяца.
ATRGS=( $(printf "%s\n" "${ADAYS[@]}" | grep "${AMONTHS[$I]}" | sort -u | head -n -1) )
# Проходим по перечню отфильтрованных дат и удаляем файлы, подпадающие под маску.
for II in "${!ATRGS[@]}"; do
ls ${DPATH}/*${ATRGS[$II]}* >> ${LOG}
rm --force ${DPATH}/*${ATRGS[$II]}*
done
unset II
# Зачищаем массив исполнительных данных для следующей итерации.
unset ATRGS
done
unset I
exit 0
# Месторасположение директорий с резервными копиями.
DPATH="/var/backups/web"
# Месторасположение журнала событий.
LOG="/var/log/backups-thinning.log"
# # Прореживаем бэкапы за прошедший месяц (за исключением ближайшей недели), оставляя по одному последнему в месяце. # #
# # Опираемся на имена файлов, содержащие даты в формате "YYYY-MM-DD", а не на метку времени создания таковых в файловой системе! # #
# Получаем перечень всех дат формата "YYYY-MM-DD", содержащихся в именах файлов,
# сортируя даты с исключением дубликатов, и выводим список в массив переменных.
ADAYS=( $(/usr/bin/find ${DPATH} -maxdepth 1 -type f | grep -E -o -z "[0-9]{4}-[0-9]{2}-[0-9]{2}" | sort -u -z | xargs --null -I {} echo {}) )
# Проходим (опираясь на "индексы") по массиву дат, нормализуя перечень.
for I in "${!ADAYS[@]}"; do
# Пропускаем строки, которые не содержат корректных дат формата "YYYY-MM-DD".
date +%Y-%m-%d -d "${ADAYS[$I]}" > /dev/null 2>&1; [ ${?} -ne 0 ] && { unset ADAYS[$I]; continue; }
# Пропускаем строки, содержащие даты ближе недели к текущей (они не подлежат удалению).
[ $(date +%s -d "${ADAYS[$I]}") -gt $(date +%s -d "7 day ago") ] && { unset ADAYS[$I]; continue; }
done
unset I
# Вычленяем из перечня дней список упомянутых сочетаний "год-месяц" в формате "YYYY-MM".
AMONTHS=( $(printf "%s\n" "${ADAYS[@]}" | grep -E -o "[0-9]{4}-[0-9]{2}" | sort -u) )
# Фиксируем время начала процедуры прореживания.
echo >> ${LOG}; echo "Thinning start at $(date +%Y-%m-%dT%H:%M):" >> ${LOG}
# Перебираем все месяцы, содержащиеся в именах файлов бэкапов.
for I in "${!AMONTHS[@]}"; do
# Выбираем все упоминаемые дни обрабатываемого месяца, вычёркивая последний из списка дней обрабатываемого месяца.
ATRGS=( $(printf "%s\n" "${ADAYS[@]}" | grep "${AMONTHS[$I]}" | sort -u | head -n -1) )
# Проходим по перечню отфильтрованных дат и удаляем файлы, подпадающие под маску.
for II in "${!ATRGS[@]}"; do
ls ${DPATH}/*${ATRGS[$II]}* >> ${LOG}
rm --force ${DPATH}/*${ATRGS[$II]}*
done
unset II
# Зачищаем массив исполнительных данных для следующей итерации.
unset ATRGS
done
unset I
exit 0
Регистрируем расписание.
Естественно, что процедура прореживания цикличных бэкапов по определению периодична, так что настроим запуск по расписанию (например раз сутки, после полуночи):
# vi /etc/crontab
....
# Backups daily thinning
0 3 * * * root /usr/local/etc/backup/backups-thinning.sh &
# Backups daily thinning
0 3 * * * root /usr/local/etc/backup/backups-thinning.sh &