k8s Ingress nginx错误页面的深度定制
错误页面是发生错误时显示的网页。 错误页面会警告用户发生的错误类型,并可能为用户提供解决问题的步骤的建议。 除了在未样式化的网页上提供错误信息的基本页面之外,还可以使用可以设计为具有额外功能和样式外观的自定义错误页面。 这些设置可以在服务器上更改。 许多服务器提供了可用于生成自定义错误页面的实用程序。
引文参考:https://www.netinbag.com/cn/internet/what-are-error-pages.html
1、错误页面状态码
网站运行过程中难免出现问题,为用户抛出一个错误页面,常见的错误页面包含403
、404
、500
、502
、503
、504
状态码,这些常见的错误页面状态码的含义如下
- 403 Forbidden
- 404 Not Found
- 500 Internal Server Eroor
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
2、在k8s中模拟错误页面
本文中涉及到的的k8s集群
版本、Ingress nginx
版本如下
# kubectl version
Client Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.0", GitCommit:"e8462b5b5dc2584fdcd18e6bcfe9f1e4d970a529", GitTreeState:"clean", BuildDate:"2019-06-19T16:40:16Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"15", GitVersion:"v1.15.0", GitCommit:"e8462b5b5dc2584fdcd18e6bcfe9f1e4d970a529", GitTreeState:"clean", BuildDate:"2019-06-19T16:32:14Z", GoVersion:"go1.12.5", Compiler:"gc", Platform:"linux/amd64"}
# POD_NAME=$(kubectl get pods -l app.kubernetes.io/name=ingress-nginx -n ingress-nginx -o jsonpath='{.items[0].metadata.name}')
# kubectl exec -it $POD_NAME -n ingress-nginx -- /nginx-ingress-controller --version
-------------------------------------------------------------------------------
NGINX Ingress controller
Release: 0.25.0
Build: git-1387f7b7e
Repository: https://github.com/kubernetes/ingress-nginx
-------------------------------------------------------------------------------
对于错误页面状态码,为了方便,这里模拟出404
和503
两个错误状态码页面
- 404页面
解析一个不存在的域名到Ingress controller
所在的节点,进行访问,页面如下
![](https://image.ssgeek.com/20201217-01.png)
这里对Ingress nginx
做了版本号的隐藏,返回了默认的404 Not Found
(页面未找到)
- 503页面
在k8s
中创建一个如下的Ingress
资源
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: example.bar.com
http:
paths:
- backend:
serviceName: nginx-service
servicePort: 80
同样将对应的域名解析到Ingress controller
所在的节点进行访问,由于该Ingress
的后端并没有对应的nginx-service
,因此会返回默认的503
(服务暂时不可用)
![](https://image.ssgeek.com/20201217-02.png)
3、默认后端错误页面
很多时候我们虽然隐藏了Ingress nginx
的版本号,但直接返回状态码还是不够友好。一些网站都会有自定义的较友好、美观的错误页面或跳转到公益页面等。
如何定制错误页面?在网址的域名dns
被正确解析而不是未注册或被劫持的情况下,简单来说可以根据网络访问链路分为以下两种情况:
- 域名通过
CNAME
解析到cdn
如果网站前面用到了类似阿里云提供的CDN
加速、全站加速等服务,域名通过CNAME
解析到CDN
,CDN
再配置关联的域名。这种情况下错误页面的定义都可以直接在CDN
控制台进行配置。如下图所示,指定状态码对应的页面即可。
![](https://image.ssgeek.com/20201217-03.png)
- 域名通过
A
记录解析到LB
或者真实服务器
如果网站域名通过A
记录解析到LB
或者真实服务器,而LB
或者真实服务器不做任何处理,那么将返回上面所示的错误状态码页面。对于k8s
中通过Ingress nginx
暴露的服务来说,可以在Ingress-controller
配置默认后端错误页面。
可以参照官方的文档说明,配置流程如下。
3.1 部署默认后端
Ingress nginx
提供了默认的自定义后端供用户使用,yaml
如下
---
apiVersion: v1
kind: Service
metadata:
name: nginx-errors
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
selector:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
ports:
- port: 80
targetPort: 8080
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-errors
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: nginx-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
containers:
- name: nginx-error-server
image: quay.io/kubernetes-ingress-controller/custom-error-pages-amd64:0.3
ports:
- containerPort: 8080
# Setting the environment variable DEBUG we can see the headers sent
# by the ingress controller to the backend in the client response.
# env:
# - name: DEBUG
# value: "true"
保证镜像可用的情况下,直接创建对应资源即可
# kubectl create -f custom-default-backend.yaml
service "nginx-errors" created
deployment.apps "nginx-errors" created
检查创建的资源
# kubectl get deploy,svc
NAME DESIRED CURRENT READY AGE
deployment.apps/nginx-errors 1 1 1 10s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-errors ClusterIP 10.0.0.12 <none> 80/TCP 10s
3.2 配置启动参数
修改Ingress controller
控制器的启动参数,加入以下配置,通过--default-backend
标志的值设置为新创建的错误后端的名称
# kubectl -n ingress-nginx edit ds nginx-ingress-controller
...
spec:
containers:
- args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
- --default-backend-service=ingress-nginx/nginx-errors # 添加此行
...
3.3 修改configmap
修改对应的configmap
指定要关联到默认后端服务的服务状态码,意味着如果状态码是配置项中的值,那么返回给客户端浏览器的就是默认后端服务
# kubectl -n ingress-nginx edit configmap nginx-configuration
apiVersion: v1
data:
custom-http-errors: 403,404,500,502,503,504 # 添加此行
3.4 测试
通过终端命令访问上面404
和503
页面的两个域名
# ingress-nginx curl example.bar.com
5xx html # ingress-nginx curl example.foo.com
<span>The page you're looking for could not be found.</span>
# 自定义Accept标头
# ingress-nginx curl -H 'Accept: application/json' example.foo.com
{ "message": "The page you're looking for could not be found" }
可以看到默认后端将404
状态码返回了字符串,503
返回了5xx html
的字符串。缺点在于这样的情况如果用浏览器进行访问,仅仅是一个字符串文本甚至无法正常显示,因此需要重新定义这个默认后端服务,提供友好的界面返回。
4、自定义错误页面
4.1 剖析请求与关键
如下图所示,Ingress Controller
控制器的工作原理,简单来说,将控制器理解为一个监听器,通过不断地监听 kube-apiserver
,实时的感知后端 Service
和Pod
的变化,当得到这些信息变化后,Ingress Controller
再结合Ingress
的配置,更新反向代理负载均衡器,从而达到服务发现的作用。Ingress-nginx
的最终目标是构造nginx.conf
这样的配置文件,主要用途是在配置文件有任何变更后都需要重新加载 nginx
。
通过上面创建ingress
资源,以及配置控制器启动参数和configmap
,进入到nginx-ingress-controller
的pod
中查看配置(文件内容很多,可以导出或过滤查看)。
会看到将状态码关联了自定义的默认后端
# kubectl -n ingress-nginx exec -it nginx-ingress-controller-2rrsw bash
www-data@k8s-qa-node-03:/etc/nginx$ grep "error_page" nginx.conf -C 10
ssl_ecdh_curve auto;
proxy_intercept_errors on;
error_page 404 = @custom_upstream-default-backend_404;
error_page 500 = @custom_upstream-default-backend_500;
error_page 502 = @custom_upstream-default-backend_502;
error_page 503 = @custom_upstream-default-backend_503;
error_page 504 = @custom_upstream-default-backend_504;
proxy_ssl_session_reuse on;
upstream upstream_balancer {
server 0.0.0.1; # placeholder
过滤出上面创建的域名example.bar.com
相关配置
## start server example.bar.com
server {
server_name example.bar.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
set $pass_access_scheme $scheme;
set $pass_server_port $server_port;
set $best_http_host $http_host;
set $pass_port $pass_server_port;
...
location @custom_upstream-default-backend_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 X-Request-ID $req_id;
proxy_set_header Host $best_http_host;
set $proxy_upstream_name upstream-default-backend;
rewrite (.*) / break;
proxy_pass http://upstream_balancer;
log_by_lua_block {
monitor.call()
}
}
location @custom_upstream-default-backend_500 {
internal;
proxy_intercept_errors off;
proxy_set_header X-Code 500;
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 X-Request-ID $req_id;
proxy_set_header Host $best_http_host;
set $proxy_upstream_name upstream-default-backend;
rewrite (.*) / break;
proxy_pass http://upstream_balancer;
log_by_lua_block {
monitor.call()
}
}
...
这个server
中关于默认后端的配置内容是关键信息(踩坑发现,后面只有用到这里的相关配置才能达到最终目标,否则无法判断)。
可以看到,在传递默认后端时,设置了多个请求头字段,其中X-Code
即状态码正是所需要的,这里意味着将控制器返回的对应状态码,例如500
定义在了X-Code中
。如果自定义一个默认后端来取代官方的默认后端,就可以通过X-Code
这个特定的头部来判断实现不同的状态码从而返回不同的自定义错误页面。
关于X-code
早期的版本可能会不生效,issue参考
4.2 构建自定义后端
自定义后端页面可以理解成就是简单的静态页面,这里可以通过熟悉的nginx
来构建这样的自定义后端。即通过手动编译安装nginx
,并打包好自定义错误页面、配置文件成一个docker
镜像。
镜像中nginx.conf
的关键配置
-
利用上面提到的
X-code
特定头部进行原始状态码的判断。 -
nginx
不支持嵌套的if
判断以及逻辑运算,因此通过设置flag
变量标记的形式实现不同状态码的判断返回,如果列出的状态码都不匹配,将状态码设置为返回404
。
server {
listen 80;
...
root /data/www/error;
error_page 403 /403.html;
error_page 404 /404.html;
error_page 500 /500.html;
error_page 502 /502.html;
error_page 503 /503.html;
error_page 504 /504.html;
location = / {
set $flag 404;
if ($http_x_code = "403"){set $flag 403;}
if ($http_x_code = "404"){set $flag 404;}
if ($http_x_code = "500"){set $flag 500;}
if ($http_x_code = "502"){set $flag 502;}
if ($http_x_code = "503"){set $flag 503;}
if ($http_x_code = "504"){set $flag 504;}
if ($flag = "403"){return 403;}
if ($flag = "404"){return 404;}
if ($flag = "500"){return 500;}
if ($flag = "502"){return 502;}
if ($flag = "503"){return 503;}
if ($flag = "504"){return 504;}
}
location = /403.html {
internal;
}
location = /404.html {
internal;
}
...
代码根目录结构
[root@docker nginx_error]# tree error/
error/
├── 403.html
├── 404.html
├── 500.html
├── 502.html
├── 503.html
└── 504.html
这里我已经将制作好的镜像上传到了dockerhub
,可以通过以下命令拉取镜像
docker pull ssgeek/nginx:nginx_error_1.14.2_v1.0
4.3 部署自定义后端
参照已有模板,重新部署一个新的默认后端
---
apiVersion: v1
kind: Service
metadata:
name: ssgeek-errors
labels:
app.kubernetes.io/name: ssgeek-errors
app.kubernetes.io/part-of: ingress-nginx
namespace: ingress-nginx
spec:
selector:
app.kubernetes.io/name: ssgeek-errors
app.kubernetes.io/part-of: ingress-nginx
ports:
- port: 80
targetPort: 80
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ssgeek-errors
labels:
app.kubernetes.io/name: ssgeek-errors
app.kubernetes.io/part-of: ingress-nginx
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ssgeek-errors
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ssgeek-errors
app.kubernetes.io/part-of: ingress-nginx
spec:
containers:
- name: ssgeek-errors
image: ssgeek/nginx:nginx_error_1.14.2_v1.0
ports:
- containerPort: 80
# Setting the environment variable DEBUG we can see the headers sent
# by the ingress controller to the backend in the client response.
# env:
# - name: DEBUG
# value: "true"
同样的,修改Ingress controller
控制器的启动参数,修改关联的service
名称
# kubectl -n ingress-nginx edit ds nginx-ingress-controller
...
spec:
containers:
- args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
- --default-backend-service=ingress-nginx/ssgeek-errors # 修改成自定义的默认后端服务
...
4.4 最终测试
测试效果如下
到这里,基于k8s Ingress nginx
对错误页面的深度定制就完成了。