dkayumov.ru
Все статьи журнала
RCA · инфраструктура9 мин чтения

RCA. 12 минут даунтайма почты после apt purge dovecot-sieve и 3 шага, которые спасли бы заранее

Команда apt purge dovecot-sieve казалась безопасной. Через 14 секунд почта не работала. Через 12 минут я её вернул из бэкапа в 03:00 ночи. RCA, который я устроил себе сам.

RCALinuxDovecotинфраструктура
На этой странице

Что случилось

В апреле я делал техдолг на собственном VPS, чистил неиспользуемые пакеты Dovecot. Команда apt purge dovecot-sieve dovecot-managesieved казалась безопасной. Два маленьких пакета фильтрации, которыми я не пользуюсь. Через несколько секунд Dovecot перестал слушать порты. Через 12 минут я вернул почту из ночного бэкапа.

Это RCA, который я устроил себе сам. Пост для всех, кто думает, что apt purge без --dry-run на проде сойдёт с рук. И для CIO компании среднего размера, у которой схожий боевой стек на Debian.

Контекст

VPS у Timeweb, Debian 13, multi-tenant. На нём крутится почта и веб моих личных и клиентских проектов. Я веду этот сервер сам, потому что не хочу терять hands-on. Платформа для меня сразу и обучение, и боевая инфраструктура.

Почтовый стек на момент инцидента: Postfix 3.10, Dovecot 2.4, Rspamd, Roundcube. Бэкап ежедневный, в 03:00 ночи, через собственный full_backup.sh по таймеру systemd. Бэкапятся /etc, /var/vmail и MariaDB-дамп.

Я зашёл на сервер в 02:30, чтобы закрыть пункт техдолга. Убрать неиспользуемые пакеты Dovecot. Sieve-фильтры на этом сервере я не использовал, вся фильтрация спама на стороне Rspamd. Логика была простая. Нет пакетов, значит нет лишней поверхности атаки и нет лишних пакетов в апдейтах.

Команда, которая всё сломала

bash
apt purge dovecot-sieve dovecot-managesieved

Здесь зарыта вся история. Я набрал команду на автопилоте, как обычно чищу пакеты. По логике это два маленьких пакета sieve, которые мне не нужны. Снести их и забыть. Я был уверен, что dovecot-sieve это изолированный аддон поверх Dovecot, и его удаление ничего не заденет. Зависимость я держал в голове задом наперёд.

На Dovecot 2.4 в Debian 13 цепочка идёт не от аддона к ядру, а наоборот. dovecot-core объявляет Depends: dovecot-sieve. Значит, sieve для core это обязательная зависимость, а не опция. Проверить легко:

bash
apt-cache depends dovecot-core | grep -i sieve # Depends: dovecot-sieve

Из-за этого нельзя выкинуть sieve, не выкинув core, который от sieve зависит. APT увидел запрос на удаление dovecot-sieve, поднял обратную цепочку и понял, что dovecot-core без него существовать не может. А раз уходит core, за ним каскадом уходят dovecot-imapd, dovecot-lmtpd и dovecot-mysql, потому что они держатся на core. Голый purge без всякого autoremove снёс шесть пакетов вместо двух названных.

Дальше сработал сам режим purge. Он сносит бинарники пакета вместе с его conffiles. У dovecot-core conffile это /etc/dovecot/dovecot.conf, главный конфиг почты. Раз core попал в транзакцию через Depends-цепочку, purge удалил его конфиг с диска целиком. Не сбросил на дефолт, а стёр файл.

Подтверждение, которое APT мне показал в терминале, я прокликал бегло. Глаз поймал короткий список sieve. А на самом деле APT выкатил «The following packages will be REMOVED» длиной в шесть строк, и в этих строках стояли dovecot-core, dovecot-imapd, dovecot-lmtpd, dovecot-mysql. Нажал Y.

Полная хронология

ВремяСобытиеИсточник
02:48:50Запустил apt purge -y dovecot-sieve dovecot-managesieved. Нажал Y без вдумчивого чтения списка.apt history.log
02:49:00APT удалил шесть пакетов: dovecot-core, dovecot-imapd, dovecot-lmtpd, dovecot-mysql, dovecot-sieve, dovecot-managesieved. /etc/dovecot/dovecot.conf стёрт с диска (purge снёс conffile core). Dovecot перестал слушать 143/993/110/995.apt history.log + journalctl
02:49:10Прочитал /var/log/apt/history.log. Увидел в строке Purge все шесть пакетов, не два. Понял масштаб. Решил восстанавливаться из daily-backup от 03:00 предыдущей ночи. RPO 24 часа, для личного проекта допустимо.apt history.log
02:49:30apt install dovecot-core dovecot-imapd dovecot-lmtpd. Пакеты вернулись (dovecot-sieve подтянулся как automatic, он же обязательная зависимость core). Лёг дефолтный dovecot.conf.apt history.log
02:52Распаковал /root/backups/site_2026-04-23_0300.tar.gz в /tmp/restore/. Заметил, что dovecot-mysql не вернулся. Без него Dovecot не достучится до виртуальных ящиков в MariaDB.tar log
02:53:38apt install dovecot-mysql. Отдельным шагом вернул драйвер userdb/passdb для MariaDB.apt history.log
02:58Скопировал /etc/dovecot/ из бэкапа поверх дефолтного. Вернул свой рабочий конфиг.tar log
02:59doveconf -n показал, что конфиг валиден, без синтаксических ошибок. systemctl start dovecot.journalctl
03:00В журналах вижу, что dovecot слушает 143/993, MySQL-userdb отвечает. Открыл Mail.app, почта пришла.Mail.app
03:00:50Полная функциональность восстановлена. Время восстановления составило 12 минут от точки отказа.

Восстановление по шагам

bash
# Шаг 1. Распаковка бэкапа в безопасное место mkdir -p /tmp/restore cd /tmp/restore tar -xzf /root/backups/site_2026-04-23_0300.tar.gz # Шаг 2. Переустановка пакетов (поставит дефолтный dovecot.conf) apt install -y dovecot-core dovecot-imapd dovecot-lmtpd # Драйвер MySQL отдельным пакетом — без него userdb/passdb в MariaDB не работают apt install -y dovecot-mysql # Шаг 3. Свой конфиг из бэкапа — ПОВЕРХ дефолтного cp -r /tmp/restore/etc/dovecot/ /etc/ # Шаг 4. Проверка конфига перед стартом doveconf -n >/dev/null || { echo "Config broken, abort"; exit 1; } # Шаг 5. Старт сервиса systemctl start dovecot systemctl status dovecot # Шаг 6. Smoke test — IMAP-логин openssl s_client -connect localhost:993 -quiet <<< "A001 LOGIN user pass"

Шаг 2 пришлось делать в два захода. Первым apt install dovecot-core dovecot-imapd dovecot-lmtpd вернул бинарники и дефолтный dovecot.conf, но не вернул dovecot-mysql. А он на этом сервере критичен. Логины и пароли виртуальных ящиков лежат в MariaDB, и без MySQL-драйвера Dovecot не прочитает userdb и passdb. Запусти я сервис после первого install, порты бы открылись, но ни один реальный ящик не залогинился бы. Драйвер я доставил отдельным apt install dovecot-mysql.

Порядок здесь не косметика. Сначала apt install, он ставит бинарники и дефолтный конфиг. Только потом конфиг из бэкапа кладётся поверх дефолтного. Если сделать наоборот, положить конфиг до установки пакетов, dpkg упрётся в уже лежащий файл и либо задаст conffile-вопрос, либо перетрёт восстановленный конфиг дефолтным.

Шаг 4 критичный. Если бы я просто запустил dovecot без doveconf -n, мог получить вторичный фейл. Конфиг из бэкапа лёг поверх свежепереустановленных пакетов, а минорная версия из репозитория могла оказаться новее той, при которой конфиг писался. На смене минора синтаксис местами уезжает. doveconf -n разбирает весь конфиг и падает с ненулевым кодом, если в нём есть незнакомый или сломанный параметр. К счастью, расхождений не было.

RCA. Почему так получилось

Корневая причина. Я выполнил apt purge на проде без предварительного --dry-run и без снапшота conffiles.

Фактор 1. Усталость. 02:48 ночи. Концентрация ниже дневной. APT-подтверждение я прокликал бегло.

Фактор 2. Зависимость задом наперёд. Я считал, что dovecot-sieve это изолированный аддон поверх ядра, и его снос ничего не заденет. На деле на Dovecot 2.4 в Debian 13 всё ровно наоборот. dovecot-core объявляет Depends: dovecot-sieve, то есть это ядро держится на sieve, а не sieve на ядре. Удалить sieve, не удалив core, нельзя по определению зависимости. А раз уходит core, каскадом уходят imapd, lmtpd и mysql, которые висят на нём. Тут не нужен ни autoremove, ни какой-то лишний флаг. Прямой purge двух пакетов утащил шесть просто из-за Depends-цепочки. Команда сделала ровно то, что я ей сказал. Я неверно представлял, что именно говорю.

Отдельно отмечу нюанс, чтобы не упрощать. Если на хосте включён глобальный APT::Get::AutomaticRemove, даже точечный purge может добрать сирот сверх Depends-цепочки. На моём сервере этот ключ пуст (проверил через apt-config dump | grep AutomaticRemove), так что сносить почту автоудалению было не нужно. Хватило прямой обязательной зависимости core → sieve.

Фактор 3. Нет процесса. На своём VPS я работал без чек-листа на деструктивные операции. На корпоративных серверах в моей практике такой чек-лист есть и обязателен. Дома почему-то нет.

Если бы я делал ту же операцию на проде клиента, чек-лист бы сработал и инцидента не было бы. То, что я наступил на эти грабли на своём VPS, а не на клиентском, в каком-то смысле повезло. Урок обошёлся в 12 минут моей почты, а не в доверие клиента.

3 шага, которые не дали бы этому случиться

Шаг 1. apt-get purge --dry-run

Запускать любую apt purge/remove в режиме сухого прогона до боевого выполнения.

bash
apt-get purge --dry-run dovecot-sieve dovecot-managesieved

В выводе ищем секцию The following packages will be REMOVED:. Если там перечислены только целевые пакеты, идём дальше. Если APT по зависимостям тянет что-то ещё (особенно -core, -common, любые системные dependencies), стоп, отмена. Ищем другой способ. Отключаем функционал через config или через systemctl disable, оставив пакет установленным.

Мой инцидент целиком в этом одном шаге. Тот же apt purge dovecot-sieve dovecot-managesieved в режиме --dry-run вывел бы шесть строк в REMOVED, и dovecot-core в их числе. Тридцать секунд чтения, и я бы отменил операцию, а не нажал Y вслепую. Дры-ран запускаем с тем же набором пакетов и флагов, что и боевую команду. Он показывает итоговый план транзакции ровно так, как APT его исполнит.

Шаг 2. Снапшот conffiles перед операцией

Даже если --dry-run показал чистый список, делаем tar-снапшот всех conffiles затронутых пакетов:

bash
# Какие conffiles будут потеряны при purge: dpkg-query -W -f='${Conffiles}\n' dovecot-* | awk '{print $1}' # Tar их заранее: tar -czf /root/backups/dovecot-conffiles-$(date +%F-%H%M).tar.gz \ $(dpkg-query -W -f='${Conffiles}\n' dovecot-* | awk '{print $1}')

Снапшот делается за 2–3 секунды и весит обычно меньше 100 КБ. Это страховка на случай, если --dry-run чего-то не показал.

Шаг 3. Постфактум smoke test

После любой деструктивной операции обязателен smoke test критичных функций. Для почты это:

bash
# Слушает ли Dovecot ss -tlnp | grep -E ':(143|993|110|995)\b' # Принимает ли логин (через SSL) openssl s_client -connect localhost:993 -quiet <<< 'A1 LOGIN test bad' # Postfix → Dovecot LMTP-доставка echo "Subject: test" | sendmail me@example.ru tail -f /var/log/mail.log # ждём delivery line

На smoke test уходит 30–60 секунд. На восстановление из бэкапа уйдёт 12 минут плюс ночное пробуждение.

Что записал в свои правила

На моём ноутбуке для каждого проекта есть файл правил CLAUDE.md. Я веду его около двух лет для работы с AI-ассистентом (Claude Code). Туда вношу всё, что узнаю на собственной шкуре. После этого инцидента в файл добавился такой блок:

Правило помогает не мне одному. Я отдаю этот же файл командам клиентов, у которых внедряю AI-ассистент в инженерные процессы. Ассистент с таким правилом не запустит apt purge на проде без --dry-run. А если конкретный инженер про протокол забыл, ассистент напомнит за него.

Для кого это важно

Если вы CIO компании с production-сервером на Debian/Ubuntu, этот класс рисков актуален и для вас. Любая команда apt на хосте под нагрузкой может стоить минут даунтайма. Особенно на ночных операциях, когда дежурный инженер устал и отвлекается на телефон и чаты.

Если у вас vCIO, который снимает с вас этот класс рисков, у вас должна быть уверенность, что подобный протокол у него прописан. Я отдаю свой CLAUDE.md любому клиенту, с которым работаю. Целиком, без купюр. Это больше тысячи строк правил, выработанных за два года практики.

Если хотите видеть больше скучных инфраструктурных разборов с реальными командами и реальным ущербом, посмотрите остальные записи журнала. Про методологию аудита я писал в записи про карту ИТ-зрелости.

Первый разговор

Я работаю с этим напрямую — ознакомительный звонок 30 минут

Расскажете ситуацию — отвечу за 4 часа в рабочее время. Звонок бесплатный, без обязательств. Если задача не моя — порекомендую коллег, у которых она хорошо ляжет.