3 часа ночи. Все blackbox-чеки зелёные: nc -zv mx1 25 отвечает, TLS-handshake проходит, аптайм-пробы показывают сплошную зелень за неделю. А в саппорте копятся тикеты: «письма приходят через 10–15 минут». Заходишь на сервер — postqueue -p | tail -1 показывает 41 273 сообщения в очереди. Принимающая сторона крупного провайдера включила throttling час назад, Postfix честно ретраит по своему backoff-расписанию, deferred растёт, а узнали вы об этом не из графика, а из злого клиента.
Проблема не в том, что не было мониторинга. Проблема в том, что мониторили не то. postfix_up и TCP-коннект на 25-й порт отвечают на вопрос «жив ли процесс», а не на вопрос «доставляется ли почта и как быстро». Настоящие сигналы здоровья MX — это глубина очередей и распределение задержки доставки по стадиям пайплайна. И то, и другое Postfix уже пишет в свои штатные источники — надо только снять.
Что мерить на самом деле: очередь и задержка, а не «процесс жив»
У Postfix четыре рабочие очереди, и рост каждой означает разное:
- incoming — только что принятые письма ждут постановки в active. Рост здесь редок и обычно значит, что qmgr не справляется с темпом приёма.
- active — то, что qmgr прямо сейчас пытается доставить. Ограничена (
qmgr_message_active_limit, по умолчанию 20000). Держится высокой — значит, доставка в реальном времени упирается в лимиты или медленный next-hop. - deferred — письма, которые не удалось доставить с первого раза и которые ждут ретрая. Это ваш главный индикатор. Рост deferred = принимающая сторона greylist'ит, throttl'ит или лежит.
- hold / maildrop / corrupt — hold обычно наполняется руками (
postsuper -h) или политикой; внезапный рост hold — чей-то забытый-h ALL.
Второе, что прячется от blackbox, — задержка доставки. Причём смотреть надо не среднее (среднее размазывает хвост ретраев и врёт), а p95/p99. Одно письмо, которое ретраится 40 минут, в среднем растворится среди тысяч мгновенных, но именно оно — ваш инцидент. И задержку нужно раскладывать по стадиям. Postfix в каждой строке лога пишет delays=a/b/c/d, и это готовая диагностика: рост a (приём + DNSBL + milter), b (ожидание в очереди), c (DNS + TLS до next-hop) и d (передача данных) указывает на совершенно разные корневые причины.
Вот инциденты, которые видны только по этим метрикам и невидимы для аптайм-чека:
- Забитый deferred из-за greylisting/throttling принимающей стороны — очередь растёт, процесс жив.
- Всплеск
before_queue_manager, когда тормозит DNSBL или milter — приём замедляется на входе. - Рост
connection_setupпри MTU/TLS-проблемах к апстриму — DNS резолвится, а handshake висит. - Наполнение hold-очереди после ручного
postsuper -h— почта стоит, метрики приёма в норме.
Как postfix_exporter снимает данные: два источника
Стандарт де-факто — kumina/postfix_exporter. Слушает :9154, отдаёт /metrics и берёт данные из двух независимых источников.
Источник 1 — mail.log (или journald). Экспортёр парсит строки лога регэкспами: события cleanup, qmgr, smtp/lmtp, smtpd превращаются в счётчики и гистограммы. Именно отсюда берётся postfix_smtp_delivery_delay_seconds — экспортёр вытаскивает поле delays=a/b/c/d из каждой строки доставки.
Источник 2 — unix-сокет `/var/spool/postfix/public/showq`. Из него экспортёр читает бинарный снапшот очередей — тот же, что отдаёт postqueue -p, — и строит гистограмму размеров сообщений по каждой очереди. Отсюда postfix_showq_message_size_bytes_count{queue=...} — число писем в incoming/active/deferred/hold/maildrop прямо сейчас.
Режимы чтения логов взаимоисключающие: либо --postfix.logfile_path /var/log/mail.log, либо --systemd.enable для journald. И здесь же — типовая причина пустых метрик. Для чтения mail.log экспортёру нужна группа adm (или systemd-journal в journald-режиме). Для сокета showq — группа postdrop: сам сокет имеет режим 0660 root:postdrop. Если postfix_showq_* пустые, а postfix_up равен 1 — на 99% процесс экспортёра не в группе postdrop.
Установка и systemd-юнит
Берёте готовый релиз или собираете go build. Кладёте бинарь в /usr/local/bin/, заводите отдельного пользователя и пишете юнит:
[Unit]
Description=Prometheus Postfix exporter
After=postfix.service
[Service]
User=postfix_exporter
ExecStart=/usr/local/bin/postfix_exporter \
--postfix.showq_path=/var/spool/postfix/public/showq \
--systemd.enable \
--web.listen-address=:9154
SupplementaryGroups=adm postdrop systemd-journal
Restart=on-failure
[Install]
WantedBy=multi-user.targetSupplementaryGroups=adm postdrop systemd-journal — та самая строчка, из-за отсутствия которой люди неделями смотрят на полупустой дашборд. Если читаете файловый лог, а не journald — уберите --systemd.enable и добавьте --postfix.logfile_path=/var/log/mail.log.
Проверка сразу после systemctl enable --now postfix_exporter:
curl -s localhost:9154/metrics | grep postfix_up
# postfix_up 1postfix_up 1 значит только, что экспортёр видит Postfix. Чтобы отличить «запустился, но логи не читает», сразу проверьте, что наполняются оба класса метрик:
curl -s localhost:9154/metrics | grep -c postfix_showq_message_size_bytes_count
curl -s localhost:9154/metrics | grep postfix_smtpd_messages_processed_totalПусто в первой — нет доступа к сокету (postdrop). Пусто во второй — нет доступа к логу (adm/systemd-journal).
Prometheus: scrape-конфиг и лейблы
scrape_configs:
- job_name: postfix
scrape_interval: 15s
static_configs:
- targets: ['mx1.evilmail.pro:9154', 'mx2.evilmail.pro:9154']
relabel_configs:
- source_labels: [__address__]
regex: '([^:]+):.*'
target_label: instance
replacement: '$1'Relabel вычищает порт из instance, чтобы на графиках было mx1.evilmail.pro, а не mx1.evilmail.pro:9154. scrape_interval: 15s достаточно: чтение сокета showq не бесплатное, а более частая дискретизация в 5s ничего полезного не покажет.
Главное правило работы с этими метриками: все счётчики сбрасываются при рестарте экспортёра, поэтому любой запрос по счётчикам строится через rate() или increase(), никогда по абсолютному значению. Исключение — postfix_showq_*: это gauge (снимок текущего состояния), их можно и нужно читать напрямую.
Ключевые метрики и что за ними стоит
- `postfix_smtpd_messages_processed_total{sasl_method}` — входящий приём на smtpd. Лейбл
sasl_methodразделяет аутентифицированных отправителей от анонимных. - `postfix_smtp_delivery_delay_seconds_bucket{stage}` — гистограмма задержки доставки,
stage ∈ {before_queue_manager, queue_manager, connection_setup, transmission}. Прямое соответствие полюdelays=в логе. Ваш главный диагностический инструмент — по тому, какая стадия растёт, вы понимаете, куда бежать. - `postfix_showq_message_size_bytes_count{queue}` — число писем в очереди,
queue ∈ {incoming, active, deferred, hold, maildrop}. - `postfix_qmgr_messages_inserted_size_bytes` / `postfix_qmgr_messages_removed_total` — темп постановки в очередь и вымывания из неё.
- `postfix_cleanup_messages_rejected_total` — reject на этапе cleanup (обычно политики/размер/заголовки).
- `postfix_smtpd_sasl_authentication_failures_total` — провалы SASL. Резкий рост = брутфорс или уже скомпрометированный аккаунт перебирает пароли.
Графики в Grafana
Диаграмма ниже — ключ к чтению задержки. Она связывает поле лога delays=, лейбл stage в метрике и конкретное действие инженера.
Набор панелей с готовым PromQL:
# Входящий rate (писем/с)
sum(rate(postfix_smtpd_messages_processed_total[5m]))
# Глубина deferred по инстансам — отдельным ярким графиком
sum(postfix_showq_message_size_bytes_count{queue="deferred"}) by (instance)
# p99 задержки доставки с разбивкой по стадиям
histogram_quantile(0.99,
sum(rate(postfix_smtp_delivery_delay_seconds_bucket[5m])) by (le, stage))
# Доля reject на cleanup
sum(rate(postfix_cleanup_messages_rejected_total[5m]))
# Провалы SASL
sum(rate(postfix_smtpd_sasl_authentication_failures_total[5m])) by (instance)Отдельно стоит панель «дыхание сервера» — просто rate входящих сообщений. Здоровый MX всегда чем-то дышит; внезапный ровный ноль — dead-air, значит smtpd перестал принимать (упал listener, забился фильтр, кто-то поменял smtpd_client_connection_count_limit). Этот график ловит инциденты, которые не поднимают ни одну очередь, потому что письма просто не входят.
Community-дашборд для kumina/postfix_exporter существует, но stage-разбивку delivery_delay в нём обычно приходится добавлять руками — а она-то и есть самое ценное.
Алерты по объёму и задержкам
Пороги ниже — для transactional MX. Для bulk/newsletter-потока их надо ослаблять: там deferred в тысячи — норма жизни.
groups:
- name: postfix
rules:
- alert: PostfixDeferredGrowing
expr: sum(postfix_showq_message_size_bytes_count{queue="deferred"}) by (instance) > 1000
for: 15m
- alert: PostfixDeliveryDelayHigh
expr: histogram_quantile(0.99,
sum(rate(postfix_smtp_delivery_delay_seconds_bucket{stage="transmission"}[5m])) by (le,instance)) > 30
for: 10m
- alert: PostfixExporterDown
expr: postfix_up == 0
for: 2m
- alert: PostfixSaslBruteforce
expr: sum(rate(postfix_smtpd_sasl_authentication_failures_total[5m])) by (instance) > 5
for: 5m
- alert: PostfixDeadAir
expr: sum(rate(postfix_smtpd_messages_processed_total[10m])) by (instance) == 0
for: 10mЛогика порогов:
- PostfixDeferredGrowing —
for: 15m, а не мгновенно, потому что кратковременный всплеск deferred при недоступности одного получателя — норма. Устойчивые 1000+ пятнадцать минут подряд — уже системная проблема доставки. - PostfixDeliveryDelayHigh — берём именно
stage="transmission": рост здесь значит медленный next-hop, а не ваши DNSBL.for: 10mотсекает единичный долгий ретрай. - PostfixDeadAir — окно
[10m]иfor: 10mзащищают от ложняка на низкотрафиковом MX ночью; на транзакционном узле, который должен дышать круглосуточно, десять минут тишины — реальный инцидент. - PostfixSaslBruteforce — 5 провалов/с устойчиво пять минут — это уже не «клиент забыл пароль», а атака или скомпрометированный ящик, который стоит немедленно заблокировать.
Чеклист внедрения
- Сокет
showqдоступен экспортёру — процесс в группеpostdrop,postfix_showq_*не пустые. postfix_upравен 1 и наполняются метрики приёма (postfix_smtpd_messages_processed_total).- Все четыре очереди видны в
showq_*— incoming, active, deferred, hold. - Гистограмма
delivery_delayнаполняется по всем четырёмstage, а не только по одному. - Все запросы по счётчикам через
rate()/increase(), showq — как gauge напрямую. - Лейбл
instanceчитаемый (mx1.evilmail.pro
Blackbox-проба порта 25 остаётся полезной как самый внешний слой — она ловит сетевые падения. Но инцидент доставки почти всегда начинается тихо: очередь ползёт вверх, одна стадия задержки расширяет хвост, а процесс всё это время зелёный. Дашборд по этим метрикам даёт те самые 15–30 минут форы — увидеть график раньше, чем его увидит клиент.


