Автосервису на Авиамоторной два года каждую ночь уезжал архив почтового сервера на сетевое хранилище. Когда в июле 2021-го умер системный диск, я взял свежую копию и уверенно пошёл разворачивать. Через четыре часа стало понятно, что развернуть её нельзя вообще — и не по одной причине, а сразу по четырём.
Бэкап Zimbra есть, а восстановиться нельзя: четыре причины
Вопрос, на который у большинства нет ответа
Спросите у того, кто отвечает за ваш сервер, когда в последний раз почту восстанавливали. Не когда снимали копию — копию снимают каждую ночь, это видно в отчёте. Именно восстанавливали: брали архив, разворачивали, открывали ящик и читали письмо.
В девяти случаях из десяти ответа не будет. Будет что-то вроде «ну, бэкап-то идёт», «места хватает», «задание зелёное». Это разные утверждения. Первое означает, что файлы куда-то пишутся. Второе — что там есть место. Третье — что программа не сообщила об ошибке. Ни одно из них не отвечает на вопрос, сможете ли вы вернуть почту в понедельник, если сервер умрёт в воскресенье.
Неприятность в том, что разница между «копия есть» и «восстановиться можно» обнаруживается ровно один раз — в тот день, когда уже поздно. И обнаруживается не как «ой, не получилось», а как многочасовое выяснение, почему не получилось, пока пятнадцать человек сидят без работы, а директор стоит рядом.
Дальше — конкретная авария, четыре независимые причины отказа и проверка, которая ловит всё это за один вечер вместо одной катастрофы.
Июль 2021, Авиамоторная: умер системный диск
Автосервис на 15 рабочих мест, два поста кузовного ремонта и склад запчастей, всё в одном здании на Авиамоторной. Почта — Zimbra 8.8.15 Open Source Edition на CentOS 7, физический сервер 2015 года, ящики небольшие, store 96 ГБ.
В понедельник утром сервер не загрузился. Системный SSD ушёл в аппаратный отказ — не деградация, а мгновенная смерть, диск не определялся в контроллере вовсе. Данные почты лежали на отдельном массиве и физически уцелели, но операционная система, конфигурация и база находились на том же системном диске.
Меня вызвали в 10:40. К этому моменту сотрудники сидели без почты уже два с половиной часа, и это было ощутимо: приёмка машин у автосервиса живёт на переписке со страховыми компаниями, а согласования по деталям идут почтой с фотографиями.
Бэкап был. Настроил его в 2019 году предыдущий подрядчик: ночной архив каталога /opt/zimbra и заливка на сетевое хранилище, глубина хранения 14 копий. Задание отработало и в ночь на понедельник, архив весил 104 ГБ, дата свежая. Отчёт по почте приходил каждое утро со словом OK.
Я поставил новую машину, распаковал архив и запустил. Дальше был длинный день.
Ложная версия: битый архив
Первая мысль была самая простая — архив повредился при передаче или на хранилище. Сетевое хранилище стояло в той же серверной, дешёвое, без контроля целостности, и версия выглядела правдоподобной.
Проверил в лоб:
$ tar -tzf /mnt/nas/zimbra/2021-07-19/opt-zimbra.tar.gz > /tmp/list.txt; echo $?
0
$ wc -l /tmp/list.txt
1284617 /tmp/list.txt
$ md5sum /mnt/nas/zimbra/2021-07-19/opt-zimbra.tar.gz
a3c9... opt-zimbra.tar.gz
Архив читался целиком, распаковывался без единой ошибки, содержал больше миллиона файлов. Контрольная сумма совпадала с той, что лежала рядом с прошлой ночи. С архивом было всё в порядке.
Полтора часа на эту версию. Не совсем зря: я заодно получил полный список файлов, и именно по нему потом увидел третью причину. Но искал я не там, и в этом была моя ошибка — я проверял целостность носителя, хотя вопрос стоял о пригодности содержимого.
Разница принципиальная. Целый архив и пригодный архив — не одно и то же. Проверка контрольной суммы отвечает только на вопрос «доехали ли байты». На вопрос «можно ли из этого собрать работающий сервер» она не отвечает никак.
Причина первая: чужое имя хоста
Новый сервер я назвал по-новому — mail2.service-avia.ru, потому что старый ещё формально существовал в документации, а мне нужно было развести их в голове. Ошибка на ровном месте.
Распакованный каталог /opt/zimbra на машине с другим именем не оживает. Внутри конфигурации, в объектах каталога, в путях и в сертификатах прошито старое имя, и никакой первый запуск это не пересчитает. Получил я вот такое:
$ su - zimbra -c 'zmcontrol start'
Host mail2.service-avia.ru
Starting ldap...FAILED
Unable to determine enabled services from ldap.
Unable to determine enabled services. Cache is out of date or doesn't exist.
Механика простая: службы спрашивают у каталога, что им запускать на хосте с таким именем. Хоста с таким именем в каталоге нет — там записан старый. Ответа нет, стартовать нечего.
Правильный путь — один из двух, и оба надо выбирать заранее, а не в аварию:
- Поднять новый сервер под тем же именем, что и старый, развернуть архив, и только потом, если нужно, менять имя штатной процедурой.
- Поставить на чистую машину Zimbra ровно той же версии и на ту же операционную систему, дать ей то же имя, и уже поверх этой инсталляции восстанавливать данные.
Второй пункт содержит требование, о которое спотыкаются чаще всего: та же версия и та же ОС. Не «девятка вместо восьмёрки», не «Ubuntu вместо CentOS». Архив, снятый с 8.8.15 на CentOS 7, разворачивается на 8.8.15 на CentOS 7 — и точка. В июле 2021 года это ещё было выполнимо. Через три года, когда CentOS 7 умерла, тот же архив стал почти неразворачиваемым, и это отдельный повод пересмотреть схему, если она у вас такая же.
Я переименовал машину, переустановил всё заново и потерял на этом 2 часа 20 минут.
Причина вторая: копия снималась на живом сервере
Второй сюрприз вылез после того, как имя стало правильным и службы дошли до базы:
InnoDB: Database page corruption on disk or a failed file read of page ...
InnoDB: Error: page ... log sequence number ... is in the future!
[ERROR] Plugin 'InnoDB' init function returned error.
Полез разбираться, как именно снимался архив, и нашёл скрипт предыдущего подрядчика. В нём не было ни одной команды остановки служб. Просто tar по живому каталогу, каждую ночь, два года подряд.
Что при этом происходит. Архиватор идёт по каталогу последовательно, файл за файлом, и на 104 гигабайтах это занимает больше часа. За этот час сервер продолжает работать: принимает почту, пишет в базу, создаёт новые файлы писем, удаляет старые. В результате база попадает в архив в состоянии, зафиксированном в 02:41, а файлы писем — в состоянии между 02:15 и 03:50. Согласованности между ними нет и быть не может.
Для базы это означает недописанные транзакции — то, что вы и видите в сообщении выше. Для хранилища — ссылки на файлы, которых нет, и файлы, на которые никто не ссылается.
Поднять базу удалось только через принудительное восстановление с постепенным повышением уровня, дампом и заливкой в чистую базу. Уровень 3 не помог, уровень 4 дал дамп с потерей части последних записей.
# /opt/zimbra/conf/my.cnf, временно
innodb_force_recovery = 4
# всё из-под zimbra: своя сборка MariaDB, свой сокет, свой пароль
$ mysqldump --all-databases \
-S /opt/zimbra/data/tmp/mysql/mysql.sock \
-u root -p$(zmlocalconfig -m nokey -s mysql_root_password) > /tmp/dump.sql
$ /opt/zimbra/libexec/zmmyinit --sql_root_pw '...'
$ mysql -S /opt/zimbra/data/tmp/mysql/mysql.sock -u root -p'...' < /tmp/dump.sql
Это сработало, но с потерями. Дамп с четвёртым уровнем восстановления обрубил хвост записей, накопившихся перед фиксацией базы в 02:41, — примерно с девяти вечера воскресенья. Плюс промежуток от копии до отказа диска в начале девятого утра. Вместе это и дало те одиннадцать часов переписки, которые потеряли безвозвратно. Файлы этих писем физически лежали на уцелевшем массиве, но без записи в базе они бесполезны: сервер не знает, чьё это письмо, в какой оно папке и от кого. Вернуть письмо в ящик можно только вместе с метаданными.
Вывод, который я с тех пор говорю каждому клиенту на первой же встрече: копия почтового сервера, снятая без остановки служб, — это не копия, а лотерея. Иногда она разворачивается. Проверить, повезло ли вам, можно только развернув.
Причина третья: каталога учётных записей в архиве не было
Вот здесь пригодился список файлов, который я получил, пока проверял ложную версию. Посмотрел, что вообще есть в архиве. Пути внутри архива относительные, вида opt/zimbra/store/..., поэтому имя интересующего каталога — третье поле, а не второе; на этом легко ошибиться и получить одно слово zimbra на миллион строк:
$ awk -F/ '{print $3}' /tmp/list.txt | sort | uniq -c | sort -rn | head
1198334 store
72140 db
9822 conf
3901 ssl
12 log
$ grep -c '^opt/zimbra/data/ldap/' /tmp/list.txt
0
Ноль. Каталога /opt/zimbra/data/ldap в архиве не было вовсе. Нашлась и причина — в том же скрипте предыдущего подрядчика стояло исключение по маске --exclude='*/data/*'. Судя по всему, автор хотел выкинуть временные файлы, а выкинул вместе с ними всю базу учётных записей. Два года никто этого не замечал, потому что архив исправно создавался и весил правдоподобно.
Что это означало практически: пятнадцати учётных записей, их паролей, алиасов, списков рассылки и настроек домена в бэкапе не существовало. Восстанавливать письма было некуда.
Каталог выгружается отдельной командой, весит копейки и читается человеком. Команд, впрочем, две: данные и конфигурация каталога выгружаются по отдельности, и вторую забывают чаще, чем первую:
$ su - zimbra -c '/opt/zimbra/libexec/zmslapcat /mnt/nas/zimbra/ldap/'
$ su - zimbra -c '/opt/zimbra/libexec/zmslapcat -c /mnt/nas/zimbra/ldap/'
$ ls -lh /mnt/nas/zimbra/ldap/
-rw-r----- 1 zimbra zimbra 8,4M ldap.bak
-rw-r----- 1 zimbra zimbra 31K ldap-config.bak
Восемь мегабайт против ста четырёх гигабайт. Именно эти восемь мегабайт определяют, будет ли куда возвращать остальные сто четыре гигабайта.
Учётки в итоге пересоздавали руками. Список собрали из двух источников: из каталогов внутри store, где каждому ящику соответствует свой номер, и из адресной книги на компьютере бухгалтера. Пароли выдали новые, всем пятнадцати, с обходом по кабинетам.
Причина четвёртая: права на файлы и зелёный отчёт
Четвёртая причина мельче трёх предыдущих, но добавила часа три.
Распакованный архив принёс с собой числовые идентификаторы владельца со старого сервера. На новой машине пакет создал пользователя zimbra с другим номером — потому что порядок установки пакетов был иной. В итоге сотни тысяч файлов принадлежали никому:
$ ls -ln /opt/zimbra/store/0/2/msg/0/ | head -3
-rw-r----- 1 1001 1001 4821 июл 19 02:33 271-1234.msg
$ id zimbra
uid=996(zimbra) gid=994(zimbra)
Лечится штатно и быстро, но знать про это надо заранее:
# от root, после распаковки, до первого старта
/opt/zimbra/libexec/zmfixperms
И последнее, о чём хочу рассказать отдельно, потому что это самая тихая из всех бед. Отчёт о бэкапе приходил каждое утро со словом OK. Я поднял почтовый ящик, куда он падал, и просмотрел письма за полгода. В шестнадцати из них внизу, после сводки, была строка про один и тот же ящик, который не выгрузился. Один из пятнадцати. Сводка при этом считалась успешной, потому что скрипт смотрел на код возврата архиватора, а не на содержимое журнала.
Похожая история бывает и со штатными средствами: полный бэкап стабильно падает на одном конкретном аккаунте с ошибкой вида NoSuchFieldError: revision, остальные проходят нормально, и общий результат выглядит почти зелёным. «Почти» здесь — ключевое слово. Один ящик из сорока — это, как правило, ящик директора или главного бухгалтера, потому что именно они самые большие и самые старые.
Смотрите на журнал, а не на итоговую строку. И заведите правило: любое сообщение об ошибке внутри успешного задания разбирается в тот же день.
Проверьте у себя за две минуты
Эта проверка не отвечает на вопрос «есть ли бэкап» — она отвечает на вопрос «развернётся ли он». Выполняется на боевом сервере и на хранилище, ничего не меняет.
# 1. Есть ли в архиве каталог учётных записей
tar -tzf /mnt/nas/zimbra/latest/opt-zimbra.tar.gz \
| grep -c '^opt/zimbra/data/ldap/'
# 2. Останавливаются ли службы при снятии копии
grep -nE 'zmcontrol|systemctl|snapshot|lvcreate' /путь/к/скрипту/бэкапа
# 3. Под каким номером живёт пользователь zimbra
id zimbra
# 4. Что за версия и ОС — их придётся повторить при восстановлении
su - zimbra -c 'zmcontrol -v'; cat /etc/redhat-release 2>/dev/null || lsb_release -d
# 5. Ошибки внутри «успешных» заданий
grep -iE 'error|failed|warn' /var/log/zimbra-backup.log | tail -20
| Что увидели | Что это значит | Насколько срочно |
|---|---|---|
Первая команда вернула 0 | Учётных записей в копии нет. Письма будет некуда возвращать | Сегодня |
| В скрипте нет остановки служб и нет снимка тома | Копия несогласованна: база и письма из разных моментов | На этой неделе |
Номер пользователя zimbra нигде не записан | При восстановлении права на файлы поедут | Записать сегодня, это одна строка |
| Версия ОС снята с поддержки | Установить такую же для восстановления будет негде | Пересматривать схему целиком |
| В журнале есть ошибки, а отчёт «успешно» | Часть ящиков не копируется месяцами | Разобрать в тот же день |
| Никто ни разу не разворачивал копию | У вас не бэкап, а предположение о бэкапе | Назначить дату учения |
Первая строка — самая частая находка. Из последних одиннадцати клиентов, у которых я это проверял, каталог учётных записей отсутствовал в копии у четверых.
Чем кончилось, сколько стоило и что делать вам
Итог. Полное восстановление заняло 31 час чистой работы за четверо суток: чистая установка 8.8.15 на CentOS 7 под старым именем, исправление прав, подъём базы принудительным восстановлением, ручное создание 15 учёток, заливка писем из уцелевшего хранилища.
Вернули 94% писем по объёму. Потеряли безвозвратно: письма за последние 11 часов, структуру папок у восьми ящиков из пятнадцати, пометки «прочитано», серверные фильтры, общие календари двух приёмщиков.
Цифры для владельца:
| Статья | Значение |
|---|---|
| Полный простой почты | 1 день 6 часов |
| Работа в ограниченном режиме | ещё 3 дня |
| Простой персонала | 15 человек × 10 часов ≈ 150 человеко-часов |
| Оценка упущенной работы | около 82 000 ₽ |
| Восстановительные работы | 31 час |
| Два срыва сроков со страховой | оценке не поддалось |
Профилактика стоила бы вечер на проверку выше и четыре часа на переделку схемы. Один к двадцати.
Что поставили после аварии: холодная копия с простоем в пять минут, ежедневная выгрузка каталога учёток, записанные рядом с архивом версия, ОС и номер пользователя zimbra, и раз в квартал — разворачивание одного ящика на тестовой машине.
Последний пункт — единственный, который что-то доказывает. Проверить бэкап можно только восстановлением. Остальное — косвенные признаки.
И контр-совет к популярному «главное, чтобы копия лежала в другом месте». Разнесение важно, спорить не буду. Но копия, которая лежит в трёх городах и не разворачивается ни в одном, стоит столько же, сколько её отсутствие, только дороже в хранении. Сначала восстановимость, потом география.
Если хотите узнать про свой сервер за один день, а не за одну аварию: выполните пять команд из проверки выше и пришлите мне вывод — наличие каталога учёток в архиве, кусок скрипта, номер пользователя, версию с ОС и хвост журнала. Отвечу, что у вас реально восстановится, что нет, и сколько работы, чтобы закрыть найденное. Если по этим пяти пунктам всё окажется в порядке — предложу назначить дату учения, потому что это следующий честный шаг.
Частые вопросы
Почему архив /opt/zimbra нельзя развернуть на сервере с другим именем?
Потому что имя хоста прошито внутри самого архива: в конфигурации служб, в объектах каталога учётных записей, в сертификатах. При старте службы спрашивают у каталога, что запускать на хосте с текущим именем, не находят такого хоста и останавливаются с сообщением, что не могут определить включённые службы. Выхода два, и выбирать надо заранее: либо поднять новый сервер под тем же именем, что и старый, либо поставить на чистую машину Zimbra ровно той же версии и на ту же операционную систему, дать ей старое имя и восстанавливать данные поверх. Менять имя штатной процедурой можно уже потом, когда всё поднялось.
Насколько важно совпадение UID пользователя zimbra?
Достаточно важно, чтобы записать его в файл рядом с архивом прямо сегодня — это одна строка вывода команды id zimbra. В распакованном архиве владелец файлов хранится числом, а на новой машине пакет может создать пользователя с другим номером: зависит от того, в каком порядке ставились пакеты. В результате сотни тысяч файлов почты оказываются принадлежащими несуществующему пользователю, и сервер их не читает. Лечится штатной утилитой /opt/zimbra/libexec/zmfixperms, запускать её надо от root после распаковки и до первого старта. У меня на этом ушло около трёх часов только потому, что я не подумал про права сразу.
Можно ли снимать копию без остановки служб, если сервер маленький?
Размер сервера тут ни при чём — важна длительность копирования. Архиватор обходит каталог за десятки минут, и всё это время сервер продолжает писать в базу и создавать файлы писем. База попадает в копию в одном моменте времени, письма — в диапазоне, и согласованности между ними нет. Иногда такая копия разворачивается нормально, иногда база требует принудительного восстановления с потерей последних записей — именно это и случилось у автосервиса. Правильный минимум: остановить службы на время создания снимка тома, а копировать уже со снимка. Простой при таком порядке — около пяти минут в сутки.
Отчёт о бэкапе приходит каждое утро со словом «успешно». Разве этого мало?
Мало, потому что «успешно» обычно означает лишь то, что архиватор вернул нулевой код. У клиента отчёт приходил два года, и в шестнадцати письмах за полгода внутри сводки была строка про ящик, который не выгружался, — но итоговая строка оставалась зелёной. То же бывает и со штатными средствами: задание стабильно падает на одном аккаунте с ошибкой вроде NoSuchFieldError: revision, остальные проходят, отчёт выглядит почти нормальным. Проблема в слове «почти»: обычно это самый большой и старый ящик, то есть директора или бухгалтера. Читайте журнал, а не итог, и разбирайте любую ошибку внутри успешного задания в тот же день.
Как проверить восстановимость, не устраивая полное учение?
Минимальная честная проверка занимает вечер. Берёте тестовую машину, ставите Zimbra той же версии и на ту же ОС под тем же именем, распаковываете туда архив, исправляете права и поднимаете службы. Дальше открываете один ящик и читаете письмо годичной давности. Если дошли до этого — восстановимость есть. Если нет, вы узнали это в спокойной обстановке, а не в аварию. Отдельно проверьте наличие в архиве каталога учётных записей одной командой tar -tzf ... | grep -c 'data/ldap/': ноль на выходе означает, что письма восстанавливать будет некуда, и это самая частая находка из всех.
Оставить комментарий