一句話結論:用 ingress-nginx 的小叢集,不必急著重寫所有 Ingress。實測下來,Traefik 的 ingress-nginx 相容模式可以原封不動吃下常見的 annotation;想一步到位改 Gateway API 也行,但轉址狀態碼、IP 白名單這類細節要自己補。這篇是評估,不是遷移紀錄。
遇到的問題
單機叢集常見的配置是:k3s 的內建 Traefik 關掉,自己用 Helm 裝 ingress-nginx,所有服務掛 ingressClassName: nginx,再用幾個 nginx.ingress.kubernetes.io/... annotation 處理 HTTPS 轉址、逾時、IP 白名單。
這個專案已經退役了。官方 repo 的說明寫得很直接:
- 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 不能直接用
先查了 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。但舊版 k3s 的叢集,得自己用 Helm 裝新版 Traefik。
下面的實驗都是這樣做:建叢集時停用內建 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 完全不用改。測試用的是三種最常見的寫法,原封不動貼進去:
# 1. HTTPS 轉址 metadata: annotations: nginx.ingress.kubernetes.io/force-ssl-redirect: "true" nginx.ingress.kubernetes.io/ssl-redirect: "true" # 2. 長時間逾時 nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" # 3. IP 白名單 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 | 透過這個 Ingress 連線、閒置 30 秒後仍可收發 |
這個 provider 可以和 Gateway API 的 provider 同時開著。Traefik 官方文件沒有說明兩者能不能並存,所以實測了:同一個 Traefik 裡,Ingress 和 HTTPRoute 都正常回應。
有一個小地方:Ingress 的 ADDRESS 欄位是空的。這個 provider 預設沒有回填 status.loadBalancer,chart 有 publishService 這個選項,我沒有測它有沒有用。依賴 Ingress status 的工具(例如自動建立 DNS 記錄的)要留意。
另外,如果打算讓 ingress-nginx 和 Traefik 並行一段時間再切換,官方文件提醒兩個 controller 會互相覆寫 Ingress 的 status,路由不受影響,但監看 status 的工具會被干擾。這一點我沒有實測。
路線二:改寫成 Gateway API
先裝 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。 CRD 的說明是「301 與 302 是 Core,其餘是 Extended」。實測 Traefik 3.7.16 接受並回傳
308 Permanent Redirect。換別的 Gateway 實作就不一定支援,要看該實作的文件。
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 Forbidden,把 sourceRange 換成 Pod 網段就通過。這是 Traefik 專屬的寫法,換 Gateway 實作就得重寫。
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。預設值下 100 秒的回應沒有被切斷(前面實測過),所以一般不用動,但一旦被調過就會是問題。
nginx annotation 不能蓋過 entrypoint。 在 entrypoint 設了 10 秒的情況下,即使 Ingress 寫了 proxy-read-timeout: "3600",後端 25 秒的請求還是在 10 秒被切斷。annotation 控制的是後端那一段的逾時,不會改寫 entrypoint 的設定。
ingress2gateway 轉出來的東西
官方的轉換工具 ingress2gateway 可以把現有的 Ingress 轉成 Gateway API。拿上面的三個 Ingress 餵給 v1.2.0:
ingress2gateway print --input-file ing.yaml --providers ingress-nginx 轉換器會印出警告,值得逐條看:
-
force-ssl-redirect被標為 Unsupported annotation。不過因為 Ingress 有 TLS,工具仍然自動產生了308的 HTTP → HTTPS 轉址路由,結果是對的,警告看起來只是沒有逐個 annotation 處理。 -
whitelist-source-range轉換失敗:IP-based authorization is not supported。白名單會直接掉,轉完的路由是沒有限制的。 - 逾時 annotation 會做「best-effort」轉成
timeouts.request,但數字不對。
最後一點值得單獨講。同樣的 annotation,轉出來的結果:
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 的相容只涵蓋「一部分」。Traefik 官方文件也寫了某些 annotation 的行為有差異,所以還是要逐項驗證。
路線二是長期比較乾淨的方向,因為 Kubernetes 官方文件說 Ingress API 已經凍結,並建議改用 Gateway。但每個服務都得重寫,白名單、逾時這類能力要換成實作專屬的寫法,等於把鎖定換了一個地方。
所以比較務實的順序是:先用路線一把 controller 換掉,擺脫退役的專案;再依需求,一個服務一個服務搬到 Gateway API。這篇只是把可行性驗過一遍,哪一天真的動手,仍然要在自己的叢集重新驗證,尤其是真實憑證、來源 IP 與多節點的情境。
