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
The page you're looking for could not be found.
Таким образом, все домены, которые явно не созданы через YAML с kind: Ingress
, попадают в default-backend. В листинге выше таким доменом стал sadsdasdas
.
2. Обработка HTTP-ошибок в приложении силами default backend
Другая ситуация — заканчивающиеся HTTP-ошибками (404, 500, 502…) запросы к приложению, в котором не обрабатываются такие ситуации (не генерируются соответствующие красивые страницы). Это может быть также вызвано желанием разработчиков отдавать одинаковые страницы ошибок во множестве приложений.
Для реализации данного кейса на серверной стороне нам необходимо:
- Выполнить инструкцию выше из пункта про default backend;
- В конфигурационный 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:
- Мы можем переопределить
default-backend
для каждого Ingress’а с помощью аннотации; - Мы можем переопределить
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:
Читайте также в нашем блоге: