案例 ingress-nginx 退役后:Traefik 兼容模式零改动吃下 annotation,Gateway API 还得自己补细节

2026-11-04 20:01:28

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 的实现。

也就是说现有集群不会马上出事,但每一天都在累积「没人修」的风险。接下来有两种做法:

  1. 换 controller,Ingress 不动,让别的 controller 认得 nginx 的 annotation。
  2. 换 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-managerv1.21.3
ingress2gatewayv1.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 Forbidden
  • proxy-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
6010m0s(应是 1 分钟)
10016m40s(应是 1 分 40 秒)
360010h0m0s(应是 1 小时)

每一笔都是预期的 10 倍。从源码看解析 annotation 那一步是正确的(秒数转 Duration),问题出在后面哪一步没追到,也没找到对应 issue。这是 v1.2.0 的观察,之后版本可能已修掉,重点是转换结果一定要人工逐项核对,不要直接 apply。

踩坑汇总

  1. k3s 内置的 Traefik 可能太旧。v1.31.5 附 2.11.18,没有 ingress-nginx 兼容 provider,要自己用 Helm 装 3.6.2 以上。
  2. Gateway 的 listener 端口是 entrypoint 的端口,写成 80、443 会对不上。
  3. RequestRedirect 不要写 port,也别忘 statusCode。前者让网址多出 :443,后者默认是 302 而不是 ingress-nginx 的 308。
  4. IP 白名单在 Gateway API 没有标准写法,得靠实现专属 Middleware,转换工具也不会帮你处理。
  5. 转换工具的输出要核对,至少超时数字要自己重算。

怎么选

如果现有集群 Ingress 很多、且多半只用到重定向、超时、白名单这几个 annotation,路线一成本最低:换 controller,YAML 一行不改。缺点是 annotation 的兼容只涵盖一部分,某些行为有差异,还是要逐项验证。

路线二是长期比较干净的方向,Kubernetes 官方文档说 Ingress API 已经冻结,建议改用 Gateway。但每个服务都得重写,白名单、超时这类能力要换成实现专属写法。

比较务实的顺序是:先用路线一把 controller 换掉,摆脱退役项目;再按需求一个服务一个服务搬到 Gateway API。真动手时仍要在自己的集群重新验证,尤其是真实证书、来源 IP 与多节点场景。

延伸阅读:

复制全文 生成海报 Kubernetes Traefik GatewayAPI Ingress 运维

推荐文章

程序员茄子在线接单