k8s Ingress nginx錯誤頁面的深度定制


錯誤頁面是發生錯誤時顯示的網頁。 錯誤頁面會警告用戶發生的錯誤類型,並可能為用戶提供解決問題的步驟的建議。 除了在未樣式化的網頁上提供錯誤信息的基本頁面之外,還可以使用可以設計為具有額外功能和樣式外觀的自定義錯誤頁面。 這些設置可以在服務器上更改。 許多服務器提供了可用於生成自定義錯誤頁面的實用程序。

引文參考:https://www.netinbag.com/cn/internet/what-are-error-pages.html

1、錯誤頁面狀態碼

網站運行過程中難免出現問題,為用戶拋出一個錯誤頁面,常見的錯誤頁面包含403404500502503504狀態碼,這些常見的錯誤頁面狀態碼的含義如下

  • 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
-------------------------------------------------------------------------------

對於錯誤頁面狀態碼,為了方便,這里模擬出404503兩個錯誤狀態碼頁面

  • 404頁面

解析一個不存在的域名到Ingress controller所在的節點,進行訪問,頁面如下

這里對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 (服務暫時不可用)

3、默認后端錯誤頁面

很多時候我們雖然隱藏了Ingress nginx的版本號,但直接返回狀態碼還是不夠友好。一些網站都會有自定義的較友好、美觀的錯誤頁面或跳轉到公益頁面等。

如何定制錯誤頁面?在網址的域名dns被正確解析而不是未注冊或被劫持的情況下,簡單來說可以根據網絡訪問鏈路分為以下兩種情況:

  • 域名通過CNAME解析到cdn

如果網站前面用到了類似阿里雲提供的CDN加速、全站加速等服務,域名通過CNAME解析到CDNCDN再配置關聯的域名。這種情況下錯誤頁面的定義都可以直接在CDN控制台進行配置。如下圖所示,指定狀態碼對應的頁面即可。

  • 域名通過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 測試

通過終端命令訪問上面404503頁面的兩個域名

#  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,實時的感知后端 ServicePod的變化,當得到這些信息變化后,Ingress Controller再結合Ingress的配置,更新反向代理負載均衡器,從而達到服務發現的作用。Ingress-nginx的最終目標是構造nginx.conf這樣的配置文件,主要用途是在配置文件有任何變更后都需要重新加載 nginx

通過上面創建ingress資源,以及配置控制器啟動參數和configmap,進入到nginx-ingress-controllerpod中查看配置(文件內容很多,可以導出或過濾查看)。

會看到將狀態碼關聯了自定義的默認后端

# 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對錯誤頁面的深度定制就完成了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM