247 lines
11 KiB
ReStructuredText
247 lines
11 KiB
ReStructuredText
|
:title: Делаем скучные бэкапы
|
|||
|
:date: 8 Oct 22
|
|||
|
|
|||
|
=====================
|
|||
|
Делаем скучные бэкапы
|
|||
|
=====================
|
|||
|
|
|||
|
Бэкапы, в самом деле, нескучные (:
|
|||
|
|
|||
|
Мотивацией для написания очередного велосипеда стало то, что даже для, казалось
|
|||
|
бы, готовых инструментов приходится писать обвязки на шелле.
|
|||
|
|
|||
|
В какой-то момент мне надоело писать баш-портянки для бэкапа и я написал одну
|
|||
|
большую, которая умеет сразу много всего. Знакомьтесь — **boring_backup**.
|
|||
|
|
|||
|
> **Пакеты для Debian/Ubuntu, Arch и сорцы**
|
|||
|
`здесь <https://git.nxhs.cloud/ge/boring_backup/releases>`_.
|
|||
|
|
|||
|
Сначала о том, чем он *НЕ* является:
|
|||
|
|
|||
|
* Тупым линейным скриптом с вызовом tar и mysqldump.
|
|||
|
* Полным аналогом `restic <https://restic.net/>`_ и похожих утилит.
|
|||
|
* POSIX-совместимым скриптом. Хотелось бы, конечно, но это достаточно больно.
|
|||
|
Может быть когда-нибудь я перепишу код и оно заработает на dash/ash и прочих
|
|||
|
шеллах, но это будет уже другой скрипт.
|
|||
|
|
|||
|
**boring_backup** — это библиотека функций Bash и интерфейс командной строки
|
|||
|
для скриптов резервного копирования.
|
|||
|
|
|||
|
Вот что в ней есть:
|
|||
|
|
|||
|
* Обработка ошибок (wow!)
|
|||
|
* Логирование в файл или syslog (WOW!)
|
|||
|
* Парсер URI практически целиком совместимый с RFC 3986!
|
|||
|
* Готовые функции для запаковки файлов (tar) и снятия SQL-дампов (MySQL/MariaDB
|
|||
|
и PostgreSQL)
|
|||
|
|
|||
|
Логика работы утилиты крайне проста. Есть два массива данных — список URI
|
|||
|
исходных ресурсов, которые надо бэкапить и список URI хранилищ, куда предстоит
|
|||
|
бэкапиться.
|
|||
|
|
|||
|
Поддерживается определённый набор схем URI, каждую схему обрабатывает свой
|
|||
|
обработчик.
|
|||
|
|
|||
|
Cамый простой скрипт резервного копирования, который можно написать с
|
|||
|
помощью **boring_backup** выглядит так:
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(/home/john)
|
|||
|
targets=(/var/backups)
|
|||
|
|
|||
|
Сохраним это в файл под именем `test`.
|
|||
|
|
|||
|
Здесь очевидно дира /home/john будет скопирована в /var/backups, однако cперва
|
|||
|
она будет запакована в архив tar без сжатия. Запускаем бэкап:
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
boring_backup test
|
|||
|
|
|||
|
Итого получим файл /var/backups/test_john_2022.10.08-1230.tar. Если мы хотим
|
|||
|
сжать архив, то добавим компрессию:
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(/home/john)
|
|||
|
targets=(/var/backups)
|
|||
|
compression=xz
|
|||
|
|
|||
|
Здесь значение переменной ``compression`` будет интерпретироваться так же как
|
|||
|
tar интерпретирует расширение архива с опцией ``--auto-compress``. То есть
|
|||
|
поддерживаются все утилиты, которыми tar умеет сжимать. Если переменная пуста
|
|||
|
или содержит что-то не то, то сжатие будет отключено.
|
|||
|
|
|||
|
Надо исключить лишние файлы из архива? Легко!
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(/home/john)
|
|||
|
targets=(/var/backups)
|
|||
|
compression=xz
|
|||
|
tar_exclude=(.cache foo bar)
|
|||
|
|
|||
|
Для передачи опций для tar есть переменная ``tar_options`` в которой можно
|
|||
|
переопределить умолчание — ``-acf``.
|
|||
|
|
|||
|
Теперь добавим базу данных MySQL в бэкап. Записывается URI в формате, который
|
|||
|
ещё называют DSN (data source name):
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(
|
|||
|
/home/john
|
|||
|
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
|||
|
)
|
|||
|
targets=(/var/backups)
|
|||
|
compression=xz
|
|||
|
tar_exclude=(.cache foo bar)
|
|||
|
|
|||
|
Обратите внимание, что пароль пользователя закодирован, чтобы не возникло
|
|||
|
коллизий из-за наличия в нём спецсимволов. Закодировать пароль крайне просто,
|
|||
|
вот пример на Perl, который можно выполнить прямо в командной строке:
|
|||
|
|
|||
|
.. code-block:: perl
|
|||
|
|
|||
|
echo ',#s{RGqH' | perl -MURI::Escape -wlne 'print uri_escape $_'
|
|||
|
|
|||
|
Теперь в /var/backups окажется два файла — test_john_2022.10.08-1230.tar.xz
|
|||
|
и test_test_db_2022.10.08-1230.sql.xz. Сваливать все файлы в одну директорию
|
|||
|
может быть неудобно, поэтому давайте сохранять файлы в папки по датам.
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(
|
|||
|
/home/john
|
|||
|
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
|||
|
)
|
|||
|
today=$(date +%Y-%m-%d) # в результате даст дату yyyy-mm-dd
|
|||
|
make_target_dir=yes
|
|||
|
targets=(/var/backups/$today)
|
|||
|
compression=xz
|
|||
|
tar_exclude=(.cache foo bar)
|
|||
|
|
|||
|
Теперь пути до файлов будут выглядеть так:
|
|||
|
/var/backups/2022-10-08/test_john_2022.10.08-1230.tar.xz.
|
|||
|
|
|||
|
Теперь поробуем передать эти файлы ещё и на удалённое хранилище. Допустим на
|
|||
|
S3-совместимое объектное хранилище. Сперва придётся установить и настроить
|
|||
|
утилиту s3cmd, затем скрипт примет вид:
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(
|
|||
|
/home/john
|
|||
|
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
|||
|
)
|
|||
|
today=$(date +%Y-%m-%d)
|
|||
|
make_target_dir=yes
|
|||
|
targets=(
|
|||
|
/var/backups/$today
|
|||
|
s3://my_bucket/$today
|
|||
|
)
|
|||
|
compression=xz
|
|||
|
tar_exclude=(.cache foo bar)
|
|||
|
|
|||
|
**boring_backup** сначала сделает бэкап и сохранит его в /var/backups и только
|
|||
|
затем передаст файлы в S3-хранилище (или любое другое). Можно указывать любое
|
|||
|
количество URI в targets и также в sources. Особенностью targets является то,
|
|||
|
что требуется хотя бы один URI со схемой `file`, куда будут сохраняться
|
|||
|
локальные бэкапы. Просто путь без указания схемы тоже считается за file.
|
|||
|
Следующие три записи полностью эквивалентны:
|
|||
|
|
|||
|
* /var/backups
|
|||
|
* file:/var/backups
|
|||
|
* file:///var/backups
|
|||
|
|
|||
|
Пойдём дальше.
|
|||
|
|
|||
|
Мы можем обрабатывать ошибки. Допишем в скрипт функцию `on_error`, которая
|
|||
|
будет нам отправлять письмо с фрагментом лога и текстом ошибки.
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(
|
|||
|
/home/john
|
|||
|
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
|||
|
)
|
|||
|
today=$(date +%Y-%m-%d)
|
|||
|
make_target_dir=yes
|
|||
|
targets=(
|
|||
|
/var/backups/$today
|
|||
|
s3://my_bucket/$today
|
|||
|
)
|
|||
|
compression=xz
|
|||
|
tar_exclude=(.cache foo bar)
|
|||
|
|
|||
|
email=me@example.com
|
|||
|
on_error() {
|
|||
|
local log_fragment err_message
|
|||
|
err_message="$*"
|
|||
|
log_fragment="$(grep -n 'Backup started' "$log_file" | tail -1 |
|
|||
|
cut -d ':' -f 1 | xargs -I {} tail -n +{} "$log_file")"
|
|||
|
printf \
|
|||
|
'Текст ошибки:\n%s\n\nЛог последнего бэкапа:\n%s' \
|
|||
|
"$err_message" "$log_fragment" |
|
|||
|
mail -s "$HOSTNAME: Ошибка при выполнении бэкапа" "$email"
|
|||
|
}
|
|||
|
|
|||
|
Также мы можем после копирование в удалённое хранилище удалять локальный
|
|||
|
бэкап. Список всех созданных за текущее выполнение скрипта бэкапов хранится
|
|||
|
в массиве ``backups``.
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
finalise() {
|
|||
|
log -p "\tClean up local backups"
|
|||
|
log -p "\tFiles to delete: ${backups[@]}"
|
|||
|
rm -rv -- "${backups[@]}"
|
|||
|
log -p "\tLocal backups deleted"
|
|||
|
}
|
|||
|
|
|||
|
Это лишь небольшая часть того, что можно реализовать. Полная документация
|
|||
|
есть `в виде мануала </cgi-bin/man.cgi?q=boring_backup&l=ru>`_.
|
|||
|
|
|||
|
Отмечу ещё несколько важных фич:
|
|||
|
|
|||
|
* Стандартную логику можно полностью переопределить, в том числе выкинуть
|
|||
|
необходимость сохранять локальный бэкап. Достаточно описать функцию с
|
|||
|
именем `backup`.
|
|||
|
* Можно выполнять действия перед бэкапом в функции `prepare` и после бэкапа
|
|||
|
— `finalise`.
|
|||
|
|
|||
|
Есть и вещи, которые вам не понравятся, но решение которых вероятно появится
|
|||
|
в слудующих версиях **boring_backup**:
|
|||
|
|
|||
|
* Концепция репозитория бэкапов не поддерживается. Файлы просто загружается
|
|||
|
туда куда вы укажете.
|
|||
|
* Из коробки можно делать только полные бэкапы.
|
|||
|
* Нет ротации бэкапов. Её нужно реализовывать самостоятельно.
|
|||
|
* Из коробки нет шифрования. GPG и кастомная функция `backup` вам в помощь.
|
|||
|
|
|||
|
В заключение дам ещё один пример скрипта для бэкапа приложения Gitea.
|
|||
|
Попробуйте сами разобраться что тут происходит.
|
|||
|
|
|||
|
.. code-block:: shell
|
|||
|
|
|||
|
sources=(.) # just pass validation
|
|||
|
targets=(.)
|
|||
|
today="$(date +%d_%b_%Y)"
|
|||
|
s3cmd_config=~/.s3cfg
|
|||
|
prepare() {
|
|||
|
systemctl stop gitea.service
|
|||
|
sleep 5
|
|||
|
}
|
|||
|
backup() {
|
|||
|
log -p "Dumping Gitea"
|
|||
|
su -c "/usr/local/bin/gitea dump -c /etc/gitea/app.ini \
|
|||
|
-f /home/git/.cache/gitea_dump.zip" - git 2>> "$log_file"
|
|||
|
backups+=(/home/git/.cache/gitea_dump.zip)
|
|||
|
tgt_s3cmd s3://mybucket/backups/gitea-$today
|
|||
|
}
|
|||
|
finalise() {
|
|||
|
systemctl start gitea.service
|
|||
|
rm -rv -- "${backups[@]}"
|
|||
|
}
|