定制ingress-nginx后端
深入ingress-nginx
1. 使用deployment 是否合适 2. 四层代理 3. 定制配置(超时, buffersize 设置) 4. https服务 5. 访问控制(session保持,支不支持AB测试)
错误页面是发生错误时显示的网页。错误页面会警告用户发生的错误类型,并可能为用户提供解决问题的步骤的建议。除了在未样式化的网页上提供错误信息的基本页面之外,
还可以使用可以设计为具有额外功能和样式外观的自定义错误页面。这些设置可以在服务器上更改。许多服务器提供了可用于生成自定义错误页面的实用程序。
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
版本如下
bash-5.1$ ./nginx-ingress-controller --version ------------------------------------------------------------------------------- NGINX Ingress controller Release: v1.2.0 Build: a2514768cd282c41f39ab06bda17efefc4bd233a Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.10
对于错误页面状态码,为了方便,这里模拟出404
和503
两个错误状态码页面
-
404页面
- 503页面
在k8s
中创建一个如下的Ingress
资源
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-nginx annotations: spec: ingressClassName: nginx rules: - host: "example.abc.com" http: paths: - pathType: Prefix path: "/" backend: service: name: nginx-service port: number: 80
同样将对应的域名解析到Ingress controller
所在的节点进行访问,由于该Ingress
的后端并没有对应的nginx-service
,因此会返回默认的503
(服务暂时不可用)
这里对Ingress nginx
做了版本号的隐藏,返回了默认的404 Not Found
(页面未找到)
3、默认后端错误页面
很多时候我们虽然隐藏了Ingress nginx
的版本号,但直接返回状态码还是不够友好。一些网站都会有自定义的较友好、美观的错误页面或跳转到公益页面等。
3.1 部署默认后端
Ingress nginx
提供了默认的自定义后端供用户使用,yaml
如下
apiVersion: v1 kind: Service metadata: name: nginx-errors
namespace: ingress-nginx 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
namespace: ingress-nginx 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 -n ingress-nginx 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" }
页面测试
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
。
root@a93f3a423b80 conf]# cat nginx.conf user www; worker_processes 4; worker_rlimit_nofile 65535; pid /var/run/nginx.pid; events { use epoll; worker_connections 4096; multi_accept on; } http { server_tokens off; include mime.types; default_type application/octet-stream; add_header X-Frame-Options SAMEORIGIN; add_header Set-Cookie "HttpOnly"; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; log_format proxy_log '$http_x_real_ip - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; server_names_hash_bucket_size 128; client_header_buffer_size 32k; large_client_header_buffers 4 32k; client_max_body_size 500m; keepalive_timeout 3600; sendfile on; tcp_nopush on; tcp_nodelay on; # FastCGI Configure fastcgi_connect_timeout 3600; fastcgi_send_timeout 3600; fastcgi_read_timeout 3600; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; # gzip compression configure gzip on; gzip_http_version 1.0; gzip_comp_level 2; gzip_proxied any; gzip_min_length 1k; gzip_buffers 4 16k; gzip_types text/plain application/x-javascript text/css application/xml; gzip_vary on; include fastcgi.conf; server { listen 80; server_name localhost; index index.html index.htm; access_log /var/log/nginx_access.log main; error_log /var/log/nginx_error.log; 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; } location = /500.html { internal; } location = /502.html { internal; } location = /503.html { internal; } location = /504.html { internal; } } }
代码根目录结构
[root@docker nginx_error]# tree error/ error/ ├── 403.html ├── 404.html ├── 500.html ├── 502.html ├── 503.html └── 504.html
docker pull 10.20.1.186/test/nginx:nginx_error_1.14.2_v1.0
4.3 部署自定义后端
参照已有模板,重新部署一个新的默认后端
--- apiVersion: v1 kind: Service metadata: name: ssgeek-errors
namespace: ingress-nginx 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
namespace: ingress-nginx 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: 10.20.1.186/test/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
同样的,修改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 # 修改成自定义的默认后端服务