Делаем дамп БД и отсылаем себе на почту

Введение

Здравствуйте.

Наша задача – периодически делать дамп указанной базы данных (БД) и отсылать нам на почту, чтобы в случае чего можно было восстановиться в любой момент времени.

Для этого достаточно штатных средств Linux‘а.

DB.SH

Итак, составляем простой шелл скрипт – текстовый файл, назовём его db.sh, и делаем его исполняемым:

$ vim /var/adm/cron/db.sh
$ chmod +x /var/adm/cron/db.sh

Учтём, что в папке /var/adm/cron находятся крон-задачи, т.е. периодически выполняемые на нашем сервере, этот каталог не доступен из Интернета.

Вот полное содержимое db.sh:

#!/bin/sh
cd /var/adm/cron/
echo "**" `date +'%Y-%m-%d %H:%I:%S'` "I am db.sh:" `id` >> log.txt
mysqldump -h localhost -u dbuser -pmypassword mydbase > db_dump.sql
bzip2 -f db_dump.sql
date +'%Y-%m-%d %H:%I:%S' | mutt -s 'mydbase backup' -a db_dump.sql.bz2 -- mymail@yandex.ru

Что мы тут делаем:
1. Переходим в каталог, где находится наш скрипт: /var/adm/cron
2. В лог (файл log.txt) выведем текстовое сообщение с текущей датой/времени в формате YYYY-MM-DD HH:mm:ss (год-месяц-число час-минуты-секунды), произвольным сообщением (I’m db.sh) и ид юзера, под которым запущен скрипт. Делать это конечно же не обязательно.
3. Делаем бэкап базы mydbase из-под пользователя dbuser с паролем mypassword (этот пользователь конечно же должен иметь доступ к указанной БД) и сохраняем его в файле db_dump.sql.
4. Сжимаем полученный бэкап архиватором BZIP2 (он сжимает несколько лучше, чем штатный GZIP). Флаг -f (force, форсировать) говорит о том, чтобы перезаписывать старый архив в случае его наличия.
5. Отправляем сообщение на адрес mymail@yandex.ru в виде текущей даты/времени со вложением только что заархивированного дампа базы данных. Замечу, что сам архивированный дамп БД мы оставляем в текущем каталоге, на всякий случай.

MUTT

Самое интересное – 5-ый шаг, мы используем консольный почтовик mutt, хотя это довольно популярная и древняя утилита, в базовые дистрибутивы она обычно по-умолчанию не входит, и её нужно установить командой своего менеджера пакетов, например, для Debian/Ubuntu:

# apt-get install mutt

Также обратите внимание на версию mutt‘а, мы используем версию 1.5.21:

# mutt -v
Mutt 1.5.21 (2010-09-15)
...

Дело в том, что для других версий команда отправки письма с сообщением может отличаться, например для Mutt 1.4.xx версии я использовал такую команду:

date | /usr/local/mutt-1.4.2.3/mutt -s 'mydbase backup' -a db_dump.sql.bz2 mymail@yandex.ru

Обратите внимание на разницу передачи пайпа от команды date к mutt‘у. Как говорится, найдите 10 отличий.

Если вы не являетесь админом на сервере, как вариант собрать mutt из исходников, и указывать абсолютный путь к нему. Я так делал на SpaceWeb‘е. Либо загуглить статически линкованный mutt. Я сейчас в первой 20-ке не смог найти статического mutt‘а по запросу `mutt static build download`. В этом случае можно собрать статический mutt у себя на локальном компьютере, инструкции как это сделать найти можно, и перенести его на сервер.

Также mutt можно заменить на утилиту mail, входящую в пакет mailx (или один из его производных). Если на сервере доступна команда mail, отослать наш дамп БД как вложение можно командой:

$ mail -s "mydbase backup" -a "db_dump.sql.bz2" "mymail@yandex.ru" <<< $(date +"%Y-%m-%d %H:%I:%S")

Параметры здесь практические такие же, что и у mutt: -s – тема, -a – аттачмент.

CRON

Теперь добавим вызов скрипта в cron. Мне достаточно делать дамп БД раз в сутки, настрою вызов скрипта каждый день, в 5:13 утра:

$ crontab -e
13 	5 	* 	* 	* 	/var/adm/cron/db.sh

Либо как вариант закинуть db.sh в папку /etc/cron.daily, если есть к ней доступ. Рабочей папкой для скрипта всё равно останется /var/adm/cron, всю работу он будет делать там.

SPLIT

Но в один прекрасный день я не получил на почту утром дамп нашей БД. Поискав логах, я нашёл примерно такую ошибку:

postdrop: warning: uid=0: File too large
sendmail: fatal: root(0): message file too big
Сообщение отправить не удалось, процесс-потомок вернул 75 (Deferred.).
Сообщение отправить не удалось.

Ага, этого рано-поздно следовало ожидать. Дамп нашей базы оказался слишком велик для передачи по электронной почте. Тут у нас есть пара вариантов (как хорошо, что жизнь компьютерная почти всегда предоставляет нам несколько решений одной и той же проблемы, что касается программирования или системного администрирования, вот бы в реальной жизни всё было бы так же).
1. Увеличить макс.размер отправляемых писем. Если Вы админ, это можно легко сделать. mutt, впрочем как и mail(x), использует установленный в системе mail-transport-agent (MTA), из коих наиболее распространёнными на сегодня пожалуй являются Postfix и Exim4. Пример с Postfix‘ом как увеличить максимальный размер отсылаемых писем:

# postconf | grep message_size_limit
message_size_limit = 10240000
# postconf -e "message_size_limit = 51200000"
# postconf | grep message_size_limit
message_size_limit = 51200000

Сперва мы узнали макс.размер письма, который оказался равен 10 мегабайт, затем увеличили его до 50 мегабайт. Да, у Postfix‘а по-умолчанию размер письма (message) включая возможные аттачи максимум 10 мегабайт. Размер последнего дампа БД, который удалось отправить, был равен 8.6 мегабайт, файлы большего размера уже не отправлялись, что довольно странно, так как до 10 мегабайт было ещё не так скоро. Хотя на самом деле аттачи отправляются в uuencoded (или base64-encoded) формате, и если мы закодируем так наш последний полученный по почте дамп, то получим размер 11,970,592 байта в chunk_split(base64_encode($attach)) или размер 12,052,444 байт при использовании convert_uudecode($attach) – были использованы стандартные PHP функции. В обоих случаях размер превышает заданный по умолчанию в Postfix‘е 10,240,000 байт.

ОК, если мы увеличим макс.разрешенный размер письма до 50 мегабайт, то мы получим какое-то время, возможно в несколько лет (если наша БД не очень быстро пополняется), прежде чем мы превысим макс.допустимый объём.

Да, тут несколько возможных проблем – рано или поздно аттач (в смысле письмо с ним) опять превысит макс.допустимый размер и перестанет отправляться, второе – принимающий сервер может со своей стороны ограничивать размер принимаемых писем. Третье – вы можете не быть админом на сервере и повысить максимальный размер письма. В скобках замечу, что, указав message_size_limit = 0 вы снимете ограничение на максимальный размер письма.

Во всех этих случаях нас выручит split, старый добрый split. Давайте будем пилить наш аттач например по 5 мегабайт и кусками отправлять нам на почту. Делается это примерно так:

split -b 5M db_dump.sql.bz2 dump5M_
 
for i in dump5M_*
do
date +'%Y-%m-%d %H:%I:%S' | mutt -s 'mydbase backup' -a $i -- mymail@yandex.ru
done
 
# почистим за собой
rm dump5M_*

Таким образом новый готовый скрипт db.sh будет выглядеть так:

#!/bin/sh
cd /var/adm/cron/
echo "**" `date +'%Y-%m-%d %H:%I:%S'` "I am db.sh:" `id` >> log.txt
mysqldump -h localhost -u dbuser -pmypassword mydbase > db_dump.sql
bzip2 -f db_dump.sql
split -b 5M db_dump.sql.bz2 dump5M_
 
for i in dump5M_*
do
date +'%Y-%m-%d %H:%I:%S' | mutt -s 'mydbase backup' -a $i -- mymail@yandex.ru
done
 
# почистим за собой
rm dump5M_*

При задании в split имени dump5M_ мы получаем файлы вида dump5M_aa, dump5M_ab, dump5M_ac и т.п. Чтобы склеить при случае полученные куски, можно обойтись штатными средствами системы:

В Windows (кавычки в именах файлов обязательны):

C:\Users\Andrey\Desktop> copy /b "dump5M_aa" + "dump5M_ab" db.sql.bz2

В Linux:

$ cat dump5M_aa dump5M_ab > db.sql.bz2

или:

$ cat dump5M_a* > db.sql.bz2

И в случае применения split’а есть недостаток, если, например, размер дампа 50 мегабайт, то мы будем каждый день отсылать/получать по 10 писем, не знаю, хорошо это или плохо. Как вариант – отсылать дамп БД на резервный сервер, к примеру с помощью команды scp, но это пожалуй уже другая история.

На сим всё, раскланиваюсь, т.к. наша задача выполнена на 100% и мы можем теперь спать спокойно, не боясь краха нашего сервера и утери нашей драгоценной базы данных с кредитной историей наших клиентов.

Спокойной ночи, сладких снов, если уже ночь, чашки кофе и стакана апельсинового сока, если утро, бокала пива с жаренными креветками, если обед, бутылки виски или Шардоне, если вечер.