Skip to content

antony@notes:~/kubernetes$ cat "Cilium-Gateway-API.md"

Cilium Gateway API

2026-04-20· kubernetes

Cilium Gateway API — Host Network Mode 部署手冊

適用環境: RKE2 bare-metal / VM + 外部 F5 / Nginx L4 LB Envoy 模式: Standalone DaemonSet(Cilium 1.16+ 預設) 目標: 讓 Cilium Gateway API 透過 hostNetwork 直接在 worker node 上監聽 80/443,由外部 LB 分流


架構總覽

Cilium Gateway API Host Network 流量架構圖

流量路徑說明:

  1. 外部 Client 發出請求
  2. F5 / Nginx (L4 LB) 將流量分發到 3 台標記為 role=gateway 的 worker node IP
  3. 每台 worker node 上的 Cilium Envoy 以 hostNetwork 模式監聽 80/443
  4. Cilium 的 eBPF 透過 TPROXY 機制將封包透明轉發給 Envoy
  5. Gateway API 的 HTTPRoute 根據 host / path 規則路由到對應的後端 K8s Service

前置條件

  • Cilium 版本 >= 1.16(hostNetwork 模式自 1.16 起支援)
  • Gateway API CRDs 已安裝
  • kubeProxyReplacement 已啟用
  • 至少 3 台 worker node 可供對外使用

Step 0:事前確認與備份

確認 Cilium 版本:

cilium version
helm list -n kube-system | grep cilium

匯出目前的 Helm values 備份(重要,萬一需要 rollback):

helm get values cilium -n kube-system -o yaml > cilium-backup-values.yaml

Step 1:為 Worker Node 打上 Label

選擇要對外的 3 台 worker node,為它們打上 role=gateway label:

kubectl label node <worker-node-1> role=gateway
kubectl label node <worker-node-2> role=gateway
kubectl label node <worker-node-3> role=gateway

驗證 label 是否正確:

kubectl get nodes -l role=gateway

預期輸出:

NAME             STATUS   ROLES    AGE   VERSION
worker-node-1   Ready    <none>   30d   v1.xx.x
worker-node-2   Ready    <none>   30d   v1.xx.x
worker-node-3   Ready    <none>   30d   v1.xx.x

Step 2:準備 Helm Values 檔案

建立 cilium-gateway-hostnetwork-values.yaml

# ── Gateway API 設定 ──────────────────────────────────────────
gatewayAPI:
  enabled: true
  hostNetwork:
    enabled: true
    # 只在有 role=gateway label 的 node 上暴露 listener
    # 如果要所有 node 都暴露,移除 nodes 區塊即可
    nodes:
      matchLabels:
        role: gateway

# ── Envoy (Standalone DaemonSet 模式) ────────────────────────
envoy:
  enabled: true
  securityContext:
    capabilities:
      # 允許 Envoy bind 80/443 等 privileged port
      keepCapNetBindService: true
      envoy:
        - NET_ADMIN
        - PERFMON
        - BPF
        - NET_BIND_SERVICE

# ── 基礎設定(通常已在現有 values 中,僅供確認)──────────────
kubeProxyReplacement: true

重點說明:

  • gatewayAPI.hostNetwork.enabled: true:啟用後會自動停用 LoadBalancer type Service,兩者互斥(Cilium 不再為 Gateway 建立 LoadBalancer type Service,改為直接在 host network 上監聽。)
  • gatewayAPI.hostNetwork.nodes.matchLabels:控制只在指定 node 上暴露 listener
  • NET_BIND_SERVICE capability:允許 Envoy bind 80/443 等 1024 以下的 privileged port

Step 3:執行 Helm Upgrade

helm upgrade cilium cilium/cilium \
  --namespace kube-system \
  --reuse-values \
  -f cilium-gateway-hostnetwork-values.yaml

注意: --reuse-values 會保留現有的所有設定,只覆蓋 values 檔案中指定的項目。


Step 4:重啟 Cilium 元件

Helm upgrade 後需重啟相關元件使設定生效:

kubectl -n kube-system rollout restart deployment/cilium-operator
kubectl -n kube-system rollout restart daemonset/cilium
kubectl -n kube-system rollout restart daemonset/cilium-envoy

等待所有 Pod 就緒:

kubectl -n kube-system rollout status deployment/cilium-operator --timeout=120s
kubectl -n kube-system rollout status daemonset/cilium --timeout=120s
kubectl -n kube-system rollout status daemonset/cilium-envoy --timeout=120s

確認 Cilium 整體狀態正常:

cilium status

Step 5:安裝 Gateway API CRDs

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.4.1/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.4.1/config/crd/standard/gateway.networking.k8s.io_gateways.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.4.1/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.4.1/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.4.1/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml

確認 GatewayClass 已存在:

kubectl get gatewayclass

執行結果:

NAME     CONTROLLER                     ACCEPTED   AGE
cilium   io.cilium/gateway-controller   True       3m52s

Step 6:部署 Demo App

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.11/samples/bookinfo/platform/kube/bookinfo.yaml

檢視 Pods

$ kubectl get pods

執行結果:

NAME                              READY   STATUS    RESTARTS   AGE
details-v1-75c85494bb-b76bs       1/1     Running   0          36s
productpage-v1-7c9bf9c988-f7cpf   1/1     Running   0          36s
ratings-v1-6456c55c66-7fczr       1/1     Running   0          36s
reviews-v1-545b8ff7c8-4fdxk       1/1     Running   0          36s
reviews-v2-5f6c87ccdb-68ws8       1/1     Running   0          36s
reviews-v3-74fb78c454-2s6ng       1/1     Running   0          36s

Step 7:Deploy the Cilium Gateway

⚠️ 關於 spec.addresses 欄位: 由於 Issue #42786 的緣故,hostNetwork 模式下此欄位不會生效也不會被填入 Gateway status。以下 YAML 中保留此欄位僅作為「此 Gateway 實際對外的 node IP」的文件化註記,Cilium 並不會使用它來分配位址。

cat <<'EOF' | kubectl apply -f -
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
spec:
  gatewayClassName: cilium
  addresses:
    - type: IPAddress
      value: "192.168.11.173"
    - type: IPAddress
      value: "192.168.11.174"
    - type: IPAddress
      value: "192.168.11.175"
  listeners:
  - protocol: HTTP
    port: 80
    name: web-gw
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-app-1
spec:
  parentRefs:
  - name: my-gateway
    namespace: default
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /details
    backendRefs:
    - name: details
      port: 9080
  - matches:
    - headers:
      - type: Exact
        name: magic
        value: foo
      queryParams:
      - type: Exact
        name: great
        value: example
      path:
        type: PathPrefix
        value: /
      method: GET
    backendRefs:
    - name: productpage
      port: 9080
EOF

Step 8:驗證端到端連通

⚠️ 驗證方法變更: 由於 Issue #42786,Gateway 層的 Programmed 條件會永遠是 False,因此不能用它來判定部署成功。請改用以下方法驗證。

8.1 檢查 Gateway 與 Listener 狀態(預期輸出已修正)

kubectl get gateway my-gateway
kubectl describe gateway my-gateway

預期輸出(受 Issue #42786 影響):

NAME         CLASS    ADDRESS   PROGRAMMED   AGE
my-gateway   cilium             False        1m

kubectl describe gateway 的輸出中,請確認以下條件皆為 True(這些才是部署成功的實際指標):

層級Condition Type預期 Status預期 Reason
GatewayAcceptedTrueAccepted
ListenerAcceptedTrueAccepted
ListenerResolvedRefsTrueResolvedRefs
GatewayProgrammedFalse(Known Issue,忽略AddressNotAssigned
ListenerProgrammedFalse(Known Issue,忽略Pending

只要上表中 AcceptedResolvedRefs 為 True,即代表 Gateway 設定被成功接受與解析,可以進入下一步實際連通驗證。

8.2 確認 Envoy 已在 Gateway node 上監聽

SSH 到任一 gateway node,確認 Envoy 有在監聽 80 port:

ss -tlnp | grep ':80'

應該可以看到 cilium-envoy 程序正在監聽 0.0.0.0:80

8.3 從外部實測 HTTP 連通性(最重要的驗證

從外部用 3 台 gateway node 的 IP 分別測試:

curl -H "Host: app.example.com" http://<worker-node-1-ip>/details/1
curl -H "Host: app.example.com" http://<worker-node-2-ip>/details/1
curl -H "Host: app.example.com" http://<worker-node-3-ip>/details/1

只要能成功取得 HTTP 回應,代表整個路徑是通的 —— Gateway status 顯示 Programmed: False 純粹是 Issue #42786 的顯示問題,不影響流量。

8.4 建議作法:用 listener 條件代替 gateway 條件作為自動化等待點

若有 CI/CD 需要「等待 Gateway 就緒」,請使用以下替代方式之一:

方式 A:等待 Listener Accepted(不受 Issue #42786 影響)

kubectl wait --for=jsonpath='{.status.listeners[0].conditions[?(@.type=="Accepted")].status}'=True \
  gateway/my-gateway --timeout=60s

方式 B:直接 curl 實測

until curl -sf -H "Host: app.example.com" http://<worker-node-1-ip>/details/1 > /dev/null; do
  sleep 2
done
echo "Gateway is serving traffic"

⚠️ 請勿使用:

# 這個會永遠 timeout,因為 Programmed 條件受 Issue #42786 影響
kubectl wait --for=condition=Programmed gateway/my-gateway --timeout=60s

F5 / Nginx LB 設定

在外部 L4 LB 上設定 pool members,指向 3 台 gateway node:

ProtocolPool Members
HTTP<worker-node-1-ip>:80, <worker-node-2-ip>:80, <worker-node-3-ip>:80
HTTPS<worker-node-1-ip>:443, <worker-node-2-ip>:443, <worker-node-3-ip>:443

Health Check 建議:

  • TCP monitor(最簡單):對 port 80 / 443 做 TCP 連線檢查
  • HTTP monitor(更精確):HTTP GET /,回傳 404 也算健康(Envoy 在沒有匹配 route 時的預設行為)

💡 提示: F5/Nginx 的 pool member 設定不會從 Gateway status.addresses 讀取。所以 Issue #42786 不影響此設定流程,你仍然是手動指定 3 台 gateway node 的 IP。


注意事項

  1. hostNetwork 與 LoadBalancer 互斥: 啟用 hostNetwork 後,Cilium 不會再為 Gateway 建立 LoadBalancer type Service
  2. Port 衝突風險: 確保 80/443 在 gateway node 上沒有被其他程式佔用(例如原本的 Ingress-Nginx)
  3. 移除舊 Ingress-Nginx: 如果要完全切換,記得先停用 RKE2 內建的 Ingress-Nginx,避免 port 衝突
  4. Gateway listener port 唯一性: 每個 Gateway 資源的 listener port 在所有 node 上必須唯一
  5. Rollback 方式: 如需還原,使用備份的 values 重新 upgrade 即可:
    helm upgrade cilium cilium/cilium \
      --namespace kube-system \
      -f cilium-backup-values.yaml
  6. Gateway status 顯示問題(Issue #42786): 在 hostNetwork 模式下 Gateway 永遠顯示 Programmed: False / AddressNotAssigned,此為已知的 upstream issue(截至 2026-04-20 仍為 Open 狀態,尚無關聯修復 PR)。此現象僅影響 status 顯示不影響實際流量。驗證部署是否成功請以 Listener 的 Accepted/ResolvedRefs 條件與實際 curl 測試為準,詳見 Step 8。

⚠️ 已知問題(Known Issue)— 必讀

Upstream Issue: cilium/cilium#42786 — Gateway API with Host Network: Gateway stays PROGRAMMED: False 最後確認時間: 2026-04-20

症狀

kubectl get gatewayADDRESS 欄空白PROGRAMMED 欄顯示 False(Reason: AddressNotAssigned)。Listener 層的 Programmed 同樣為 False(Reason: Pending)。

Issue 現狀(截至 2026-04-20)

GitHub Issue 仍為 Open 狀態,無關聯修復 PR,Cilium v1.19.x 亦未修復。詳細追蹤資訊請見文末附錄。

影響範圍與根因

回報者 @dke 在 issue 中明確指出:

“Not a regression, but wanted to state that I can get a working gateway-api if I don’t use host network mode, but rather a setup with, for example, cilium and bgp based load balancers.”

也就是說:

  • 不是 regression(不是新版本把舊版本弄壞的問題)
  • 這是 hostNetwork 模式特有的行為——一旦改用 Cilium + BGP LoadBalancer(有真正的 Service 可供 LB-IPAM 指派 IP)就會恢復正常
  • 根因是 Cilium 的 spec.addresses 實作與 LB-IPAM 強耦合,而 hostNetwork 模式會停用 LoadBalancer Service,導致 status.addresses 沒有 IP 來源可以填入

此問題不影響的部分 ✅

  • 實際流量 routing 完全正常:Envoy 仍會在 host 的 80/443 port 監聽,F5 → node IP → Envoy → backend 的路徑運作如預期
  • Listener 的 AcceptedResolvedRefs 都會是 True:代表 Gateway / HTTPRoute 的設定本身是有效、可接受的
  • HTTPRoute 功能正常:path 比對、header 比對、backend 轉發都會生效

此問題會影響的部分 ❌

  • kubectl get gateway 的 ADDRESS 欄永遠是空白
  • Gateway 層的 Programmed 條件永遠是 False
  • 不可使用 kubectl wait --for=condition=programmed gateway/<name> 作為 CI/CD 或部署自動化的等待條件