Вопрос или проблема
Когда proxy_pass
в nginx возвращает 502, может быть множество причин. Я хочу иметь возможность обнаруживать, когда 502 был возвращен, потому что upstream хост не найден (то есть не удалось разрешить).
Я знаю о proxy_intercept_errors
, но это, похоже, не помогает в моем случае.
Что у меня есть
У меня есть сервер шлюза nginx, работающий в контейнере Kubernetes. Он настроен на маршрутизацию запросов к соответствующим службам Kubernetes в зависимости от первой части имени хоста (слово перед первой точкой, например, service-name.example.com
должно маршрутизироваться к службе с именем service-name
).
Вот упрощенный раздел конфигурации, отвечающий за эту логику:
server {
listen 80;
resolver 172.16.2.3; // IP адрес Pod
server_name "~^(?<svc>[\w-]+)\.";
location / {
// Каждая служба Kubernetes имеет внутреннее доменное имя, соответствующее следующему шаблону
proxy_pass "http://$svc.default.svc.cluster.local";
proxy_set_header Host $host;
// Проксирование заголовков `X-Forwarded`, отправленных ELB: http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/x-forwarded-headers.html
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
}
Проблема
Не важно, почему upstream недоступен (если он отказывается принимать соединения, внутренне сбоит или просто не существует), nginx возвращает 502. Единственное место, где можно увидеть реальную причину – это ошибка в журнале nginx.
Поскольку шлюз доступен публично через AWS ELB, к нему часто обращаются по IP или по случайным именам, что создает шум в мониторингах, настроенных на реакцию на всплески ошибок 5XX.
Что я хочу сделать
Настроить nginx так, чтобы он возвращал менее агрессивную ошибку (например, 404) в случае, если имя хоста службы не может быть разрешено разрешателем Kubernetes.
Например, я отправляю следующий запрос:
curl -H "Host: non-existent-service.example.com" http://gateway.example.com
Я хочу, чтобы nginx мог обнаружить тот факт, что имя хоста, соответствующее службе, не может быть разрешено внутри, и затем вернуть 404 вместо 502.
В настоящее время журналы выглядят следующим образом:
-
журнал ошибок:
2017/11/10 16:03:58 [error] 22#22: *482894 non-existent-service.default.svc.cluster.local не удалось разрешить (3: Хост не найден), клиент: 172.16.1.2, сервер: ~^(?<svc>[\w-]+)\., запрос: "GET / HTTP/1.1", хост: "non-existent-service.example.com"
-
журнал доступа:
172.16.1.2 - - [10/Nov/2017:16:03:58 +0000] "non-existent-service.example.com" "GET / HTTP/1.1" 502 173 "-" "curl/7.43.0" "194.126.122.250" "EE"
ОБНОВЛЕНИЕ
Следовало упомянуть об этом в первую очередь. “Catch-all” блок сервера по умолчанию был первым, что я попробовал. Оказалось, что этот блок никогда не достигает, потому что практически любое имя хоста соответствует regexp.
Просто повторно включите виртуальный хост по умолчанию и игнорируйте все, что в него попадает (поскольку такие запросы запрашивают IP напрямую или являются злонамеренными).
Например, как видно в nginx 1.12.x nginx.conf
:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
// Загружаю файлы конфигурации для блока сервера по умолчанию.
include /etc/nginx/default.d/*.conf;
location / {
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
Я знаю о proxy_intercept_errors, но это, похоже, не помогает в моем случае.
Правда? Именно с этого я бы и начал – к сожалению, вы не объяснили, почему вы не нашли это полезным.
Я это не тестировал, но я бы начал с чего-то вроде:
server {
listen 80;
resolver 172.16.2.3; // IP адрес Pod
server_name "~^(?<svc>[\w-]+)\.";
location / {
proxy_intercept_errors on;
error_page 404 /.rproxy/404/;
error_page 502 /.rproxy/502/;
error_page 500 503 504 /proxy/5xx/;
// Каждая служба Kubernetes имеет внутреннее доменное имя, соответствующее следующему шаблону
proxy_pass "http://$svc.default.svc.cluster.local";
proxy_set_header Host $host;
// Проксирование заголовков `X-Forwarded`, отправленных ELB: http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/x-forwarded-headers.html
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
location /.rproxy/ {
// и я бы реализовал это как, возможно, обработчик PHP
}
}
(С другой стороны, я бы никогда не делал это: server_name "~^(?<svc>[\w-]+)\.";...proxy_pass "http://$svc.default.svc.cluster.local"
)
Ответ или решение
Чтобы решить проблему с ошибками 502, возникающими из-за отсутствия разрешения имен у upstream-сервисов в Nginx, предоставлю вам детальный подход для реализаций, используя возможности конфигурации Nginx и обработки ошибок.
Контекст и Проблема
Вы настроили Nginx в качестве шлюза для маршрутизации запросов к соответствующим службам Kubernetes, используя первую часть имени хоста. Однако, когда запрос отправляется к несуществующей службе, Nginx возвращает ошибку 502. Это происходит, когда Nginx не может разрешить имя хоста, но эта причина неявно скрыта под общей ошибкой 502, что создает шум в ваших системах мониторинга.
Решение через proxy_intercept_errors
Использование директивы proxy_intercept_errors
позволяет захватывать ошибки и перенаправлять их на кастомные страницы ошибок или обработчики, что мы и сделаем в нашем случае.
Конфигурация
- Включаем
proxy_intercept_errors
- Настраиваем страницы ошибок для 502 и других 5xx
- Добавляем обработчик для 404 на случай, когда служба не может быть разрешена
Пример возможной конфигурации может выглядеть следующим образом:
server {
listen 80;
resolver 172.16.2.3; # IP-адрес пода с разрешением
server_name "~^(?P<svc>[\w-]+)\.";
location / {
proxy_intercept_errors on;
error_page 502 = @handle_502; # Перенаправление на кастомный блок при 502
error_page 500 503 504 /error_pages/50x.html; # Стандартные страницы ошибок
proxy_pass "http://$svc.default.svc.cluster.local";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
location @handle_502 {
# Проверка на случай, если сервис действительно недоступен
internal; # Внутренний обработчик
set $upstream_host "$svc.default.svc.cluster.local";
resolver 172.16.2.3 valid=10s; # Установите время проверки кеша для DNS
resolver_timeout 5s; # Таймаут на разрешение
# Попытка разрешить хост
if ($upstream_host = "") {
return 404; # Возврат 404 если хост не был найден
}
# Редирект к фактическому upstream
return 502; # В противном случае возвращаем 502
}
location /error_pages/ {
root /usr/share/nginx/html; # Папка с вашими страницами ошибок
internal; # Эти страницы не должны быть доступны напрямую
}
}
Объяснение конфигурации
-
Использование
proxy_intercept_errors
: Эта директива включает перехват ошибок на уровне прокси, позволяя вам управлять выходными данными, когда уровень 502 или 5xx ошибки возникает. -
Кастомный обработчик
@handle_502
: Здесь вы обработаете случай, когда возникает ошибка 502. Мы проверяем, можем ли разрешить имя хоста. Если имя не может быть разрешено, то вернется ошибка 404. -
Настройка
resolver
и таймауты: Для управления временем ожидания и кэширования DNS-ответов это минимизирует время лечения в случае, если службы ясных писем не существуют.
Заключение
Данный подход позволит вам дифференцировать ошибки на уровне Nginx, сигнализируя о том, что служба не найдена (404), вместо слишком общем и потенциально сбивающего с толку 502. Это сделает ваш мониторинг более точным и соответствующим, минимизировав шум от неверных имен хостов.
Важно также просмотреть журналы Nginx на случай других, менее предсказуемых ошибок и внести корректировки в логику обработки по мере необходимости.