Ingress NGINX Controller 自定义错误页(Custom Errors)完整指南:从 ConfigMap 配置到自定义 default-backend 实现
【免费下载链接】ingress-nginxIngress NGINX Controller for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/in/ingress-nginx
本文以 Ingress NGINX Controller(本仓库ingress-nginx)官方文档 docs/user-guide/custom-errors.md 为核心骨架,系统讲解如何通过custom-http-errors配置启用自定义错误页机制:NGINX 在发生指定 HTTP 错误时如何把错误请求转发给default-backend,以及如何编写/部署一个能够按客户端Accept头动态返回 HTML、JSON 等不同格式错误页面的自定义后端。读完本文,你将掌握该特性的完整配置链路、官方custom-error-pages镜像的内部实现原理,以及手动部署、Helm 部署与"全集群维护页"三种实战方案。
一、特性概述:错误发生时,谁在处理响应?
默认情况下,当 Ingress 后端返回错误(如 404、503)时,NGINX 会直接向客户端返回默认错误页。而启用custom-http-errors后,Ingress Controller 会配置 NGINX 将错误请求转发给default-backend,由自定义的错误后端根据请求上下文生成最合适的错误响应。
该机制的核心依赖 NGINX 的两个指令(本仓库模板 rootfs/etc/nginx/template/nginx.tmpl 中均有体现):
proxy_intercept_errors on;:让 NGINX 拦截上游返回的 HTTP 错误响应,转而处理error_page指令(模板第 494 行附近,按配置全局开启;第 847、1044 行也有按场景关闭的处理);error_page <code> = @custom_<backend>_<code>;:把指定错误码重定向到内部的@custom_*location(模板第 502 行、第 1405 行)。
在 docs/user-guide/nginx-configuration/configmap.md 中,官方对custom-http-errors的解释是:
启用哪些 HTTP 状态码应通过
error_page指令交给错误后端处理;只要设置至少一个状态码,同时也会开启处理error_page所必需的proxy_intercept_errors。
因此配置该特性时你只需关心"把哪些错误码交给错误后端",其余联动行为由 Controller 自动完成。
二、转发到 default-backend 的 8 个请求头
当custom-http-errors启用后,NGINX 在发生错误并转发给default-backend时,会附加以下请求头(来自原文档的完整表格):
| Header | Value |
|---|---|
X-Code | HTTP status code returned by the request |
X-Format | Value of theAcceptheader sent by the client |
X-Original-URI | URI that caused the error |
X-Namespace | Namespace where the backend Service is located |
X-Ingress-Name | Name of the Ingress where the backend is defined |
X-Service-Name | Name of the Service backing the backend |
X-Service-Port | Port number of the Service backing the backend |
X-Request-ID | Unique ID that identifies the request - same as for backend service |
这些请求头在 NGINX 模板的CUSTOM_ERRORS定义中逐一生成(见 rootfs/etc/nginx/template/nginx.tmpl 附近的@custom_*内部 location):
location @custom_{{ $upstreamName }}_{{ $errCode }} { internal; proxy_intercept_errors off; proxy_set_header X-Code {{ $errCode }}; 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; ... rewrite (.*) / break; proxy_pass http://upstream_balancer; }自定义错误后端可以读取这些信息,返回最能表达错误的页面。例如:如果客户端发送的Accept头是application/json,一个精心设计的后端可以返回 JSON 格式的错误载荷而不是 HTML——这正是"同一个错误码、多种内容协商"的核心价值。
⚠️ 重要约束
自定义后端必须返回正确的 HTTP 状态码,而不是一律返回
200。因为NGINX 不会修改自定义 default-backend 返回的响应(原文档 Important 说明)。
也就是说,错误码的"语义"最终由你的后端负责还原——它收到X-Code: 404,就应该回写404状态码(实现细节见下文官方镜像源码)。
三、官方参考实现:custom-error-pages 镜像
原文档明确指出:仓库内提供了一个开箱即用的自定义错误后端示例,位于images/custom-error-pages(本文按仓库根目录相对路径为 images/custom-error-pages)。
3.1 目录结构与启动入口
该镜像基于 Go 编写,核心实现位于 images/custom-error-pages/rootfs/main.go,从源码可以看出其设计:
- 定义了与上表一一对应的请求头常量(
X-Format、X-Code、X-Original-URI、X-Namespace、X-Ingress-Name、X-Service-Name、X-Service-Port、X-Request-ID),用于从 NGINX 转发的请求中读取错误上下文; - 启动时监听
:8080端口,同时暴露/metrics(Prometheus 指标)和/healthz(健康检查)接口; - 支持两个环境变量:
ERROR_FILES_PATH:错误页面文件所在目录,默认/www;DEFAULT_RESPONSE_FORMAT:客户端未指定或无法识别的Accept时的默认响应格式,默认text/html;- 设置
DEBUG环境变量时,会把收到的全部X-*请求头原样回写进响应头,便于调试排查。
3.2 内容协商与文件命名规则
errorHandler的核心逻辑是"内容协商 + 文件查找":
- 读取
X-Format请求头(即客户端的Accept);为空则使用默认格式;多个格式用逗号分隔时取第一个; - 通过
mime.ExtensionsByType把 MIME 类型映射为文件扩展名(如application/json→.json,text/html→.html,并兼容.htm→.html); - 读取
X-Code得到错误码,拼接文件路径/www/<code><ext>(如/www/404.html); - 降级策略:若精确文件(如
404.html)不存在,则回退到/www/<首位数字>xx<ext>(如4xx.html),保证任意 4xx/5xx 错误都有兜底页面;再找不到则返回 404; - 关键一步:
w.WriteHeader(code)——用从X-Code解析出的状态码原样回写,满足前文"必须返回正确状态码"的硬性要求。
镜像预置的错误页面文件位于 images/custom-error-pages/rootfs/www,共 8 个:
| 文件 | 内容示例 |
|---|---|
404.html | <span>The page you're looking for could not be found.</span> |
404.json | { "message": "The page you're looking for could not be found" } |
4xx.html/4xx.json | 任意 4xx 错误的兜底页 |
500.html/500.json | 500 精确页 |
5xx.html/5xx.json | 任意 5xx 错误的兜底页 |
镜像的构建方式可参考 images/custom-error-pages/rootfs/Dockerfile:多阶段构建,先用golang镜像编译出静态二进制nginx-errors,再以 distroless 无 root 用户镜像打包,最终以nonroot用户运行CMD ["/nginx-errors"]。
四、实战部署(一):Helm Chart 方式
原文档对应的 Helm 示例位于 docs/examples/customization/custom-errors/custom-default-backend.helm.values.yaml,核心配置如下:
controller: config: custom-http-errors: "404,503" defaultBackend: enabled: true image: registry: registry.k8s.io image: ingress-nginx/custom-error-pages tag: v1.2.9@sha256:203d3020005dbdd735c1ad51f238d8663b9851399b52cc0c9c9e3f7273b6b299 extraVolumes: - name: custom-error-pages configMap: name: custom-error-pages items: - key: "404" path: "404.html" - key: "503" path: "503.html" extraVolumeMounts: - name: custom-error-pages mountPath: /www要点解读:
controller.config.custom-http-errors与手动部署时 ConfigMap 中的键完全一致,即"只有 404 与 503 会被交给错误后端";defaultBackend.enabled: true使 Chart 直接部署官方custom-error-pages镜像作为默认后端;- 通过
extraVolumes/extraVolumeMounts把自定义 ConfigMap 挂载到/www(即ERROR_FILES_PATH默认目录),实现"不重新打镜像即可替换错误页内容"。
别忘了先创建错误页 ConfigMap(见 docs/examples/customization/custom-errors/custom-default-backend-error_pages.configMap.yaml),其数据结构为data.<状态码>: <页面内容>:
apiVersion: v1 kind: ConfigMap metadata: name: custom-error-pages data: 404: | <!DOCTYPE html> <html> <head><title>PAGE NOT FOUND</title></head> <body>PAGE NOT FOUND</body> </html> 503: | <!DOCTYPE html> <html> <head><title>CUSTOM SERVICE UNAVAILABLE</title></head> <body>CUSTOM SERVICE UNAVAILABLE</body> </html>该 ConfigMap 的键404、503会通过items映射为挂载目录下的404.html、503.html,正好命中镜像"精确码文件优先"的查找规则。
五、实战部署(二):手动 kubectl 部署
不使用 Helm 时,按 docs/examples/customization/custom-errors/README.md 的步骤操作。
5.1 创建自定义 default-backend
使用示例清单 docs/examples/customization/custom-errors/custom-default-backend.yaml 创建 Deployment 与 Service:
$ kubectl create -f custom-default-backend.yaml service "nginx-errors" created deployment.apps "nginx-errors" created该清单定义了一个名为nginx-errors的 Deployment(副本数 1,镜像为registry.k8s.io/ingress-nginx/custom-error-pages,容器端口8080)和对应的 Service(端口80→targetPort: 8080)。清单中注释还演示了两个可选能力:
- 设置环境变量
DEBUG: "true"可在客户端响应中回显 Controller 转发过来的全部X-*头,便于排查; - 通过注释掉的
volumeMounts/volumes片段,把自定义错误页 ConfigMap 挂载到/www目录。
验证部署:
$ 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 10s5.2 配置 Ingress Controller
如果尚未部署 Ingress-Nginx Controller,请先按部署指南完成部署,然后依次进行:
- 编辑
ingress-nginx-controllerDeployment,将启动参数--default-backend-service的值改为新创建的错误后端名称,例如--default-backend-service=<namespace>/nginx-errors(原文档示例中该 flag 指向新建的nginx-errors后端); - 编辑
ingress-nginx-controllerConfigMap,新增键custom-http-errors,值为404,503; - 记下 Ingress Controller Service 的 IP:
$ kubectl get svc ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx ClusterIP 10.0.0.13 <none> 80/TCP,443/TCP 10m注意:示例中
ingress-nginxService 类型为ClusterIP,实际环境可能不同;请确保在继续之前能用该 Service 正常访问 NGINX。
5.3 用 cURL 验证错误页
向 Ingress Controller 发送不带路径的请求,命中的是默认后端,返回 404 与自定义 HTML:
$ curl -D- http://10.0.0.13/ HTTP/1.1 404 Not Found Server: nginx/1.13.12 Date: Tue, 12 Jun 2018 19:11:24 GMT Content-Type: */* Transfer-Encoding: chunked Connection: keep-alive <span>The page you're looking for could not be found.</span>携带Accept: application/json再请求,错误后端按内容协商返回 JSON:
$ curl -D- -H 'Accept: application/json' http://10.0.0.13/ HTTP/1.1 404 Not Found Server: nginx/1.13.12 Date: Tue, 12 Jun 2018 19:12:36 GMT Content-Type: application/json Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding { "message": "The page you're looking for could not be found" }注意两次响应都保持了404 Not Found状态码——这正是"后端必须回写正确状态码"约束的直观体现。
更进一步,可以部署自己的应用与 Ingress,然后验证后端返回 503 的场景(例如把某个 Deployment 缩容到 0 副本),确认响应仍是正确格式。
六、从源码看参数解析链路
custom-http-errors的完整处理链路贯穿 Controller 的多个模块,掌握它可以帮你更精准地排查问题:
- ConfigMap 解析:键
custom-http-errors在 internal/ingress/controller/template/configmap.go 中定义为常量customHTTPErrors,默认值[]int{}(见 internal/ingress/controller/config/config.go 附近);读取时经filterErrors过滤非法值(configmap.go),测试用例 configmap_test.go 覆盖了"300,400,demo"这类含非法项的输入,最终得到[]int{300, 400},说明非数字项会被剔除; - Ingress 级覆盖:每个 Ingress/location 也可携带自己的错误码集合,见 internal/ingress/controller/controller.go 的
loc.CustomHTTPErrors = anns.CustomHTTPErrors,即 annotations 中提供的注解同样可以按 Ingress 覆盖全局配置;而 controller.go 还显示:当后端无可用 Endpoints 且该 location 配置了CustomHTTPErrors时,请求会按自定义错误逻辑处理; - 模板渲染:全局与 location 级错误码分别在 rootfs/etc/nginx/template/nginx.tmpl 与第 1405 行渲染
error_page指令,并在第 834 行的CUSTOM_ERRORS模板中生成带X-*请求头的内部转发 location;模板单元测试 template_test.go 覆盖了错误码集合变化(如401,402→402,403、501,502→504,505)时模板输出随之更新的场景。
七、进阶技巧
7.1 完全自研错误后端
参考官方镜像(images/custom-error-pages/rootfs/main.go)即可自己实现一个错误后端,只需满足:
- 读取上述 8 个
X-*请求头(至少读取X-Code与X-Format); - 按
X-Format内容协商选择 HTML / JSON / 纯文本等展示格式; - 用
X-Code指定的状态码原样返回响应(这是最容易踩的坑); - 健康检查接口可选,但建议暴露
/healthz供就绪探针使用。
7.2 全集群"维护页"方案
原文档还给出了一个极具实用价值的场景:把自定义错误页升级为全集群维护页,在计划维护期间阻止用户访问业务服务。具体三步:
- 按上文指南为503错误启用自定义错误页;
- 将 Controller 启动参数
--watch-namespace-selector的值设置为某个不存在的命名空间(例如nonexistent-namespace),这会阻止 Controller 读取集群中任何命名空间的Ingress资源; - 在 ConfigMap 中设置
location-snippet: return 503;,让 NGINX 对所有请求一律返回 503 状态码。
此时所有请求都会被 503 错误页接管,客户端看到的是统一维护页面,维护结束后撤销上述配置即可恢复。原文档特别指出,维护页以503 Service Unavailable状态码返回给客户端。
7.3 按需关闭错误拦截
全局配置项disable-proxy-intercept-errors可显式关闭error_page/custom-http-errors联动开启的proxy_intercept_errors(字段定义见 internal/ingress/controller/config/config.go,默认false)。当出现"错误响应被意外改写"的奇怪现象时,可检查是否与此配置相关。
八、小结
自定义错误页是 Ingress NGINX Controller 提供的、基于标准 NGINX 指令(error_page+proxy_intercept_errors)的扩展能力。它的完整形态是:
- 一个开关:ConfigMap 键
custom-http-errors(全局)或 Ingress 注解(局部),声明哪些状态码交给错误后端; - 一条链路:NGINX 拦截错误 → 通过
X-Code、X-Format等 8 个请求头携带上下文 → 转发给default-backend; - 一个后端:官方
custom-error-pages镜像(Go 实现,按状态码 + MIME 类型查找/www下的页面文件,原样回写状态码),也可自行实现; - 三种落地方式:Helm values、手动
kubectl部署、以及"全集群维护页"的组合玩法。
掌握这套机制后,你可以让集群的每一个错误响应都符合自己的品牌、格式与语义要求,同时保持 HTTP 状态码的规范性。
【免费下载链接】ingress-nginxIngress NGINX Controller for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/in/ingress-nginx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考