company_banner

Kubernetes tips & tricks: персонализированные страницы ошибок в NGINX Ingress



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

    1. Изменение бэкенда по умолчанию


    По умолчанию в NGINX Ingress используется default backend, который выполняет соответствующую функцию. Это означает, что при запросе Ingress'а с указанием хоста, которого нет в Ingress-ресурсах, мы получаем такую страницу с кодом ответа 404:



    Однако все чаще наши клиенты приходят с просьбой вместо стандартного 404 показать свою страницу с фирменным логотипом и прочими удобствами. Для этого у NGINX Ingress есть встроенная возможность переопределить default-backend-service. Одноимённой опции в качестве аргумента передаем запись формата namespace/servicename. Порт у сервиса должен быть 80.

    Для этого необходимо создать свой pod (deployment) и сервис с вашим приложением (пример реализации в YAML из репозитория ingress-nginx), которое будет отдаваться вместо default backend.

    Вот небольшая иллюстрация:

    ~$ curl -i -XGET http://sadsdasdas.kube-cloud.my/
    HTTP/1.1 404 Not Found
    Date: Mon, 11 Mar 2019 05:38:15 GMT
    Content-Type: */*
    Transfer-Encoding: chunked
    Connection: keep-alive
    
    <span>The page you're looking for could not be found.</span>

    Таким образом, все домены, которые явно не созданы через YAML с kind: Ingress, попадают в default-backend. В листинге выше таким доменом стал sadsdasdas.

    2. Обработка HTTP-ошибок в приложении силами default backend


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

    Для реализации данного кейса на серверной стороне нам необходимо:

    1. Выполнить инструкцию выше из пункта про default backend;
    2. В конфигурационный ConfigMap nginx-ingress добавить ключ custom-http-errors, например, со значением 404,503 (очевидно, соответствует кодам ошибки, на которые распространяется новое правило).

    Ожидаемый результат достигнут: при работе клиентского приложения и получении ошибки с кодом ответа 404 или 503 запрос будет автоматически перенаправлен в новый default backend…

    Однако при разработке приложения для default backend и custom-http-errors нужно учесть важную особенность:

    !!! Important The custom backend is expected to return the correct HTTP status code instead of 200. NGINX does not change the response from the custom default backend.

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

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

    Разным приложениям — разный default backend


    Чтобы решение не было глобальным на весь кластер, а относилось только к конкретным приложениям, для начала надо проверить версию Ingress. Если она соответствует 0.23 или выше, воспользуйтесь «родными» аннотациями Ingress:

    1. Мы можем переопределить default-backend для каждого Ingress'а с помощью аннотации;
    2. Мы можем переопределить custom-http-errors для каждого Ingress'а с помощью аннотации.

    В результате, ресурс Ingress будет выглядеть примерно так:

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: {{ .Chart.Name }}-app2
      annotations:
        kubernetes.io/ingress.class: "nginx"
        nginx.ingress.kubernetes.io/custom-http-errors: "404,502"
        nginx.ingress.kubernetes.io/default-backend: error-pages
    spec:
      tls:
      - hosts:
        - app2.example.com
        secretName: wildcard-tls
      rules:
      - host: app2.example.com
        http:
          paths:
          - path: /
            backend:
              serviceName: {{ .Chart.Name }}-app2
              servicePort: 80

    В таком случае ошибки 404 и 502 будут перенаправлены в сервис error-pages со всеми нужными заголовками.

    В предыдущих версиях Ingress такой возможности не было (судьбоносный коммит в 0.23). И если у вас в кластере работает 2 совершенно разных приложения и вы хотите для каждого из них указывать разные default-backend-service и обработку различных кодов ошибок — для этого придётся воспользоваться workaround'ами, коих у нас два.

    Ingress < 0.23: подход первый


    Этот вариант более прост. В качестве приложения, которое отдает свои страницы, у нас обычный HTML, который не умеет смотреть на заголовки и отдавать корректные коды ответа. Такое приложение выкатывается с Ingress'ом с url /error-pages, а в каталоге ws будет лежать отдаваемый HTML.

    Иллюстрация в YAML:

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: {{ .Chart.Name }}-app2
      annotations:
        kubernetes.io/ingress.class: "nginx"
        ingress.kubernetes.io/server-snippet: |
          proxy_intercept_errors on;
          error_page 500 501 502 503 504 @error_pages;
          location @error_pages {
            rewrite ^ /error-pages/other/index.html break;
            proxy_pass http://error-pages.prod.svc.cluster.local;
          }
    spec:
      tls:
      - hosts:
        - app2.example.com
        secretName: wildcard-tls
      rules:
      - host: app2.example.com
        http:
          paths:
          - path: /
            backend:
              serviceName: {{ .Chart.Name }}-app2
              servicePort: 80

    Сервис для этого деплоя должен быть с типом ClusterIP.

    При этом в приложении, где будем обрабатывать ошибку, в Ingress'е добавляем server-snippet или configuration-snippet со следующим содержимым:

    nginx.ingress.kubernetes.io    /server-snippet: |
          proxy_intercept_errors on;
          error_page 500 501 502 503 504 @error_pages;
          location @error_pages {
            rewrite ^ /error-pages/ws/index.html break;
            proxy_pass http://error-pages.prod.svc.cluster.local;
          }

    Ingress < 0.23: подход второй


    Вариант для приложения, которое умеет обрабатывать заголовки… Да и вообще это более корректный путь, заимствованный из custom-http-errors. Его использование вручную (копирование) позволит не изменять глобальные настройки.

    Шаги следующие. Создаем такой же deployment с приложением, которое умеет слушать нужные заголовки и отвечать корректно. Добавляем в Ingress приложения server-snippet со следующим содержимым:

    nginx.ingress.kubernetes.io    /server-snippet: |
          proxy_intercept_errors off;
          error_page 404 = @custom_404;
          error_page 503 = @custom_503;
          location @custom_404 {
            internal;
            proxy_intercept_errors off;
            proxy_set_header       X-Code             404;
            proxy_set_header       X-Format           $http_accept;
            proxy_set_header       X-Original-URI     $request_uri;
            proxy_set_header       X-Namespace        $namespace;
            proxy_set_header       X-Ingress-Name     $ingress_name;
            proxy_set_header       X-Service-Name     $service_name;
            proxy_set_header       X-Service-Port     $service_port;
            proxy_set_header       Host               $best_http_host;
            rewrite ^ /error-pages/ws/index.html break;
            proxy_pass http://error-pages.prod.svc.cluster.local;
          }
          location @custom_503 {
            internal;
            proxy_intercept_errors off;
            proxy_set_header       X-Code             503;
            proxy_set_header       X-Format           $http_accept;
            proxy_set_header       X-Original-URI     $request_uri;
            proxy_set_header       X-Namespace        $namespace;
            proxy_set_header       X-Ingress-Name     $ingress_name;
            proxy_set_header       X-Service-Name     $service_name;
            proxy_set_header       X-Service-Port     $service_port;
            proxy_set_header       Host               $best_http_host;
            rewrite ^ /error-pages/ws/index.html break;
            proxy_pass http://error-pages.prod.svc.cluster.local;
          }

    Как видно, для каждой ошибки, которую мы хотим обрабатывать, нужно сделать свой location, где будут подставляться все необходимые заголовки, как в «родном» custom-error-pages. Так мы можем создавать разные персонализированные страницы с ошибками даже для отдельных location'ов и серверов.

    P.S.


    Другое из цикла K8s tips & tricks:


    Читайте также в нашем блоге:

    Флант
    275,00
    Специалисты по DevOps и Kubernetes
    Поддержать автора
    Поделиться публикацией

    Комментарии 0

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

    Самое читаемое