ingress-nginx 退役后:Traefik 兼容模式零改动吃下 annotation,Gateway API 还得自己补细节
一句话结论:用 ingress-nginx 的小集群,不必急着重写所有 Ingress。实测下来,Traefik 的 ingress-nginx 兼容模式可以原封不动吃下常见 annotation;想一步到位改 Gateway API 也行,但重定向状态码、IP 白名单这类细节要自己补。
背景:ingress-nginx 已退役
单机集群常见配置是:关掉 k3s 内置 Traefik,自己用 Helm 装 ingress-nginx,所有服务挂 ingressClassName: nginx,再用几个 nginx.ingress.kubernetes.io/... annotation 处理 HTTPS 重定向、超时、IP 白名单。
这个项目已经退役。官方 repo(kubernetes/ingress-nginx)的说明写得很直接:
- 2026 年 3 月之后,不再有新版本、bug 修复,也不再修补新发现的安全漏洞。
- 已经部署的不会坏,Helm chart 和镜像也还在。
- 没安装过的人,建议直接找一个 Gateway API 的实现。
也就是说现有集群不会马上出事,但每一天都在累积「没人修」的风险。接下来有两种做法:
- 换 controller,Ingress 不动,让别的 controller 认得 nginx 的 annotation。
- 换 API,改写成 Gateway API 的 Gateway 与 HTTPRoute。
测试环境
k3d 单节点集群:
| 项目 | 版本 |
|---|---|
| k3s(k3d 默认) | v1.31.5 |
| Traefik(Helm chart 42.2.0) | v3.7.16 |
| Gateway API CRD(standard) | v1.7.0 |
| cert-manager | v1.21.3 |
| ingress2gateway | v1.2.0 |
测试后端是 traefik/whoami,会回传请求头,支持 ?wait=25s 让后端故意慢响应,以及 /echo 的 WebSocket 端点。
限制:集群内用自签证书,没有真的向 Let's Encrypt 申请;从集群内的 Pod 发请求,没经过 NodePort/LoadBalancer,所以「来源 IP 会不会被改掉」没有验证;单节点,没做性能比较。
前置:k3s 内置的 Traefik 不能直接用
k3d 默认的 k3s v1.31.5 内置 Traefik 2.11.18。Traefik 的 ingress-nginx 兼容 provider 官方文档写明需要 v3.6.2 以上,所以 v1.31.5 附带的版本没有这个功能(traefik --help 只看到 kubernetesgateway,而且还需 --experimental.kubernetesgateway)。较新的 k3s 附带 Traefik 3.7.x:k3s v1.37.1 的内置 manifest chart 是 41.4.0,对应 Traefik v3.7.12。
建集群时禁用内置 Traefik,再用 Helm 装:
k3d cluster create demo --servers 1 --agents 0 --no-lb \
--k3s-arg "--disable=traefik@server:0"
路线一:让 Traefik 直接吃 nginx 的 Ingress
Helm values 只要打开 provider:
providers:
kubernetesIngressNGINX:
enabled: true # 默认 ingressClass 是 nginx
原本的 Ingress 完全不用改。测试三种最常见写法:
metadata:
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/whitelist-source-range: "192.0.2.0/24"
实测结果:
- HTTP → HTTPS 重定向:308 Permanent Redirect,Location 正确带回原本路径,与 ingress-nginx 默认一致
whitelist-source-range:名单外来源收到 403 Forbiddenproxy-read-timeout:生效。改成 5 秒、后端 25 秒才响应,5.0 秒收到 504;改成 60 秒正常通过backend-protocol: "HTTPS":后端自签 HTTPS 收到 200;拿掉 annotation 变成 400- WebSocket:空闲 30 秒后仍可收发
这个 provider 可以和 Gateway API 的 provider 同时开着:同一个 Traefik 里 Ingress 和 HTTPRoute 都正常响应。
注意 Ingress 的 ADDRESS 字段是空的,这个 provider 默认没有回填 status.loadBalancer,chart 有 publishService 选项但没测。依赖 Ingress status 的工具(例如自动建 DNS 记录的)要留意。如果打算让 ingress-nginx 和 Traefik 并行一段时间再切换,官方文档提醒两个 controller 会互相覆写 Ingress 的 status,路由不受影响,但监控 status 的工具会被干扰。
路线二:改写成 Gateway API
先装 CRD,再装 Traefik 并打开 Gateway provider:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.7.0/standard-install.yaml
providers:
kubernetesGateway:
enabled: true
gateway:
enabled: false # 自己建 Gateway
Traefik chart 输出信息建议安装 v1.5.1,装的是 v1.7.0,GatewayClass 仍正常被接受。Gateway 的 listener 端口必须对应 Traefik 的 entrypoint(默认 HTTP 8000、HTTPS 8443),不是对外的 80 和 443:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: main
namespace: traefik
spec:
gatewayClassName: traefik
listeners:
- name: web
port: 8000
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: websecure
port: 8443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: demo-tls
allowedRoutes:
namespaces:
from: All
HTTP 转 HTTPS
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-redirect
namespace: demo
spec:
parentRefs:
- name: main
namespace: traefik
sectionName: web
hostnames:
- app.example.test
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 308
三个发现:
- 不要写 port。写了
port: 443,Location 变成https://app.example.test:443/x;拿掉才是干净的https://app.example.test/x。 statusCode默认是 302。没写的话,原本 ingress-nginx 的 308 会变成临时重定向。- 308 可以用,但属于 Extended。301 与 302 是 Core,其余是 Extended;Traefik 3.7.16 接受并回传 308,换别的实现不一定支持。
IP 白名单
Gateway API 标准没有 IP 白名单字段。Traefik 的做法是建 Middleware,再用 ExtensionRef 挂到 HTTPRoute:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: only-office
namespace: demo
spec:
ipAllowList:
sourceRange:
- "192.0.2.0/24"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: internal
namespace: demo
spec:
parentRefs:
- name: main
namespace: traefik
sectionName: websecure
hostnames:
- internal.example.test
rules:
- filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: only-office
backendRefs:
- name: whoami
port: 80
名单外来源收到 403,把 sourceRange 换成 Pod 网段就通过。这是 Traefik 专属写法,换实现就得重写。
cert-manager 集成
证书位置从 Ingress 的 annotation 搬到 Gateway 的 listener。cert-manager 要打开 Gateway 支持:
config:
apiVersion: controller.config.cert-manager.io/v1alpha1
kind: ControllerConfiguration
enableGatewayAPI: true
Gateway 加上 issuer 的 annotation,listener 指向一个「还不存在」的 Secret:
metadata:
annotations:
cert-manager.io/cluster-issuer: selfsigned
spec:
listeners:
- name: auto
hostname: auto.example.test
port: 8443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: auto-tls
约 12 秒后 cert-manager 建出 Certificate(READY=True)与 auto-tls Secret,Traefik 开始用它提供 HTTPS,请求回 200。用的是自签 issuer,Let's Encrypt 的 DNS-01/HTTP-01 流程没验证。
长连接与超时
原本 annotation 的 proxy-read-timeout: 3600 主要是为 WebSocket 和长时间响应。到 Traefik 要分三种情况:
默认值:WebSocket 空闲 400 秒后再发消息仍收到响应;后端 100 秒才响应的 HTTP 请求在 100.1 秒收到 200。3600 秒本身没有实际等待测试。
调得很短的 entrypoint 超时:把 HTTPS entrypoint 的 readTimeout、writeTimeout、idleTimeout 都设成 10 秒:
ports:
websecure:
transport:
respondingTimeouts:
readTimeout: 10s
writeTimeout: 10s
idleTimeout: 10s
WebSocket 空闲 30 秒仍正常,升级成 WebSocket 后不受这几个超时影响;一般 HTTP 请求后端 25 秒才响应,在第 10 秒被切断(客户端看到连接被中断,没有 HTTP 状态)。也就是说要让长时间响应的请求(例如流式输出)不被切,看的是 entrypoint 的 writeTimeout。
nginx annotation 不能盖过 entrypoint:entrypoint 设 10 秒时,即使 Ingress 写了 proxy-read-timeout: "3600",后端 25 秒的请求还是在 10 秒被切断。annotation 控制的是后端那一段的超时。
ingress2gateway 转出来的结果
ingress2gateway print --input-file ing.yaml --providers ingress-nginx
转换器会打印警告,值得逐条看:
force-ssl-redirect被标为 Unsupported annotation。不过因为 Ingress 有 TLS,工具仍自动生成 308 的重定向路由,结果是对的,警告看起来只是没有逐个 annotation 处理。whitelist-source-range转换失败:IP-based authorization is not supported。白名单会直接掉,转完的路由没有限制。- 超时 annotation 做 best-effort 转成
timeouts.request,但数字不对:
| proxy-read-timeout(秒) | 转出的 timeouts.request |
|---|---|
| 60 | 10m0s(应是 1 分钟) |
| 100 | 16m40s(应是 1 分 40 秒) |
| 3600 | 10h0m0s(应是 1 小时) |
每一笔都是预期的 10 倍。从源码看解析 annotation 那一步是正确的(秒数转 Duration),问题出在后面哪一步没追到,也没找到对应 issue。这是 v1.2.0 的观察,之后版本可能已修掉,重点是转换结果一定要人工逐项核对,不要直接 apply。
踩坑汇总
- k3s 内置的 Traefik 可能太旧。v1.31.5 附 2.11.18,没有 ingress-nginx 兼容 provider,要自己用 Helm 装 3.6.2 以上。
- Gateway 的 listener 端口是 entrypoint 的端口,写成 80、443 会对不上。
- RequestRedirect 不要写 port,也别忘
statusCode。前者让网址多出:443,后者默认是 302 而不是 ingress-nginx 的 308。 - IP 白名单在 Gateway API 没有标准写法,得靠实现专属 Middleware,转换工具也不会帮你处理。
- 转换工具的输出要核对,至少超时数字要自己重算。
怎么选
如果现有集群 Ingress 很多、且多半只用到重定向、超时、白名单这几个 annotation,路线一成本最低:换 controller,YAML 一行不改。缺点是 annotation 的兼容只涵盖一部分,某些行为有差异,还是要逐项验证。
路线二是长期比较干净的方向,Kubernetes 官方文档说 Ingress API 已经冻结,建议改用 Gateway。但每个服务都得重写,白名单、超时这类能力要换成实现专属写法。
比较务实的顺序是:先用路线一把 controller 换掉,摆脱退役项目;再按需求一个服务一个服务搬到 Gateway API。真动手时仍要在自己的集群重新验证,尤其是真实证书、来源 IP 与多节点场景。
延伸阅读: