Nginx: особое поведение при ошибках “Хост не найден” для upstream?

Вопрос или проблема

Когда 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 позволяет захватывать ошибки и перенаправлять их на кастомные страницы ошибок или обработчики, что мы и сделаем в нашем случае.

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

  1. Включаем proxy_intercept_errors
  2. Настраиваем страницы ошибок для 502 и других 5xx
  3. Добавляем обработчик для 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;  # Эти страницы не должны быть доступны напрямую
    }
}

Объяснение конфигурации

  1. Использование proxy_intercept_errors: Эта директива включает перехват ошибок на уровне прокси, позволяя вам управлять выходными данными, когда уровень 502 или 5xx ошибки возникает.

  2. Кастомный обработчик @handle_502: Здесь вы обработаете случай, когда возникает ошибка 502. Мы проверяем, можем ли разрешить имя хоста. Если имя не может быть разрешено, то вернется ошибка 404.

  3. Настройка resolver и таймауты: Для управления временем ожидания и кэширования DNS-ответов это минимизирует время лечения в случае, если службы ясных писем не существуют.

Заключение

Данный подход позволит вам дифференцировать ошибки на уровне Nginx, сигнализируя о том, что служба не найдена (404), вместо слишком общем и потенциально сбивающего с толку 502. Это сделает ваш мониторинг более точным и соответствующим, минимизировав шум от неверных имен хостов.

Важно также просмотреть журналы Nginx на случай других, менее предсказуемых ошибок и внести корректировки в логику обработки по мере необходимости.

Оцените материал
Добавить комментарий

Капча загружается...