
Kubernetes tips & tricks: personalized error pages in NGINX Ingress

In this article I want to talk about two features of NGINX Ingress related to displaying personalized error pages, as well as the limitations that exist in them and ways to get around them.
1. Changing the default backend
By default, NGINX Ingress uses the default backend, which performs the corresponding function. This means that when we request Ingress with a host that is not in the Ingress resources, we get a page with a response code of 404:

However, more and more often our customers come up with a request instead of the standard 404 to show their page with a company logo and other amenities. For this, NGINX Ingress has a built-in ability to override
default-backend-service
. The option of the same name as an argument is passed a format record namespace/servicename
. The port for the service should be 80. To do this, you need to create your own pod (deployment) and a service with your application ( an example implementation in YAML from the ingress-nginx repository), which will be given instead of the default backend.
Here is a small illustration:
~$ 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.
Thus, all domains that are not explicitly created through YAML c
kind: Ingress
fall into the default-backend. In the listing above, it has become such a domain sadsdasdas
.2. Processing HTTP errors in the application using the default backend
Another situation is requests to the application that end up with HTTP errors (404, 500, 502 ...), in which such situations are not processed (corresponding beautiful pages are not generated). It can also be caused by the desire of developers to give the same error pages in many applications.
To implement this case on the server side, we need:
- Follow the instructions above from the item about default backend;
- Add a key to the configuration ConfigMap nginx-ingress
custom-http-errors
, for example, with a value404,503
(obviously, it corresponds to error codes covered by the new rule).
The expected result is achieved: when the client application is running and receiving an error with a response code of 404 or 503, the request will be automatically redirected to the new default backend ...
However, when developing the application for default backend and custom-http-errors, an important feature must be taken into account:
!!! 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.
The fact is that when redirecting a request, the headers will contain useful information with the previous response code and additional information (a complete list of them is available here ).
This means that you yourself must take care of the correct response code . Here is an example from the documentation of how this works.
To different applications - different default backend
So that the solution is not global for the entire cluster, but applies only to specific applications, first you need to check the version of Ingress. If it matches 0.23 or higher , use the Ingress native annotations:
- We can redefine
default-backend
for each Ingress'a by means of the annotation ; - We can redefine
custom-http-errors
for each Ingress'a by means of the annotation .
As a result, the Ingress resource will look something like this:
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
In this case, errors 404 and 502 will be redirected to the error-pages service with all the necessary headers.
In previous versions of Ingress, this was not possible (the fateful commit at 0.23 ). And if you have 2 completely different applications running in your cluster and you want to specify different default-backend-service and handling different error codes for each of them - you will have to use workarounds for this, of which we have two.
Ingress <0.23: approach one
This option is simpler. As an application that gives its pages, we have regular HTML, which does not know how to look at the headers and give the correct response codes. Such an application is rolled out with Ingress from the url
/error-pages
, and ws
the HTML will be given in the directory . Illustration in 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
The service for this deployment must be of type ClusterIP.
At the same time, in the application where we will handle the error, in Ingress we add server-snippet or configuration-snippet with the following contents:
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: approach two
An option for an application that can handle headers ... Anyway, this is a more correct path, borrowed from custom-http-errors. Using it manually (copying) will allow you to not change the global settings.
The steps are as follows. We create the same deployment with an application that can listen to the necessary headers and respond correctly. Add server-snippet applications to Ingress with the following contents:
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;
}
As you can see, for each error that we want to handle, you need to make your location, where all the necessary headers will be substituted, as in the "native" custom-error-pages . So we can create different personalized pages with errors even for individual locations and servers.
PS
Other from the K8s tips & tricks cycle:
- “ Transfer of resources working in a cluster to Helm 2 management ”;
- “ On the allocation of nodes and the load on the web application ”;
- " Access to dev-sites ";
- “ Speeding up the bootstrap of large databases. ”
Read also in our blog: