У одного заказчика бэкап базы делает мой старый скрипт на Ruby, крон запускает его раз в сутки на VPS. На прошлой неделе заказчик попросил развернуть копию базы на тестовом сервере, и оказалось, что последние 12 файлов весят по 20 байт. При этом скрипт ни разу не сообщил об ошибке. Вот он:
require "open3"
dir = "/var/backups/db"
file = File.join(dir, "#{Time.now.strftime('%Y%m%d')}.sql.gz")
cmd = "mysqldump --single-transaction #{ENV.fetch('DB_NAME')} | gzip > #{file}"
_out, err, status = Open3.capture3({ "MYSQL_PWD" => ENV.fetch("DB_PASS") }, cmd)
abort "бэкап упал: #{err}" unless status.success?
Dir[File.join(dir, "*.sql.gz")].sort[0...-14].each { |f| File.delete(f) }
Если вывести err, там ошибка доступа: у пользователя базы сменили пароль, а в переменных окружения остался старый. Но статус почему-то успешный, и abort не срабатывал.
Статус пайпа это статус последней команды, то есть gzip. А gzip честно сжал пустоту и вышел с нулём, отсюда и 20 байт. В sh, через который Open3 запускает строку, pipefail обычно нет. Лучше обойтись без шелла вообще, через Open3.pipeline, там у каждой команды свой статус:
statuses = Open3.pipeline(
["mysqldump", "--defaults-extra-file=/etc/backup/my.cnf",
"--single-transaction", ENV.fetch("DB_NAME")],
["gzip"],
out: file
)
abort "бэкап упал" unless statuses.all?(&:success?)
Пароль, кстати, лучше держать в этом my.cnf с правами 600, а не в переменных окружения. И проверяйте размер файла: бэкап меньше килобайта уже повод кого-нибудь разбудить.
и восстановление раз в месяц проверяйте, хоть на ноутбуке. бэкап, из которого ни разу не восстанавливались, это надежда, а не бэкап
Переписал на Open3.pipeline, пароль убрал в my.cnf с правами 600, как советовал armen_pet. Добавил проверку размера и письмо себе, если файл меньше 10 КБ. Восстановление проверил вечером дома в Гатчине: дамп на 1,7 ГБ поднялся на ноутбуке за 9 минут. Копию заказчику развернул, повезло, что сама база всё это время была цела.