NATS на сервере: частые ошибки и решения
NATS выбирают за простоту: один бинарник, минимум конфигурации, брокер поднимается за минуту и держит десятки тысяч сообщений в секунду без танцев с ZooKeeper или партициями, как у Kafka. Но именно эта простота подводит — типовые ошибки почти всегда не в самом NATS, а в том, как его запускают, кластеризуют и подключают к клиентам. Разберём конкретные сбои и их причины: от отказа стартовать до тихой потери сообщений в JetStream.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Установка и первый запуск: типичные ошибки конфигурации
Самый быстрый способ попробовать NATS — Docker:
docker run -d --name nats -p 4222:4222 -p 8222:8222 -p 6222:6222 nats:2.10-alpine
Порт 4222 — клиентские подключения, 8222 — HTTP-мониторинг, 6222 — кластерные routes. Если контейнер падает сразу после старта, почти всегда причина в конфиг-файле, который передали через -c:
docker run -d --name nats -p 4222:4222 -v /opt/nats/nats.conf:/etc/nats/nats.conf nats:2.10-alpine -c /etc/nats/nats.conf
Частая ошибка — address already in use. На сервере с уже поднятым Redis или другим сервисом порт 4222 обычно свободен, но конфликт возникает, если вы пробовали запустить NATS дважды (например, через systemd и вручную одновременно). Проверка:
ss -tlnp | grep 4222
Вторая частая ошибка при работе с конфиг-файлом — синтаксис. NATS использует собственный формат (похож на JSON без обязательных кавычек у ключей), и опечатка в фигурной скобке валит сервер с невнятным parse error. Проверяйте конфиг перед перезапуском:
nats-server -t -c /etc/nats/nats.conf
Флаг -t только тестирует конфиг и завершает процесс — это должно стать привычкой перед каждым systemctl restart nats. Для systemd unit-файла:
[Unit]
Description=NATS Server
After=network.target
[Service]
ExecStart=/usr/local/bin/nats-server -c /etc/nats/nats.conf
ExecStop=/bin/kill -SIGINT $MAINPID
User=nats
Group=nats
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Обратите внимание на LimitNOFILE: у NATS каждое клиентское подключение — это файловый дескриптор, и при большом количестве коннектов дефолтный лимит в 1024 упирается в стену с ошибкой too many open files в логе, а не в понятном месте у клиента.
"no responders available for request" и разрыв соединений
Это, пожалуй, самая частая жалоба в issues и на форумах. Ошибка означает, что клиент отправил request-reply запрос (nc.Request()), но ни один подписчик на этот subject не откликнулся. Причины обычно три:
- Сервис-обработчик ещё не поднялся — гонка при старте контейнеров. Если API и воркер стартуют одновременно через
docker-compose up, запрос может уйти раньше, чем воркер успел подписаться. - Subject не совпадает буквально — NATS не прощает опечаток:
orders.createdиorders.Created— разные subjects, wildcard>ловит всё после точки,*— ровно один токен. - Подписчик отвалился по таймауту, но клиент этого не узнал — соединение мертво, но TCP-сессия ещё не закрылась операционной системой.
Для третьего случая критичны настройки keepalive в конфиге сервера:
ping_interval: "2m"
ping_max: 2
write_deadline: "10s"
Если сеть нестабильна (это особенно заметно на маршрутах через несколько стран), уменьшите ping_interval до 30-60 секунд — сервер быстрее заметит мёртвого подписчика и разорвёт соединение, освободив subject для реального обработчика вместо того, чтобы слать запросы в никуда.
Отдельно проверьте таймаут на стороне клиента — дефолтные 2 секунды в большинстве клиентских библиотек часто слишком малы для холодного старта сервиса под нагрузкой:
resp, err := nc.Request("orders.created", data, 5*time.Second)
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверКластеризация NATS: routes, auth, cluster_id
Кластер из трёх узлов — стандартная схема для отказоустойчивости. Конфиг на каждом узле:
port: 4222
http: 8222
cluster {
name: "prod-cluster"
listen: 0.0.0.0:6222
routes: [
nats-route://10.0.0.1:6222
nats-route://10.0.0.2:6222
nats-route://10.0.0.3:6222
]
}
Узел не видит соседей в кластере — типичная ошибка новичков — почти всегда сводится к одному из трёх пунктов:
cluster.nameне совпадает между узлами. NATS откажется объединять узлы с разными именами кластера, и в логе будет молчаливое отсутствие handshake, а не явная ошибка.- Firewall режет порт 6222 между серверами. Проверьте telnet или
nc -zv 10.0.0.2 6222с каждого узла — это routes-порт, отдельный от клиентского 4222. - В routes указан сам себя — узел добавил свой собственный адрес в список routes, из-за чего логи засоряются повторяющимися
connect attemptбез реального вреда, но путают при диагностике.
Если кластер использует аутентификацию, добавьте её одинаково на все узлы — иначе routes не установятся:
cluster {
name: "prod-cluster"
listen: 0.0.0.0:6222
authorization {
user: cluster_user
password: "$2a$11$..."
}
routes: [...]
}
Пароль лучше хранить в виде bcrypt-хэша (генерируется nats-server --gen_password в некоторых сборках или вручную через htpasswd), а не открытым текстом в конфиге — файл всё равно стоит держать с правами 600 и владельцем nats.
JetStream: персистентность, диск и retention
Без JetStream NATS — чистый fire-and-forget: подписчика нет, сообщение потеряно. Для очередей задач, которые должны пережить рестарт, включайте JetStream:
jetstream {
store_dir: "/data/nats/jetstream"
max_mem: 1G
max_file: 20G
}
Здесь и живёт большинство проблем "в проде": диск на /data/nats заканчивается, потому что retention не настроен и стрим растёт бесконечно. Создавая стрим, явно задавайте политику хранения:
nats stream add ORDERS \
--subjects "orders.>" \
--retention limits \
--max-age 7d \
--max-bytes 5GB \
--storage file \
--replicas 3
--max-age и --max-bytes — то, что чаще всего забывают в первой версии конфигурации, а потом получают заполненный диск и упавший сервер, потому что JetStream не может писать WAL. Проверить текущее состояние стрима:
nats stream info ORDERS
Если увидели messages: 4200000 при ожидаемых сотнях — почти наверняка забыт consumer с ack_policy: explicit, который не подтверждает сообщения, и они копятся вместо удаления по прочтении. Для consumer'а с явным подтверждением:
nats consumer add ORDERS worker-1 \
--ack explicit \
--pull \
--max-deliver 5
--max-deliver ограничивает число повторных доставок — без него "зависшее" сообщение, которое воркер не смог обработать, будет пересылаться бесконечно, забивая логи и метрики повторами.
TLS и аутентификация: закрываем NATS от внешнего мира
Если порт 4222 торчит наружу без аутентификации — это открытый брокер, в который может писать и читать кто угодно, кто найдёт IP. На VPS с публичным IP это не гипотетический риск, а вопрос дней до первого сканирования. Минимальная защита — аккаунты и токены:
authorization {
users: [
{ user: "app", password: "$2a$11$hash...", permissions: {
publish: "orders.>"
subscribe: "orders.>"
}}
{ user: "monitor", password: "$2a$11$hash...", permissions: {
subscribe: ">"
}}
]
}
Permissions ограничивают не только доступ, но и то, какие subjects конкретный пользователь может публиковать или читать — полезно, когда несколько команд делят один кластер и не должны видеть чужие очереди.
Для шифрования трафика между клиентами и сервером (обязательно, если сервер принимает подключения не только из локальной сети):
tls {
cert_file: "/etc/nats/certs/server-cert.pem"
key_file: "/etc/nats/certs/server-key.pem"
ca_file: "/etc/nats/certs/ca.pem"
verify: true
}
Сертификаты можно выпустить через Let's Encrypt, если у NATS есть собственное доменное имя — процесс выпуска и продления описан в статье про Let's Encrypt SSL на сервере. Частая ошибка после включения TLS — клиенты падают с x509: certificate signed by unknown authority, потому что забыли передать ca_file в клиентский конфиг, а не только на сервере.
Мониторинг и логи NATS на сервере
HTTP-эндпоинт на порту 8222 отдаёт метрики без дополнительной настройки:
curl http://localhost:8222/varz | jq '.connections, .in_msgs, .out_msgs'
curl http://localhost:8222/connz | jq '.connections[].subscriptions'
/varz — общая статистика сервера, /connz — список активных подключений с их подписками, /jsz — состояние JetStream (полезно смотреть messages и bytes по каждому стриму). Для постоянного мониторинга разумнее не дёргать curl руками, а поднять prometheus-nats-exporter, который транслирует эти же метрики в формат Prometheus:
services:
nats-exporter:
image: natsio/prometheus-nats-exporter:latest
command: ["-varz", "-connz", "-jsz=all", "http://nats:8222"]
ports:
- "7777:7777"
Дальше метрики забирает Prometheus и визуализирует Grafana — если такой связки ещё нет, разворачивание с нуля описано в статье про Grafana и Prometheus на сервере, а типичные грабли при их совместной настройке — в отдельном разборе частых ошибок Grafana и Prometheus.
Логи самого nats-server по умолчанию идут в stdout (или в файл, если указан log_file в конфиге). Для отладки полезен debug: true и trace: true в конфиге — но включайте их только временно: trace-режим логирует каждое сообщение целиком и на нагруженном сервере за час съедает гигабайты диска. Диск под логи и данные JetStream стоит мониторить отдельно — как настроить алерты по заполнению раздела, описано в статье про мониторинг диска на сервере.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
NATS теряет сообщения при рестарте сервера — это нормально?
Без JetStream — да, это ожидаемое поведение: core NATS не хранит сообщения, только доставляет их живым подписчикам. Если нужна персистентность, включайте JetStream с файловым хранилищем и retention-политикой, как описано выше.
Сколько памяти и диска закладывать под NATS на VPS?
Core NATS почти ничего не требует — сотни мегабайт памяти хватает даже под тысячи подключений. JetStream — другое дело: диск нужен под весь непрочитанный объём сообщений с учётом max-bytes и max-age, а память — под индексы стримов, обычно десятки-сотни мегабайт на активный стрим, но при большом числе стримов стоит закладывать запас и проверять /jsz на реальной нагрузке.
Чем NATS отличается от Kafka и Redis Streams для очередей задач?
NATS проще в эксплуатации и легче по ресурсам, но менее богат по гарантиям порядка и exactly-once семантике, чем Kafka. Если сравниваете варианты под конкретную нагрузку, посмотрите разбор Redis или Memcached — что выбрать — логика выбора между лёгким и тяжёлым инструментом там похожая.
Можно ли запускать NATS в кластере всего на двух узлах?
Технически да, но это не даёт кворума для JetStream-репликации (нужно нечётное число узлов, минимум 3, для устойчивости к split-brain). Для продакшена с JetStream закладывайте три узла с самого начала — добавить узлы позже сложнее, чем поднять сразу правильную топологию.
Как безопасно хранить пароли и токены из конфига NATS?
Не держите их открытым текстом в репозитории — используйте переменные окружения или секрет-хранилище на уровне Docker/Compose. Общий подход к управлению такими секретами на сервере разобран в статье про Docker secrets и управление паролями.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →