# теория · шаг 1 из 5
Симптом, причина, строка в логе
Коротко
При инциденте сопоставь результат запроса с этапом, на котором он возник.
- Upstream-поля показывают обращение к приложению; прочерк сам по себе не означает, что приложение упало.
- В error_log различай отказ соединения, чтение заголовков и обрыв тела уже начатого ответа.
- $request_time рядом с $upstream_response_time помогает локализовать задержку, но не заменяет поиск причины.
- Воспроизведи запрос с тем же именем и путём, затем сравни ответ через nginx и напрямую от бэкенда.
Коды знаешь наизусть - листай до «Что ломается без этого»: там про поломки, которых в кодах ответа не видно.
Три строки одного лога. По какой из них видно, что до бэкенда дело не дошло?
200 ups_status=[200] ups_addr=[172.25.0.2:9000]
502 ups_status=[502] ups_addr=[127.0.0.1:9]
200 ups_status=[-] ups_addr=[-]
Ответ неочевиден: не по второй. Там 502 и адрес - соединение пытались
открыть, и это записано. Прочерк стоит в третьей строке, где nginx ответил сам
и никуда не ходил.
На что это похоже
Разбор инцидента похож на разговор с диспетчером: прежде чем обсуждать, что случилось у мастера, надо выяснить, доехал ли он вообще. Половина времени теряется именно здесь - разработчиков будят из-за ошибки, которая произошла до того, как запрос покинул nginx.
Поэтому первый вопрос всегда один и тот же: кто ответил. И ответ на него уже лежит в логе - если формат лога придуман заранее, а не в момент аварии.
Механизм: кто ответил на запрос
Правило
log_format timing '$status ups_status=[$upstream_status] ups_addr=[$upstream_addr] '
'rt=$request_time urt=[$upstream_response_time] "$request"';
| Код | Частая причина | Что проверить |
|---|---|---|
| 502 | соединения не было либо ответ негоден; сюда же no live upstreams; бывает и 502 самого бэкенда |
жив ли процесс, error_log со словом upstream |
| 504 | бэкенд не уложился в таймаут - соединения или ответа | этап в строке upstream timed out, медленные запросы приложения |
| 503 | сработал твой limit_req или limit_conn; 503 шлёт и бэкенд на обслуживании |
зоны лимитов, строка limiting requests, $upstream_status |
| 500 | у прокси - кончились слоты worker_connections; ещё ошибка в конфиге фазы или само приложение |
[alert] в error_log, $upstream_status |
| 499 | клиент ушёл, не дождавшись | $upstream_response_time - стало медленно |
| 413 | тело больше client_max_body_size |
загрузка файлов |
| 414 / 400 | длинный URI или заголовок | large_client_header_buffers |
no live upstreams - это 502, а не 503: группа пуста, соединяться не с
чем. Путаница возникает потому, что 503 отдают лимиты, и оба кода приходят из
одного слова «недоступно».
Разбор: прочерк против 502 в upstream_status
Расхожая формулировка «$upstream_status пуст, если до бэкенда дело не дошло»
неточна, и неточность стоит времени. Замер по трём маршрутам одного конфига:
| маршрут | $status |
$upstream_status |
$upstream_addr |
|---|---|---|---|
| прокси к живому бэкенду | 200 | 200 |
172.25.0.2:9000 |
| прокси к закрытому порту | 502 | 502 |
127.0.0.1:9 |
return 200 без прокси |
200 | - |
- |
| прокси с кешем, второй запрос (HIT) | 200 | - |
- |
Последняя строка - замер на 1.31.8: проксирование настроено, но идти к бэкенду
не понадобилось. Правильная формулировка: прочерк означает, что к бэкенду не
обращались - ответил return, статика, лимит, кеш или error_page без
обращения к бэкенду. Как
только nginx попробовал соединиться, в переменной появляется код, который он сам
и назначил.
Практическое следствие: строка 502 ups_status=[502] - это не обязательно
«бэкенд ответил 502»: точно так же выглядит «nginx не смог получить ответ». Отличить одно от другого можно по
error_log: у настоящего 502 от приложения там ничего не будет.
Разбор: строки error_log, читаемые буквально
connect() failed (111: Connection refused) while connecting to upstream
Порт не слушает: процесс не поднялся, упал или слушает другой адрес. Самая
частая причина 502 и самая быстрая в проверке - ss -ltnp.
upstream timed out (110: Operation timed out) while reading response header
Соединение есть, ответа нет: бэкенд занят, а не мёртв. Хвост строки важен:
со словом header ошибка случилась, пока заголовки ответа ещё не получены
целиком, и клиент получит 504. Та же строка без слова header означает, что код
ответа клиенту уже ушёл, - тогда в access_log будет этот код, обычно 200, с
обрезанным телом (шестой раздел).
upstream sent too big header while reading response header from upstream
Заголовки не влезли в proxy_buffer_size. Классика после жирного Set-Cookie
или длинного JWT (токена авторизации, который клиент носит в заголовке): часть страниц работает, а личный кабинет отдаёт 502.
[alert] 10#10: *10 4 worker_connections are not enough while connecting to upstream
Слота не нашлось при подключении к бэкенду - в счёт входят и такие соединения,
поэтому реальный запас вдвое меньше настроенного. Родственная строка на уровне
warn с хвостом reusing connections означает противоположное: nginx закрыл
простаивающие keepalive и справился.
Что ломается без этого
Обрыв после заголовков не виден в кодах ответа вовсе. Если бэкенд отдал
заголовки и оборвался, клиент получает 200 и обрезанное тело, а в
access_log кода ошибки нет. Мониторинг «доля 5xx» такой класс поломок не
ловит; ловят его расхождение $body_bytes_sent с обещанным размером и строки
в error_log.
Отброшенные соединения не пишутся в лог совсем. Когда слоты кончились,
запроса не случилось - значит и строки в access_log нет. Единственные следы -
accepts против handled в stub_status и [alert] в error_log.
Текст системной ошибки зависит от библиотеки. Один и тот же таймаут: на
alpine (110: Operation timed out), на debian (110: Connection timed out).
Занятый порт: (98: Address in use) против (98: Address already in use).
Номер одинаков всегда - искать надо по номеру, иначе grep из чужой статьи не
находит ничего, и рождается вывод «у нас другая проблема».
Зачем это в работе
Четыре правила, которых хватает, чтобы за минуту отделить «чинить nginx» от «будить разработчиков»:
$request_timeбольшой,$upstream_response_timeмаленький - тормозит не бэкенд: медленный клиент, огромный ответ, узкий канал или очередьlimit_req;- обе большие - тормозит приложение;
- в
$upstream_addrнесколько адресов через запятую - были повторы на другие узлы, то есть первый отказал; $upstream_statusпуст - к бэкенду в этом запросе не ходили: ответилreturn, статика, лимит или кеш, смотреть конфиг nginx и кеш.
Отдельная категория - инциденты без единой строки в error_log: старые данные
у части пользователей (ключ кеша), неработающая загрузка больших файлов (413
видно только в браузере), сломанный WebSocket (в логе обычный 200), отвалившиеся
картинки на поддомене (сертификат не покрывает имя). Для них нужен не лог, а
воспроизведение с параметрами пострадавшего: тот же Host, тот же метод, то же
тело.
Теперь сам
В логе всплеск строк вида 502 ups_status=[502] ups_addr=[10.0.0.7:8000,
10.0.0.8:8000] rt=3.1. Что здесь произошло и в каком порядке смотреть?
Ответ: два адреса через запятую означают, что запрос повторили на второй узел
после отказа первого, и оба не ответили - это proxy_next_upstream в работе.
ups_status=[502] при этом поставил сам nginx, значит ответа от приложения не
было ни от одного узла. Смотреть: error_log рядом по времени - там будет
connect() failed (порт не слушает) или upstream timed out (занят); затем
жив ли процесс приложения на обоих узлах; и только потом - не слишком ли
строгие max_fails, из-за которых узлы могли быть выключены после единичных
таймаутов. Три секунды в rt при этом подсказывают, что время ушло на попытки
соединиться, а не на ожидание ответа.
Главное
Начни расследование с того, кто сформировал ответ и на каком этапе возник отказ. Сопоставь upstream-поля с error_log, затем воспроизведи запрос. Один код ответа допускает несколько причин; время и полнота тела помогают найти ошибки, которые не увеличивают долю 5xx.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий