Пропавшая база приносит убытки, но украденная копия приносит их вдвойне: у кого на руках дамп, у того и пароли, и контакты, и история заказов. Файл выгрузки MySQL или MariaDB, лежащий в каталоге или облаке без шифрования, сводит защиту на нет, потому что читается любым текстовым редактором. Поэтому шифрование копий - не модная опция, а часть самой процедуры, наравне с расписанием и ротацией. Схема ниже строится на двух инструментах: mysqldump снимает логический дамп, mariabackup делает физическое горячее копирование, openssl закрывает оба потока шифром на лету.
Порядок простой: сначала роли и гранты, затем скрипт дампа с шифрованием потока, физические копии с добавочными уровнями, ротация, перенос на вторую машину и проверка восстановления. Все инструменты бесплатны и живут в репозиториях любого дистрибутива, вся схема умещается в пару скриптов и один файл ключа.
Слои схемы защиты от логического дампа до журналов операций
Логический дамп - это текст SQL. mysqldump читает базу построчно, и включённый по умолчанию ключ --quick не даёт утилите складывать строки в память. Такой файл переносится между версиями и платформами, читается глазами, восстанавливается частями. Слабое место - скорость: база на сотни гигабайт выгружается и заливается часами.
Физическая копия снимает файлы как есть. mariabackup переносит данные InnoDB в горячем режиме, почти не мешая рабочим запросам, а восстановление сводится к возврату файлов на место и короткой подготовке. Инструмент принадлежит стеку MariaDB: физические горячие копии MySQL делают утилитой XtraBackup или встроенным плагином clone, mariabackup на MySQL не рассчитан.
Третий слой - бинлоги. Между двумя копиями сервер пишет журнал всех операций, и при аварии недостающий хвост докатывается утилитой mysqlbinlog прямо до секунды перед сбоем. Четвёртый слой - шифрование и вынос копий за пределы машины: архив, который не читается без ключа, и вторая площадка закрывают сразу два сценария потери.
Ориентир по версиям. У MariaDB длинной поддержкой закрыты серии 11.4 и 11.8, за ними выходят короткие 12.x и 13.x, у MySQL линия с долгой поддержкой - 8.4. mysqldump присутствует в обоих стеках, в MariaDB он вызывается и под именем mariadb-dump, ключи почти совпадают, различия заметны в мелочах вроде имени опции записи координат бинлога.
Подготовка пользователя и параметров сервера перед первым запуском
Дамп снимается служебной учёткой с минимальным набором прав. Для логической выгрузки MySQL нужны SELECT, SHOW VIEW, TRIGGER и EVENT на нужных схемах, PROCESS, если не передан ключ --no-tablespaces, а при включённых GTID и --single-transaction ещё и RELOAD. Право PROCESS требуется потому, что утилита читает метаданные табличных пространств; ключ --no-tablespaces снимает это требование, но тогда определения пространств в дамп не попадут. Одним блоком:
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'S0me-Long-Passphrase';
GRANT SELECT, SHOW VIEW, TRIGGER, EVENT, LOCK TABLES, PROCESS, RELOAD ON *.* TO 'backup'@'localhost';
Для mariabackup хватает RELOAD, PROCESS, LOCK TABLES и REPLICATION CLIENT:
CREATE USER 'xb'@'localhost' IDENTIFIED BY 'S0me-Long-Passphrase';
GRANT RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'xb'@'localhost';
Пароли держат в ~/.my.cnf с правами 600, утилиты обоих стеков читают секцию [client] молча:
# ~/.my.cnf
[client]
user=backup
password=S0me-Long-Passphrase
host=localhost
На стороне сервера для отката по времени включается журнал операций, его глубина в свежих версиях MySQL ограничивается переменной binlog_expire_logs_seconds. Для больших дампов по сети помогает поднятый max_allowed_packet и увеличенные таймауты чтения, локальная выгрузка через сокет снимает эти вопросы вовсе.
Скрипт логического дампа со шифрованием потока на лету
Скрипт выстраивает конвейер: mysqldump отдаёт текст, gzip сжимает, openssl шифрует. Порядок именно такой, потому что шифр превращает данные в шум, который не сжимается: сначала сжатие, потом запирание.
#!/bin/bash
set -euo pipefail
BASE=/var/backups/mysql
KEY=/etc/backup/backup.key
STAMP=$(date +%F_%H%M)
LOG=/var/log/mysql-dump.log
exec >>"$LOG" 2>&1
mysqldump --single-transaction --quick --hex-blob --routines --events --triggers --source-data --all-databases | gzip -9 | openssl enc -aes-256-cbc -pbkdf2 -iter 60000 -salt -pass file:"$KEY" -out "$BASE/all-$STAMP.sql.gz.enc"
sha256sum "$BASE/all-$STAMP.sql.gz.enc" > "$BASE/all-$STAMP.sha256"
openssl enc -d -aes-256-cbc -pbkdf2 -iter 60000 -pass file:"$KEY" -in "$BASE/all-$STAMP.sql.gz.enc" | zcat | tail -n 2
find "$BASE" -name 'all-*.enc' -mtime +14 -delete
find "$BASE" -name 'all-*.sha256' -mtime +14 -delete
echo "$(date +%F_%T) dump ok: $(du -sm "$BASE/all-$STAMP.sql.gz.enc" | awk '{print $1}') MiB"
Пояснения. Ключ --single-transaction даёт согласованный снимок InnoDB без блокировок. Хранимые процедуры, события и триггеры выгружаются ключами --routines, --events и --triggers, двоичные поля - ключом --hex-blob, иначе блобы ломаются при переносе между версиями. Ключ --source-data записывает в дамп координаты бинлога, требует включённого журнала операций и в MariaDB называется --master-data. При включённых GTID в игру вступает ключ --set-gtid-purged, определяющий, попадёт ли в дамп отметка о перенесённых транзакциях: при переносе на сервер с другой историей поведение ключа стоит проверить заранее. Контрольная строка с tail нужна не для галочки: конец дампа помечен комментарием "-- Dump completed", по нему видно, что файл дожил до конца. Соль и 60 тысяч итераций PBKDF2 делают перебор парольной фразы дорогим удовольствием.
Физическое горячее копирование через mariabackup с добавочными уровнями
Когда база вырастает за сотни гигабайт, ночные дампы перестают успевать. Физическое копирование решает иначе: mariabackup переносит файлы табличных пространств, почти не нагружая сервер, раз в неделю снимается полная копия, в остальные дни - добавочные. В начале и в конце съёма инструмент коротко блокирует таблицы, чтобы зафиксировать согласованность метаданных, остальное время запись не останавливается.
mariabackup --backup --target-dir=/var/backups/mysql/full-2026-10-04
mariabackup --backup --target-dir=/var/backups/mysql/inc-2026-10-09 --incremental-basedir=/var/backups/mysql/full-2026-10-04
Снятая копия не готова к старту сервера, её доводит ключ --prepare. Цепочка собирается строго по порядку: полная готовится с --apply-log-only, последняя добавочная поверх неё - без него:
mariabackup --prepare --apply-log-only --target-dir=/var/backups/mysql/full-2026-10-04
mariabackup --prepare --target-dir=/var/backups/mysql/inc-2026-10-09 --incremental-dir=/var/backups/mysql/full-2026-10-04
Восстановление сводится к остановке службы, очистке каталога данных, возврату файлов и поправке владельца:
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/var/backups/mysql/inc-2026-10-09
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb
Добавочная копия строится от позиции LSN в журнале, поэтому цепочка собирается только последовательно, перепрыгивать уровни нельзя.
Шифрование физических копий и правила хранения ключей
Физическую копию шифруют двумя путями. Первый - шифровать поток прямо при съёме: mariabackup умеет отдавать данные в формате xbstream, и конвейер не оставляет на диске открытых файлов вовсе:
mariabackup --backup --stream=xbstream --user=xb | openssl enc -aes-256-cbc -pbkdf2 -iter 60000 -salt -pass file:/etc/backup/backup.key -out /var/backups/mysql/full-$STAMP.xbs.enc
Обратный путь - дешифровка и распаковка в пустой каталог, затем --prepare:
openssl enc -d -aes-256-cbc -pbkdf2 -iter 60000 -pass file:/etc/backup/backup.key -in full-2026-10-09.xbs.enc | xbstream -x -C /srv/restore
mariabackup --prepare --target-dir=/srv/restore
Второй путь - шифровать уже снятый каталог перед переносом, конвейером вида "tar -C $FULL -cf - . | openssl enc ... -out copy.tar.enc". Оба варианта равнозначны, потоковый короче и не требует свободного места под промежуточный архив.
Правила ключей короткие. Файл ключа лежит с правами 600, на разделе, куда не дотягивается резервная копия, и никогда не переносится в одном наборе с копиями. Ключ меняется при смене персонала или компрометации хоста, старые копии при этом дочитываются прежним ключом, поэтому ключи хранятся и после ротации. Раз в квартал контрольная дешифровка копии месячной давности подтверждает, что связка ключа и архива жива.
Ротация по возрастам и перенос копий на вторую машину
Экономика. Текстовый дамп сжимается gzip-ом в 5-10 раз: база на 80 ГБ превращается в архив 8-15 ГБ, шифрование размер почти не меняет. Физическая копия занимает объём данных целиком, добавочные - лишь дельту. Полная копия раз в неделю с месячной глубиной хранения, добавочные ежедневно с двухнедельной глубиной и суточные дампы за две недели дают ориентировочно 550-650 ГБ при исходных 80 ГБ.
Хранить всё в одном каталоге - значит рано или поздно упереться в диск, поэтому правила удаления прописываются явно:
find "$BASE" -name 'full-*' -mtime +35 -delete
find "$BASE" -name 'inc-*' -mtime +16 -delete
find "$BASE" -name 'all-*.enc' -mtime +30 -delete
Перед записью проверяется свободное место, а ночной rsync выносит копии за пределы машины:
FREE=$(df -Pm "$BASE" | awk 'NR==2{print $4}')
[ "$FREE" -lt 40960 ] && { echo "mysql-backup: low space $FREE MiB" >&2; exit 3; }
rsync -az --delete "$BASE/" Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. :/srv/mysql/
Ключ шифрования на второй хост не копируется: там хранятся только шифрованные архивы.
Проверка восстановления и уведомления о каждом запуске
Копия, которую ни разу не восстанавливали, - просто файл с оптимистичным именем. Репетиция логического дампа поднимает запасной экземпляр на свободном сокете и заливает в него расшифрованный поток:
openssl enc -d -aes-256-cbc -pbkdf2 -iter 60000 -pass file:"$KEY" -in "$BASE/all-$STAMP.sql.gz.enc" | zcat | mysql --protocol=socket -S /tmp/restore.sock
mysql --protocol=socket -S /tmp/restore.sock -e "select count(*) from shop.orders"
Хвост бинлогов после копии докатывается утилитой mysqlbinlog с позиции из дампа:
mysqlbinlog --start-position=157 binlog.000042 binlog.000043 | mysql --protocol=socket -S /tmp/restore.sock
Уведомления держатся на коде возврата и почте. Функция отправляет письмо прямо из скрипта:
notify() {
curl -sS --url "smtp://mail.example.com:587" --ssl-reqd --mail-from Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. --mail-rcpt Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. --user Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. :app-password' -T - <<EOF
From: Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
To: Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
Subject: mysql backup on $(hostname)
$1
EOF
}
trap 'notify "mysqldump FAILED, see $LOG"' ERR
notify "dump ok, $STAMP, size $SIZE MiB"
Для систем мониторинга годится короткий вебхук с теми же полями:
curl -fsS -X POST "https://hooks.example.com/mysql-backup" --data-urlencode "host=$(hostname)" --data-urlencode "stamp=$STAMP"
Расписание и обработку падений удобно держать в systemd: таймер с Persistent=true доигрывает пропущенные ночные запуски, а директива OnFailure в юните отправляет письмо при ненулевом коде возврата.
Перед вводом схемы в бой стоит пройти короткий чек-лист приёмки:
- сквозной прогон от снятия копии до живой базы на тестовой машине с замером времени;
- контрольная дешифровка копии недельной давности ключом из хранилища секретов;
- проверка свободного места и скорости на обеих сторонах переноса;
- письма об успехе и о сбое, полученные в обоих режимах;
- запись в журнале с датой, размером и контрольной суммой последней копии.
Схема готова не тогда, когда впервые отработали скрипты, а когда восстановления перестают быть событием: раз в месяц расшифрованная копия поднимает запасную базу за предсказуемые минуты, а письма с цифрами приходят сами. Шифрование при этом перестаёт быть ритуалом и становится обычным свойством копии - тем же, чем является имя файла или права доступа.