MAATRIX / Блог / NATS на сервере: частые ошибки и решения

NATS на сервере: частые ошибки и решения

MAATRIX

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 не откликнулся. Причины обычно три:

  1. Сервис-обработчик ещё не поднялся — гонка при старте контейнеров. Если API и воркер стартуют одновременно через docker-compose up, запрос может уйти раньше, чем воркер успел подписаться.
  2. Subject не совпадает буквально — NATS не прощает опечаток: orders.created и orders.Created — разные subjects, wildcard > ловит всё после точки, * — ровно один токен.
  3. Подписчик отвалился по таймауту, но клиент этого не узнал — соединение мертво, но 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →