1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 1 из 5

Как nginx понимает, что сервер лёг

Коротко

Открытый nginx не ходит на бэкенды проверять их состояние. Выводы он делает из обычных запросов пользователей - и цена этих выводов зависит от одной строки в группе.

  • max_fails неудач за fail_timeout выводят сервер из ротации на тот же срок. Умолчание - одна неудача и десять секунд.
  • Без zone счётчик у каждого воркера свой: замер даёт четыре открытия одного и того же падения четырьмя процессами. Со zone - одно.
  • Ответ 500 или 502 от приложения неудачей не считается. Добавишь http_502 в proxy_next_upstream - и бэкенд, штатно отвечающий 502, вылетит из группы.
  • Имя группы вместо адреса в $upstream_addr означает no live upstreams и 502 клиенту.

Если знаешь, что 500 не считается отказом, и понимаешь роль zone в счётчиках - листай до «Теперь сам».

Сначала ответь сам

Четыре рабочих процесса, группа из трёх серверов, max_fails=1. Один сервер падает. Сколько неудачных запросов уйдёт в мёртвый сервер, прежде чем nginx перестанет туда ходить?

Ответ зависит от строки, которой в конфиге обычно нет. Замер на стенде без zone:

↳  выводgrep "temporarily disabled" error.log
[warn] 72#72: *31 upstream server temporarily disabled while connecting to upstream
[warn] 73#73: *44 upstream server temporarily disabled while connecting to upstream
[warn] 74#74: *57 upstream server temporarily disabled while connecting to upstream
[warn] 75#75: *68 upstream server temporarily disabled while connecting to upstream

72#72 - номер процесса и потока (у каждого воркера свой), *31 - номер соединения. Четыре строки от четырёх разных процессов: каждый узнал о падении сам и заплатил за это своим неудачным соединением. Со строкой zone app 64k; в группе строка ровно одна.

На что это похоже

Четыре продавца в одном зале и общий список поставщиков. Если список висит на стене, первый же продавец, которому не отгрузили товар, вычеркнет поставщика для всех. Если у каждого свой блокнот, каждому придётся обжечься лично.

Работа в итоге сделается одинаково, разница только в цене обучения - и в том, сколько покупателей подождёт, пока все четверо разберутся.

Механизм: пассивная проверка

время запрос упал неудача 1 max_fails набран сервер выключен прошёл fail_timeout пробуем снова пробный запрос - обычный пользовательский, а не служебный не ответил - снова пауза; ответил - сервер вернулся в ротацию активных проверок в открытой версии нет
nginx учится на живых запросах. Первым после паузы всегда идёт чей-то настоящий запрос.

Правило

/etc/nginx/nginx.confhttp
upstream app {
    zone app 64k;
    server a1:8000 max_fails=3 fail_timeout=15s;
    server a2:8000;
}

Читается так: «если за 15 секунд с сервером случилось 3 неудачи, считать его недоступным на те же 15 секунд». Умолчание - max_fails=1 fail_timeout=10s.

Список того, что считается неудачей, задаёт proxy_next_upstream:

/etc/nginx/conf.d/shop.local.confhttp server location /api/
proxy_next_upstream error timeout;
proxy_next_upstream_tries 2;      # всего попыток, считая первую
proxy_next_upstream_timeout 10s;  # общий бюджет времени на все попытки

Умолчание - error timeout: обрыв соединения, отказ в соединении, таймаут. Код ответа приложения в этот список не входит.

Разбор: 502 от приложения - не отказ, и это к лучшему

Стенд, бэкенд отвечает 502 на каждый запрос. С умолчаниями:

↳  выводчетыре запроса, proxy_next_upstream по умолчанию
/fail -> a1  status=502
/fail -> a2  status=502
/fail -> a1  status=502
/fail -> a2  status=502

nginx передал ответ клиенту и ничего не заподозрил. Теперь добавим http_502 в список, как советуют «чтобы клиент не видел ошибок»:

↳  выводчетыре запроса, proxy_next_upstream error timeout http_502
/fail -> a1, a2   status=502  ustatus=502, 502
/fail -> a2, app  status=502  ustatus=502, 502
/fail -> app      status=502  ustatus=502
/fail -> app      status=502  ustatus=502

Первый запрос обошёл оба сервера. Второй нашёл только один живой и добил группу. Дальше в $upstream_addr стоит имя группы app - значит no live upstreams: живых серверов не осталось, и nginx отвечает 502 сам, даже не пробуя соединиться:

↳  выводerror.log
[error] no live upstreams while connecting to upstream, upstream: "http://app/fail"

Клиент как получал 502, так и получает. Разница в том, что теперь вся группа выключена, и штатные запросы, на которые бэкенд ответил бы 200, тоже получат 502. Отсюда правило: коды в proxy_next_upstream добавляют, только когда 502 означает «сервер сломан», а не «приложение так отвечает».

Разбор: как выглядит успешный повтор

Когда сервер и правда умер, клиент обычно не видит ничего:

↳  выводaccess_log
/api/cart -> 10.0.0.11:8000, 10.0.0.12:8000  status=200  ustatus=502, 200

Два адреса через запятую - первая попытка и вторая, успешная. В error_log в этот момент две строки подряд:

↳  выводerror.log
[error] connect() failed (111: Connection refused) while connecting to upstream
[warn] upstream server temporarily disabled while connecting to upstream

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

Что ломается без этого

non_idempotent добавляют, не подумав. Этот флаг в списке proxy_next_upstream разрешает повторять на другом сервере POST, PUT и DELETE - запросы, меняющие данные (идемпотентный запрос можно повторить без вреда, меняющий - нет). Если первый бэкенд успел провести платёж и умер до ответа, повтор проведёт его второй раз. Для меняющих данные запросов повтор безопасен, только когда приложение умеет идемпотентность по ключу операции.

Сервер, вернувшийся из паузы, встречает пользователя, а не проверку. Между «процесс запустился» и «готов обслуживать» проходит время: прогрев, миграции, подключение к базе. slow_start в открытой версии нет, поэтому вернувшийся сервер сразу получает полную долю; чинится это порядком выкладки из следующего урока, а не настройками группы.

Единственный сервер группы не выключается никогда. Когда сервер в группе один, max_fails и fail_timeout игнорируются: выключать некого, отвечать стало бы нечем. Поэтому на одиночном бэкенде строки no live upstreams не бывает в принципе, и весь разговор про max_fails начинается со второго сервера. А вот когда серверов несколько, недоступными могут стать все - это и показал разбор http_502 выше: «последний живой» защищён, только пока он буквально единственный в группе.

Зачем это в работе

down и backup - две соседние строки с очень разным смыслом:

/etc/nginx/nginx.confhttp
upstream app {
    zone app 64k;
    server a1:8000 down;                 # выведен руками, на обслуживание
    server a2:8000;
    server maintenance:8080 backup;      # включится, только если все основные легли
}

down - «не трогать вовсе»; так выводят машину на работы, не удаляя строку (удаление перетасует привязки при hash и ip_hash). backup - резерв, который молчит, пока жив хоть один основной сервер. Типичное применение - не второй экземпляр приложения, а маленький сервис со страницей «идут работы»: вежливая заглушка лучше, чем 502.

Теперь сам

В группе два сервера. Мониторинг показывает, что раз в несколько минут появляется пара строк connect() failed и temporarily disabled, но доля 5xx у пользователей нулевая. Стоит ли реагировать?

Стоит. Пользователей выручает повтор на живом сервере, но каждый такой эпизод - это половина мощности, выключенная на fail_timeout, и лишняя задержка у попавших под него запросов. Смотреть надо в бэкенд: обычно это перезапуск по нехватке памяти или исчерпание пула соединений к базе. И заодно добавить zone, чтобы падение открывал один воркер, а не каждый по очереди.

Главное

Открытая версия проверяет бэкенды пассивно: max_fails неудач за fail_timeout (по умолчанию одна за десять секунд) выключают сервер на тот же срок, а первым после паузы идёт настоящий пользовательский запрос. Без zone счётчик у каждого воркера свой, и падение открывают все по очереди. Неудачей считается error timeout, но не код ответа: добавив http_502, легко выключить всю группу из-за приложения, которое так отвечает штатно. Имя группы вместо адреса в $upstream_addr - это no live upstreams. Сервер на обслуживание помечают down, а backup держат под заглушку.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.