Kubernetes tips & tricks: персоналізовані сторінки помилок у NGINX Ingress

Kubernetes tips & tricks: персоналізовані сторінки помилок у NGINX Ingress

У цій статті я хочу розповісти про дві можливості NGINX Ingress, пов'язані з відображенням персоналізованих сторінок з помилками, а також про існуючі в них обмеження та способи їх обійти.

1. Зміна бекенду за умовчанням

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

Kubernetes tips & tricks: персоналізовані сторінки помилок у NGINX Ingress

Однак все частіше наші клієнти приходять із проханням замість стандартного 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

Інша ситуація - запити до додатку, в якому не обробляються такі ситуації (не генеруються відповідні красиві сторінки). Це може бути також викликане бажанням розробників віддавати однакові сторінки помилок у багатьох програмах.

Для реалізації даного кейсу на серверній стороні нам потрібно:

  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'ів та серверів.

PS

Інше з циклу K8s tips & tricks:

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук