Схема по материалу: Как я отличаю ошибку Nginx upstream от сетевой недоступности приложения
Схема основана на материале «Как я отличаю ошибку Nginx upstream от сетевой недоступности приложения» и его сигналах. Слева пользовательская и Zabbix-точки, в центре Nginx с ветвью stub_status и request ID, справа цепь DNS → TCP → TLS → HTTP → payload и группа backend; под ней временная линия access, error и application logs.

Что показывает Nginx, а чего в его метриках нет

Для базового наблюдения я открываю stub_status только на локальном или выделенном служебном listener, разрешённом конкретному сборщику. Он даёт агрегаты активных соединений, принятых и обработанных соединений, запросов, состояний Reading, Writing и Waiting. Эти числа показывают общий сдвиг, но не называют URI, upstream, request ID и причину отдельного отказа. Prometheus exporter опрашивает разрешённый endpoint и превращает агрегаты во временные ряды; судьбу запроса он не восстанавливает. Я держу область минимальной: endpoint закрыт от пользовательской сети, а сборщик имеет доступ без административных прав. Для события отправной точкой остаются журналы и корреляционный идентификатор, агрегаты дают только фон.

Собираю причинную цепь по request ID и узкому времени

Сначала я фиксирую время со стороны пользователя с часовым поясом, host, метод, путь без чувствительной строки запроса и request ID. Если внешний прокси уже создаёт идентификатор, Nginx принимает его только по согласованному правилу; иначе генерирует собственный и передаёт backend. В access log мне нужны status, request_time, upstream_addr, upstream_status, upstream_connect_time, upstream_header_time и upstream_response_time. В error log ищу тот же запрос и соседний контекст только в узком интервале. Затем сопоставляю журнал приложения по переданному ID и времени. Порядок важен: клиент мог закрыть соединение раньше, чем backend закончил работу, а сообщение приложения могло появиться после ответа Nginx. Я сохраняю исходную причинную последовательность и делаю рабочую копию фрагментов. В ней редактирую Authorization, Cookie, токены, сессии, персональные данные, параметры форм, внутренние адреса и секретные части URI. Маскирование оставляет тип ошибки, фазу соединения, код, длительность и стабильную обезличенную метку узла. Доступ к оригиналам остаётся у минимального круга в защищённом контуре.

Времена запроса и случай, когда upstream не выбран

$request_time измеряет весь интервал обработки запроса Nginx, включая чтение от клиента, выбор маршрута, обмен с backend и отправку ответа. $upstream_response_time относится к обмену с выбранным upstream и при повторных попытках может содержать несколько значений. Большой request_time при коротком upstream_response_time способен указывать на медленного клиента, локальную обработку или передачу ответа; близкие величины не доказывают задержку backend. Дефис в upstream-полях я не называю сетевым отказом. Upstream мог не выбираться: запрос обслужил статический location, cache, return, rewrite, локальная авторизация, ограничение доступа или ветка до proxy_pass. Сначала восстанавливаю выбранные server и location. Для нескольких попыток сохраняю порядок адресов, статусов и времён, иначе последняя запись заслонит первый connect failure. Семантику полей проверяю по действующему log_format.

Почему 499, 502, 503 и 504 требуют продолжения разбора

Код 499 в журнале Nginx обычно означает, что клиент закрыл соединение до ответа; причиной может быть пользовательский таймаут, промежуточный балансировщик, потеря связи или долго работающий backend. 502 говорит, что шлюз не получил пригодный ответ upstream: ветви включают connect refusal, reset, преждевременное закрытие, ошибочный протокол или заголовок. 503 может прийти от самого приложения, возникнуть при отсутствии доступной группы backend либо быть намеренным ответом локального правила. 504 связывают с ожиданием upstream, но ещё нужно установить фазу и участника, установившего предел. Ни один из кодов сам по себе не различает сеть, Nginx и payload. Я проверяю пару upstream_status и итогового status, текст error log, времена фаз и журнал приложения. Гипотезу об отказе backend отклоняю, если запрос с тем же ID был принят приложением и оно осмысленно вернуло ответ; гипотезу о DNS отклоняю только после подтверждения использованного адреса и успешного разрешения в нужный момент, а не по текущему результату команды спустя время.

Синтетическая цепочка отделяет транспорт от приложения

Проверку я раскладываю на DNS resolve → TCP connect → TLS handshake → HTTP transaction и выполняю из той точки, где работает Nginx, либо из заранее разрешённого диагностического сегмента с сопоставимым маршрутом. На DNS фиксирую имя, полученный адрес, используемый resolver и время наблюдения. На TCP проверяю установление соединения именно с выбранным адресом и портом. Для TLS сохраняю результат handshake, SNI, проверку цепочки и имени без вывода ключевого материала. Затем отправляю безопасный HTTP-запрос с корректными Host и маршрутом, записывая status, заголовок типа содержимого и ограниченный проверяемый признак тела. Успех каждого шага разрешает только соответствующую ветвь: рабочий DNS не обещает TCP, handshake не гарантирует HTTP, а HTTP 200 не доказывает правильность бизнес-payload. Синтетика также не воспроизводит пользовательскую сессию, права, размер тела, конкретные данные и путь через внешний балансировщик. Поэтому её результат я связываю с логами реального request ID, но не ставлю вместо них. Проверку прекращаю, если она может изменить данные или создать непредусмотренную нагрузку.

Health, payload, keepalive и таймауты относятся к разным вопросам

Доступный health endpoint подтверждает только заложенную проверку процесса или зависимостей. Он не гарантирует валидный ответ пользовательского маршрута. Я отдельно проверяю схему JSON, обязательные поля, типы, бизнес-инвариант или безопасный маркер; пустое тело и страница ошибки с кодом 200 нарушают payload. Тяжёлый health, напротив, может объявить backend нездоровым при рабочем маршруте. Keepalive — повторное использование соединений Nginx с конкретной группой upstream; устаревшие соединения, лимиты backend, балансировка и частота запросов меняют его пользу. Connect, send и read timeouts относятся к разным фазам, а пределы всех посредников образуют цепь. Я не переношу универсальные числа. Значения определяют профиль маршрута, сеть, допустимое время операции и политика повторов.

Как связываю Zabbix с пользовательской точкой наблюдения

В Zabbix HTTP agent я получаю служебный endpoint или ограниченный диагностический URL, сохраняю код, время и тело в пределах необходимого. Preprocessing извлекает конкретный JSONPath, регулярный признак либо числовое поле, проверяет тип и обрабатывает отсутствие значения явно; секреты не должны попадать в history. Web scenario полезен для последовательности шагов, cookies и проверки ожидаемой строки, когда важен пользовательский маршрут. Однако server или proxy Zabbix может находиться внутри сети и обходить публичный DNS, CDN, внешний балансировщик или межсетевой экран. Поэтому я документирую source, resolver, маршрут, Host/SNI, IP-версию и аутентификационный контекст агентской проверки, затем сравниваю их с реальной пользовательской точкой. Зелёная внутренняя проверка не опровергает внешний сетевой сбой. И наоборот, внешний отказ не доказывает болезнь backend, если внутренняя транзакция валидна. Триггер разделяет транспортный результат и проверку payload, а теги события связывают service, observation point и обезличенный route. Агрегаты stub_status остаются зависимыми элементами контекста, не причиной события.

Контролируемая проверка backend и условия возврата

Если собранных следов недостаточно и владелец сервиса разрешил воздействие, я рассматриваю контролируемый drain одного backend: сначала подтверждаю избыточность, актуальный способ исключения узла, исправность остальных экземпляров и доступность команды возврата. Фиксирую конфигурационную версию, активные соединения, распределение запросов, ошибки, payload-проверку и пользовательскую точку до изменения. Узел перестаёт получать новые запросы только предусмотренным для данной платформы способом; существующие соединения завершаются согласно её поведению. Во время окна я наблюдаю Nginx, Zabbix и приложение, не совмещая drain с релизом или сетевыми работами. Если ошибка следует за исключённым backend и возвращается вместе с ним при согласованной проверке, это усиливает адресную гипотезу, но требует лога приложения. Если симптом остаётся на других узлах, от версии об одном backend отказываюсь. Условия немедленного отката заранее определены: ухудшение доступности, рост ошибок, потеря запаса мощности, неверная маршрутизация или отсутствие наблюдаемости. Возврат выполняется штатным обратным действием с проверкой конфигурации и payload. Произвольный reload, массовый restart и очистка логов в такой проверке недопустимы.

Сигналы, которые сопоставляю

  • Request ID и узкое время, связанная строка access log, соответствующий фрагмент error log, upstream-адреса и времена фаз, след приложения либо доказанное отсутствие его приёма, результат транспорта и отдельная проверка payload.
  • Я принимаю причину, когда независимые наблюдения совпадают по запросу, маршруту и времени, объясняют фазу отказа, а альтернативы опровергнуты положительными проверками.
  • До воздействия сохраняются минимальные обезличенные фрагменты; полные журналы, заголовки и тела не переносятся из защищённого контура без необходимости.
  • Nginx aggregate endpoint
  • internal HTTP agent
  • external web scenario
  • application payload check
  • transport status and phase
  • HTTP response code
  • transaction duration
  • payload validity
  • observation point identity
  • Preprocessing извлекает только ожидаемый признак, валидирует тип и различает пустой ответ, ошибку разбора и неверное значение; чувствительное тело отбрасывается до хранения.
  • Событие связывается с service, route и observation point; внутренний и внешний результаты сравниваются только при документированном соответствии DNS, маршрута, Host/SNI и контекста запроса.

Отвергнутые альтернативы

  • Что показывает Nginx, а чего в его метриках нет: Для базового наблюдения я открываю stub_status только на локальном или выделенном служебном listener, разрешённом конкретному сборщику. Он даёт агрегаты активных соединений, принятых и обработанных соединений, запросов, состояний Reading, Writing и Waiting. Эти числа показывают общий сдвиг, но не называют URI, upstream, request ID и причину отдельного отказа. Prometheus exporter опрашивает разрешённый endpoint и превращает агрегаты во временные ряды; судьбу запроса он не восстанавливает. Я держу область минимальной: endpoint закрыт от пользовательской сети, а сборщик имеет доступ без административных прав. Для события отправной точкой остаются журналы и корреляционный идентификатор, агрегаты дают только фон.
  • Собираю причинную цепь по request ID и узкому времени: Сначала я фиксирую время со стороны пользователя с часовым поясом, host, метод, путь без чувствительной строки запроса и request ID. Если внешний прокси уже создаёт идентификатор, Nginx принимает его только по согласованному правилу; иначе генерирует собственный и передаёт backend. В access log мне нужны status, request_time, upstream_addr, upstream_status, upstream_connect_time, upstream_header_time и upstream_response_time. В error log ищу тот же запрос и соседний контекст только в узком интервале. Затем сопоставляю журнал приложения по переданному ID и времени. Порядок важен: клиент мог закрыть соединение раньше, чем backend закончил работу, а сообщение приложения могло появиться после ответа Nginx. Я сохраняю исходную причинную последовательность и делаю рабочую копию фрагментов. В ней редактирую Authorization, Cookie, токены, сессии, персональные данные, параметры форм, внутренние адреса и секретные части URI. Маскирование оставляет тип ошибки, фазу соединения, код, длительность и стабильную обезличенную метку узла. Доступ к оригиналам остаётся у минимального круга в защищённом контуре.
  • Времена запроса и случай, когда upstream не выбран: $request_time измеряет весь интервал обработки запроса Nginx, включая чтение от клиента, выбор маршрута, обмен с backend и отправку ответа. $upstream_response_time относится к обмену с выбранным upstream и при повторных попытках может содержать несколько значений. Большой request_time при коротком upstream_response_time способен указывать на медленного клиента, локальную обработку или передачу ответа; близкие величины не доказывают задержку backend. Дефис в upstream-полях я не называю сетевым отказом. Upstream мог не выбираться: запрос обслужил статический location, cache, return, rewrite, локальная авторизация, ограничение доступа или ветка до proxy_pass. Сначала восстанавливаю выбранные server и location. Для нескольких попыток сохраняю порядок адресов, статусов и времён, иначе последняя запись заслонит первый connect failure. Семантику полей проверяю по действующему log_format.
  • Почему 499, 502, 503 и 504 требуют продолжения разбора: Код 499 в журнале Nginx обычно означает, что клиент закрыл соединение до ответа; причиной может быть пользовательский таймаут, промежуточный балансировщик, потеря связи или долго работающий backend. 502 говорит, что шлюз не получил пригодный ответ upstream: ветви включают connect refusal, reset, преждевременное закрытие, ошибочный протокол или заголовок. 503 может прийти от самого приложения, возникнуть при отсутствии доступной группы backend либо быть намеренным ответом локального правила. 504 связывают с ожиданием upstream, но ещё нужно установить фазу и участника, установившего предел. Ни один из кодов сам по себе не различает сеть, Nginx и payload. Я проверяю пару upstream_status и итогового status, текст error log, времена фаз и журнал приложения. Гипотезу об отказе backend отклоняю, если запрос с тем же ID был принят приложением и оно осмысленно вернуло ответ; гипотезу о DNS отклоняю только после подтверждения использованного адреса и успешного разрешения в нужный момент, а не по текущему результату команды спустя время.
  • Синтетическая цепочка отделяет транспорт от приложения: Проверку я раскладываю на DNS resolve → TCP connect → TLS handshake → HTTP transaction и выполняю из той точки, где работает Nginx, либо из заранее разрешённого диагностического сегмента с сопоставимым маршрутом. На DNS фиксирую имя, полученный адрес, используемый resolver и время наблюдения. На TCP проверяю установление соединения именно с выбранным адресом и портом. Для TLS сохраняю результат handshake, SNI, проверку цепочки и имени без вывода ключевого материала. Затем отправляю безопасный HTTP-запрос с корректными Host и маршрутом, записывая status, заголовок типа содержимого и ограниченный проверяемый признак тела. Успех каждого шага разрешает только соответствующую ветвь: рабочий DNS не обещает TCP, handshake не гарантирует HTTP, а HTTP 200 не доказывает правильность бизнес-payload. Синтетика также не воспроизводит пользовательскую сессию, права, размер тела, конкретные данные и путь через внешний балансировщик. Поэтому её результат я связываю с логами реального request ID, но не ставлю вместо них. Проверку прекращаю, если она может изменить данные или создать непредусмотренную нагрузку.
  • Health, payload, keepalive и таймауты относятся к разным вопросам: Доступный health endpoint подтверждает только заложенную проверку процесса или зависимостей. Он не гарантирует валидный ответ пользовательского маршрута. Я отдельно проверяю схему JSON, обязательные поля, типы, бизнес-инвариант или безопасный маркер; пустое тело и страница ошибки с кодом 200 нарушают payload. Тяжёлый health, напротив, может объявить backend нездоровым при рабочем маршруте. Keepalive — повторное использование соединений Nginx с конкретной группой upstream; устаревшие соединения, лимиты backend, балансировка и частота запросов меняют его пользу. Connect, send и read timeouts относятся к разным фазам, а пределы всех посредников образуют цепь. Я не переношу универсальные числа. Значения определяют профиль маршрута, сеть, допустимое время операции и политика повторов.
  • Как связываю Zabbix с пользовательской точкой наблюдения: В Zabbix HTTP agent я получаю служебный endpoint или ограниченный диагностический URL, сохраняю код, время и тело в пределах необходимого. Preprocessing извлекает конкретный JSONPath, регулярный признак либо числовое поле, проверяет тип и обрабатывает отсутствие значения явно; секреты не должны попадать в history. Web scenario полезен для последовательности шагов, cookies и проверки ожидаемой строки, когда важен пользовательский маршрут. Однако server или proxy Zabbix может находиться внутри сети и обходить публичный DNS, CDN, внешний балансировщик или межсетевой экран. Поэтому я документирую source, resolver, маршрут, Host/SNI, IP-версию и аутентификационный контекст агентской проверки, затем сравниваю их с реальной пользовательской точкой. Зелёная внутренняя проверка не опровергает внешний сетевой сбой. И наоборот, внешний отказ не доказывает болезнь backend, если внутренняя транзакция валидна. Триггер разделяет транспортный результат и проверку payload, а теги события связывают service, observation point и обезличенный route. Агрегаты stub_status остаются зависимыми элементами контекста, не причиной события.
  • Контролируемая проверка backend и условия возврата: Если собранных следов недостаточно и владелец сервиса разрешил воздействие, я рассматриваю контролируемый drain одного backend: сначала подтверждаю избыточность, актуальный способ исключения узла, исправность остальных экземпляров и доступность команды возврата. Фиксирую конфигурационную версию, активные соединения, распределение запросов, ошибки, payload-проверку и пользовательскую точку до изменения. Узел перестаёт получать новые запросы только предусмотренным для данной платформы способом; существующие соединения завершаются согласно её поведению. Во время окна я наблюдаю Nginx, Zabbix и приложение, не совмещая drain с релизом или сетевыми работами. Если ошибка следует за исключённым backend и возвращается вместе с ним при согласованной проверке, это усиливает адресную гипотезу, но требует лога приложения. Если симптом остаётся на других узлах, от версии об одном backend отказываюсь. Условия немедленного отката заранее определены: ухудшение доступности, рост ошибок, потеря запаса мощности, неверная маршрутизация или отсутствие наблюдаемости. Возврат выполняется штатным обратным действием с проверкой конфигурации и payload. Произвольный reload, массовый restart и очистка логов в такой проверке недопустимы.

Конфигурация и сбор

{"correlation_rule": "Событие связывается с service, route и observation point; внутренний и внешний результаты сравниваются только при документированном соответствии DNS, маршрута, Host/SNI и контекста запроса.", "item_design": ["transport status and phase", "HTTP response code", "transaction duration", "payload validity", "observation point identity"], "observation_entities": ["Nginx aggregate endpoint", "internal HTTP agent", "external web scenario", "application payload check"], "preprocessing_rule": "Preprocessing извлекает только ожидаемый признак, валидирует тип и различает пустой ответ, ошибку разбора и неверное значение; чувствительное тело отбрасывается до хранения."}

Контролируемая проверка и свидетельство

Любое воздействие допускается после read-only сбора доказательств и согласования с владельцем сервиса. Для backend drain обязательны исправные альтернативные экземпляры, подтверждённый запас, штатный механизм исключения и возврата, контроль конфигурационной версии, активных соединений, ошибок и payload. Наблюдение ведётся одновременно из агентской и пользовательской точек. При ухудшении доступности, росте отказов, потере запаса, неожиданном маршруте или разрыве телеметрии проверка прекращается и узел возвращается предусмотренным обратным действием. Таймауты, keepalive и retry не меняются одновременно с drain, чтобы сохранить различимость причины.

Фрагмент рабочего регламента

  • Зафиксировать пользовательскую точку, точное время, host, безопасный route и request ID.
  • Связать access, error и application logs, сохранив порядок и отредактировав секретные данные.
  • Сравнить request_time с упорядоченными upstream-полями и доказать, выбирался ли upstream.
  • Разложить проверку на DNS, TCP, TLS, HTTP и отдельно подтвердить валидность payload.
  • Сверить источник Zabbix HTTP agent или web scenario с реальным пользовательским маршрутом.
  • Проверить конфигурацию keepalive и таймаутов применительно к конкретному сервису и фазе.
  • До drain сохранить контрольную точку, предусловия, критерии остановки и штатный возврат.

Откат

Любое воздействие допускается после read-only сбора доказательств и согласования с владельцем сервиса. Для backend drain обязательны исправные альтернативные экземпляры, подтверждённый запас, штатный механизм исключения и возврата, контроль конфигурационной версии, активных соединений, ошибок и payload. Наблюдение ведётся одновременно из агентской и пользовательской точек. При ухудшении доступности, росте отказов, потере запаса, неожиданном маршруте или разрыве телеметрии проверка прекращается и узел возвращается предусмотренным обратным действием. Таймауты, keepalive и retry не меняются одновременно с drain, чтобы сохранить различимость причины.

Ограничения вывода

Набор переменных зависит от log_format и сборки Nginx, детализация error log — от уровня журналирования, а повторные upstream-попытки усложняют сопоставление. NAT, CDN, service mesh и балансировщики добавляют точки, часы и собственные пределы ожидания. Короткие ошибки могут исчезнуть из агрегатов, request ID может не пройти через все компоненты, а синтетический запрос не повторяет пользовательские данные. Поэтому вывод ограничен зафиксированным маршрутом и интервалом; параметры keepalive, таймаутов и порогов выбираются заново для конкретного приложения и сети.

Источники

  • NGINX: Module ngx_http_stub_status_module: https://nginx.org/en/docs/http/ngx_http_stub_status_module.html
  • NGINX: Module ngx_http_upstream_module: https://nginx.org/en/docs/http/ngx_http_upstream_module.html
  • NGINX: Module ngx_http_proxy_module: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
  • Zabbix: Web monitoring: https://www.zabbix.com/documentation/current/en/manual/web_monitoring
  • Zabbix: HTTP agent item: https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/http