Skip to content

Cilium Gateway API 高级动手实验室

目标:通过实验理解 Cilium Gateway API 的入口流量、HTTPRoute、Header 修改、Mirror、Rewrite、Redirect、跨命名空间、gRPC、内部 L7 流量管理和灰度。

1. Lab说明

1.1 一次性前置初始化(推荐直接复制执行)

下面这段适用于本实验使用的 Cilium v1.17.0。它先安装 Gateway API CRD 和 Cilium EnvoyConfig CRD,再用 Cilium CLI 开启 Gateway API、EnvoyConfig、kube-proxy replacement 和裸机 L2 宣告。

set -euo pipefail

export CILIUM_VERSION="1.17.0"
export GATEWAY_API_VERSION="v1.4.1"

# Gateway API 标准/实验 CRD
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml"
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_gateways.yaml"
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml"
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml"
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml"
kubectl apply -f "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${GATEWAY_API_VERSION}/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml"

# Cilium Gateway controller 需要的 EnvoyConfig CRD
kubectl apply -f "https://raw.githubusercontent.com/cilium/cilium/v${CILIUM_VERSION}/pkg/k8s/apis/cilium.io/client/crds/v2/ciliumenvoyconfigs.yaml"
kubectl apply -f "https://raw.githubusercontent.com/cilium/cilium/v${CILIUM_VERSION}/pkg/k8s/apis/cilium.io/client/crds/v2/ciliumclusterwideenvoyconfigs.yaml"

# 如果 GatewayClass 是手工创建的,先让 Helm release 可以接管它。
if kubectl get gatewayclass cilium >/dev/null 2>&1; then
  kubectl label gatewayclass cilium \
    app.kubernetes.io/managed-by=Helm --overwrite
  kubectl annotate gatewayclass cilium \
    meta.helm.sh/release-name=cilium \
    meta.helm.sh/release-namespace=kube-system --overwrite
fi

# 必须通过 Cilium CLI 持久化,避免后续 upgrade 覆盖手工 ConfigMap/RBAC 修改。
if kubectl -n kube-system get daemonset/cilium >/dev/null 2>&1; then
  cilium upgrade \
    --namespace kube-system \
    --version "${CILIUM_VERSION}" \
    --reuse-values \
    --set kubeProxyReplacement=true \
    --set gatewayAPI.enabled=true \
    --set envoyConfig.enabled=true \
    --set l2announcements.enabled=true \
    --set-string gatewayAPI.gatewayClass.create=true
else
  cilium install \
    --namespace kube-system \
    --version "${CILIUM_VERSION}" \
    --set kubeProxyReplacement=true \
    --set gatewayAPI.enabled=true \
    --set envoyConfig.enabled=true \
    --set l2announcements.enabled=true \
    --set-string gatewayAPI.gatewayClass.create=true
fi

kubectl -n kube-system rollout status deployment/cilium-operator --timeout=180s
kubectl -n kube-system rollout status daemonset/cilium --timeout=300s

kubectl wait --for=condition=Accepted=True gatewayclass/cilium --timeout=120s
kubectl get gatewayclass cilium

上面的脚本会自动判断使用 cilium install 还是 cilium upgrade。gatewayAPI.gatewayClass.create 在 Cilium v1.17.0 Chart 中是字符串值,所以必须使用 --set-string。

1.2 裸机 LoadBalancer 地址和 L2 宣告

Gateway 默认会创建 LoadBalancer Service。裸机集群必须同时准备 LB IPPool 和 L2 Announcement,否则集群内可能能访问 VIP,但集群外主机不会收到 ARP 响应。

下面示例精确提供 192.168.5.200-192.168.5.215。不能写成单个 192.168.5.200/28,因为 /28 会归一化为 192.168.5.192/28。

kubectl apply -f - <<'EOF'
apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
  name: lab-lb-pool
spec:
  blocks:
  - cidr: "192.168.5.200/29"
  - cidr: "192.168.5.208/29"
---
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
  name: lab-lb-l2
spec:
  serviceSelector:
    matchLabels:
      app: web
  nodeSelector:
    matchLabels:
      kubernetes.io/os: linux
  interfaces:
  - "^ens18$"
  loadBalancerIPs: true
---
apiVersion: cilium.io/v2alpha1
kind: CiliumL2AnnouncementPolicy
metadata:
  name: gw-lab-l2
spec:
  serviceSelector:
    matchLabels:
      gateway.networking.k8s.io/gateway-name: cilium-gw
  nodeSelector:
    matchLabels:
      kubernetes.io/os: linux
  interfaces:
  - "^ens18$"
  loadBalancerIPs: true
EOF

kubectl get ciliumloadbalancerippool
kubectl get ciliuml2announcementpolicy

gw-lab-l2 必须单独存在:Cilium 自动生成的 Gateway Service 标签是 gateway.networking.k8s.io/gateway-name=cilium-gw,不是 app=web。如果只配置 lab-lb-l2,Gateway 状态可以是 Programmed=True,但集群外仍可能无法访问它的 VIP。

1.3 高级网关 API 使用案例

本实验要掌握的核心链路:

外部客户端
  -> Cilium Gateway API
  -> Cilium Envoy
  -> HTTPRoute / GRPCRoute 规则
  -> Kubernetes Service
  -> 后端 Pod

Cilium Gateway API 和传统 ingress-nginx 的关键区别:

ingress-nginx:
  外部流量 -> nginx ingress controller Pod -> Service -> Pod

Cilium Gateway API:
  外部流量 -> Cilium 管理的 Gateway/Envoy -> Service -> Pod

Cilium 官方文档要求 Gateway API 至少满足:

  • Cilium 开启 kubeProxyReplacement=true。
  • Cilium 开启 Gateway API controller。
  • L7 proxy 开启,通常默认是开启的。
  • Gateway API CRD 已安装。
  • 默认暴露模式会创建 LoadBalancer 类型 Service,所以裸金属环境需要 LB IPAM + L2 Announcements/BGP,或者使用 host network 模式。

先检查版本:

cilium version
cilium status --wait
cilium config view | sort

检查关键配置:

cilium config view | grep -E 'kube-proxy|gateway|l7|envoy'

预期至少看到类似:

enable-gateway-api true
kube-proxy-replacement true
enable-l7-proxy true

如果没有启用 Gateway API,不要只执行 cilium config set 或直接 patch cilium-config。请重新执行上面的“一次性前置初始化”,让 Cilium CLI 同时更新 ConfigMap、RBAC、EnvoyConfig 和 L2 Announcement 配置;否则下一次 cilium upgrade 可能会把手工修改覆盖掉。

如果 kube-proxy-replacement 不是 true,不要在生产直接改。这个属于 Cilium 安装级变更,建议在测试集群重装或按官方升级/迁移文档规划。

上面的前置初始化已经安装了与 Cilium v1.17.0 配套的 Gateway API CRD 和 EnvoyConfig CRD。

验证 CRD:

kubectl get crd | grep gateway.networking.k8s.io

验证 Cilium GatewayClass:

kubectl get gatewayclass
kubectl describe gatewayclass cilium

如果没有 cilium 这个 GatewayClass,继续看:

kubectl -n kube-system logs deployment/cilium-operator --tail=200
kubectl -n kube-system logs daemonset/cilium --tail=200
cilium config view | grep gateway

创建实验命名空间(可重复执行):

kubectl create namespace gw-lab --dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace gw-lab purpose=gw-lab --overwrite

启动 Hubble 观察。后续每个实验都建议开一个观察窗口:

cilium hubble port-forward &
sleep 3
hubble status
hubble observe --namespace gw-lab --follow

2. 负载均衡器

2.1 部署应用程序

理论点:

  • Gateway API 默认通过 LoadBalancer Service 暴露 Gateway。
  • Cilium Gateway 收到请求后转发到后端 Service。
  • 后端应用最好能回显请求头、路径和 Pod 名,方便验证路由行为。

部署两个 echo 后端。这里不直接依赖外部实验文件,方便你复制执行。

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-1
  labels:
    app: echo-1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-1
  template:
    metadata:
      labels:
        app: echo-1
        version: v1
    spec:
      containers:
      - name: echo
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/k8s.gcr.io/echoserver:1.9
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo-1
  labels:
    app: echo-1
spec:
  selector:
    app: echo-1
  ports:
  - name: http
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-2
  labels:
    app: echo-2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo-2
  template:
    metadata:
      labels:
        app: echo-2
        version: v2
    spec:
      containers:
      - name: echo
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/k8s.gcr.io/echoserver:1.9
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: echo-2
  labels:
    app: echo-2
spec:
  selector:
    app: echo-2
  ports:
  - name: http
    port: 8090
    targetPort: 8080
EOF

检查:

kubectl -n gw-lab rollout status deployment/echo-1 --timeout=180s
kubectl -n gw-lab rollout status deployment/echo-2 --timeout=180s
kubectl -n gw-lab get pods,svc -o wide

本实验统一使用当前环境可拉取的 echoserver 镜像。不要改 Service 名和端口,后续实验会引用。

2.2 部署 Gateway 和 HTTPRoute

部署一个基础 Gateway 和 HTTPRoute:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: cilium-gw
spec:
  gatewayClassName: cilium
  listeners:
  - name: web
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: echo-basic
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /echo
    backendRefs:
    - name: echo-1
      port: 8080
EOF

检查 Gateway 状态:

kubectl -n gw-lab get gateway,httproute,svc
kubectl -n gw-lab describe gateway cilium-gw
kubectl -n gw-lab describe httproute echo-basic

取 Gateway 地址:

kubectl -n gw-lab wait --for=condition=Programmed=True gateway/cilium-gw --timeout=180s
GATEWAY="$(kubectl -n gw-lab get gateway cilium-gw -o jsonpath='{.status.addresses[0].value}')"
echo "${GATEWAY}"

如果 GATEWAY 为空,说明 Gateway 没拿到外部地址。裸金属环境先检查:

kubectl -n gw-lab get svc | grep cilium-gateway
kubectl get ciliumloadbalancerippool
kubectl get ciliuml2announcementpolicy
kubectl get ciliumbgpclusterconfig

有地址后访问:

kubectl -n gw-lab get svc cilium-gateway-cilium-gw -o wide
curl -sS --max-time 5 "http://${GATEWAY}/echo" | head -40

如果客户端位于集群外,Gateway VIP 还必须有 ARP 宣告:

kubectl -n gw-lab get svc cilium-gateway-cilium-gw --show-labels
kubectl get ciliuml2announcementpolicy gw-lab-l2 -o yaml
kubectl -n kube-system get lease | grep cilium-l2announce

Hubble 观察:

hubble observe --namespace gw-lab --protocol http --follow

结论:

  • Gateway 是入口监听器。
  • HTTPRoute 是路由规则。
  • Gateway 获得的外部地址通常来自 LoadBalancer Service。
  • Hubble 可以看到请求落到哪个后端 Pod,但不会直接告诉你 HTTPRoute 为什么选中这条规则,需要结合 kubectl describe httproute 看 Conditions。

3. HTTP 标头请求修饰符

3.1 部署 HTTPRoute

理论点:

  • RequestHeaderModifier 可以对进入后端之前的 HTTP 请求头做 add、set、remove。
  • 适合注入平台标识、灰度标识、请求追踪头。
  • 这不是应用内逻辑,是 Gateway/Envoy 在入口层修改。

先部署一个添加请求头的路由:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: request-header-add
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /add-request-header
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: x-lab-added-by
          value: cilium-gateway
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证:

curl -sS --max-time 5 "http://${GATEWAY}/add-request-header" \
  | grep -i -E 'x-lab-added-by|request headers' -A20

部署一个删除请求头的路由:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: request-header-remove
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /remove-request-header
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        remove:
        - x-user-token
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证:

curl -sS --max-time 5 \
  -H 'x-user-token: secret-demo-value' \
  "http://${GATEWAY}/remove-request-header" \
  | grep -i 'x-user-token' || true

预期:后端输出里不应再看到 x-user-token。

部署一个覆盖请求头的路由:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: request-header-set
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /set-request-header
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        set:
        - name: x-env
          value: production
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证:

curl -sS --max-time 5 \
  -H 'x-env: test' \
  "http://${GATEWAY}/set-request-header" \
  | grep -i 'x-env'

预期:后端看到的 x-env 是 production。

3.2 可观测性

Hubble 观察 L7:

hubble observe --namespace gw-lab --protocol http --follow

再访问:

curl -sS --max-time 5 "http://${GATEWAY}/add-request-header" >/dev/null

你应该能看到 HTTP request/response 的 flow。

注意:

  • Hubble 能看到 HTTP 请求路径、状态码等 L7 信息。
  • Hubble 默认不一定显示所有请求头。是否记录 header 取决于 Cilium/Hubble/Envoy 观测配置。
  • 请求头是否被修改,最直接的验证方式是看 echo 后端响应。

4. HTTP 响应标头重写

理论点:

  • ResponseHeaderModifier 修改的是后端返回给客户端之前的响应头。
  • 常用于加安全头、删除内部头、统一响应标识。

部署:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: response-header-modifier
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /response-header
    filters:
    - type: ResponseHeaderModifier
      responseHeaderModifier:
        add:
        - name: x-platform
          value: cilium-gateway-api
        set:
        - name: x-frame-options
          value: DENY
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证响应头:

curl -i -sS --max-time 5 "http://${GATEWAY}/response-header" | head -30

预期看到:

x-platform: cilium-gateway-api
x-frame-options: DENY

Hubble 观察:

hubble observe --namespace gw-lab --protocol http --follow

结论:

  • 请求头修改影响后端收到的 request。
  • 响应头修改影响客户端收到的 response。
  • 两者都是 Gateway API 的 HTTPRoute filter 能力。

5. HTTP 流量镜像

5.1 demo应用部署

理论点:

  • Request Mirror 会把主请求复制一份给镜像后端。
  • 客户端只接收主后端响应。
  • 镜像后端适合做灰度观测、压测影子流量、兼容性验证。

确认 echo-1 和 echo-2 都存在:

kubectl -n gw-lab get deploy,svc -l app=echo-1
kubectl -n gw-lab get deploy,svc -l app=echo-2

5.2 部署镜像路由

部署镜像路由。主流量发给 echo-1,镜像流量发给 echo-2。

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: request-mirror
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /mirror
    filters:
    - type: RequestMirror
      requestMirror:
        backendRef:
          name: echo-2
          port: 8090
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证客户端响应来自主后端:

for i in $(seq 1 5); do
  curl -sS --max-time 5 "http://${GATEWAY}/mirror" | grep -E 'Hostname|host=|pod name' | head -3
done

这里只能看到 echo-1,这是正确结果。Request Mirror 不会把镜像后端的响应返回给客户端,客户端始终收到主后端 echo-1 的响应。

Hubble 观察镜像流量:

hubble observe --namespace gw-lab --follow

另一个终端触发:

for i in $(seq 1 20); do
  curl -sS --max-time 5 "http://${GATEWAY}/mirror" >/dev/null
done

验证主后端和镜像后端都收到 /mirror:

echo '--- primary echo-1 ---'
kubectl -n gw-lab logs -l app=echo-1 --since=2m --prefix=true \
  | grep '/mirror' | tail -20 || true

echo '--- mirror echo-2 ---'
kubectl -n gw-lab logs -l app=echo-2 --since=2m --prefix=true \
  | grep '/mirror' | tail -20 || true

预期两段日志都能看到:

"GET /mirror HTTP/1.1" 200

如果只看 curl 输出,会误以为只有 echo-1,因为镜像请求的响应会被 Envoy 丢弃。

进一步按后端 Pod 观察:

kubectl -n gw-lab get pods -l app=echo-2
hubble observe --namespace gw-lab --to-service echo-2 --protocol http --follow

结论:

  • Mirror 不改变客户端响应来源。
  • Mirror 会增加后端请求量,生产使用前要确认镜像服务容量。
  • 如果镜像后端失败,通常不应影响主请求,但仍要看具体实现和错误指标。

6. HTTP URL 重写

理论点:

  • URLRewrite 可以在转发到后端前改写路径或 hostname。
  • 常用于把外部路径 /api 改成应用内部路径 /。
  • Rewrite 是内部转发,不是让客户端重新请求。

部署:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: url-rewrite
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /rewrite
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /echo-rewritten
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证:

curl -sS --max-time 5 "http://${GATEWAY}/rewrite/demo" \
  | grep -E 'real path|request_uri|path|Request Information' -A10

预期:

  • 客户端访问 /rewrite/demo。
  • 后端看到的路径前缀变成 /echo-rewritten。

Hubble:

hubble observe --namespace gw-lab --protocol http --follow

结论:

  • Rewrite 对客户端透明。
  • 如果你想让浏览器地址栏变化,用 Redirect,不用 Rewrite。

7. HTTP 流量重定向

7.1 部署流量重定向

理论点:

  • RequestRedirect 返回 3xx 给客户端。
  • 客户端会重新请求新的路径、host 或 scheme。
  • Redirect 和 Rewrite 最大区别:Redirect 会让客户端感知跳转。

部署基础 redirect:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-basic
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /redirect-basic
    filters:
    - type: RequestRedirect
      requestRedirect:
        path:
          type: ReplaceFullPath
          replaceFullPath: /echo
        statusCode: 302
EOF

验证:

curl -i -sS --max-time 5 "http://${GATEWAY}/redirect-basic" | head -20

预期:

HTTP/1.1 302
location: /echo
1. 请求:`http://${GATEWAY}/redirect-basic`
返回 302,跳转地址:`http://${GATEWAY}/echo`
2. 请求:`http://${GATEWAY}/redirect-basic/test/abc`
依然完整替换路径,跳转:`http://${GATEWAY}/echo`

7.2 重定向到新主机名和新前缀

部署:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-host-prefix
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /old-prefix
    filters:
    - type: RequestRedirect
      requestRedirect:
        hostname: new.example.local
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /new-prefix
        statusCode: 302
EOF

验证:

curl -i -sS --max-time 5 "http://${GATEWAY}/old-prefix/test" | head -20

预期:Location 指向 new.example.local/new-prefix/test。

7.3 重定向 - 新状态代码和新前缀

部署 301 永久跳转:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-301
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /permanent
    filters:
    - type: RequestRedirect
      requestRedirect:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /echo
        statusCode: 301
EOF

验证:

curl -i -sS --max-time 5 "http://${GATEWAY}/permanent/abc" | head -20

7.4 重定向 - 从 HTTP 到 HTTPS 和新前缀

这里先只验证 HTTPRoute 的跳转响应,不要求你已经部署 HTTPS Gateway。

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: redirect-to-https
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /to-https
    filters:
    - type: RequestRedirect
      requestRedirect:
        scheme: https
        hostname: app.example.local
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /secure
        statusCode: 302
EOF

验证:

curl -i -sS --max-time 5 "http://${GATEWAY}/to-https/demo" | head -20

结论:

  • RequestRedirect 适合 HTTP 到 HTTPS、老域名到新域名、旧路径到新路径。
  • 生产中 301 会被客户端和浏览器缓存,灰度期间建议先用 302/307 验证。

8. 跨命名空间支持

8.1 ACME 的跨命名空间

理论点:

  • Gateway API 默认重视命名空间边界。
  • Route 是否能绑定 Gateway,由 Gateway listener 的 allowedRoutes 控制。
  • 跨命名空间引用后端 Service 时,需要目标命名空间创建 ReferenceGrant。
  • ACME/cert-manager 场景经常涉及证书、Gateway、Route 跨 namespace 协作。

创建业务命名空间:

kubectl create namespace gw-app --dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace gw-app purpose=gw-app --overwrite

部署后端到 gw-app:

kubectl -n gw-app apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cross-echo
  labels:
    app: cross-echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: cross-echo
  template:
    metadata:
      labels:
        app: cross-echo
    spec:
      containers:
      - name: echo
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/k8s.gcr.io/echoserver:1.9
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: cross-echo
spec:
  selector:
    app: cross-echo
  ports:
  - name: http
    port: 8080
    targetPort: 8080
EOF

8.2 跨命名空间网关

创建一个允许其它 namespace 绑定的 Gateway:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gw
spec:
  gatewayClassName: cilium
  listeners:
  - name: web
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All
EOF

在 gw-app 里创建 HTTPRoute,绑定 gw-lab/shared-gw:

kubectl -n gw-app apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: cross-route
spec:
  parentRefs:
  - name: shared-gw
    namespace: gw-lab
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /cross
    backendRefs:
    - name: cross-echo
      port: 8080
EOF

验证:

kubectl -n gw-lab get gateway shared-gw
kubectl -n gw-app describe httproute cross-route

SHARED_GATEWAY="$(kubectl -n gw-lab get gateway shared-gw -o jsonpath='{.status.addresses[0].value}')"
echo "${SHARED_GATEWAY}"

curl -sS --max-time 5 "http://${SHARED_GATEWAY}/cross" | head -40

再做跨 namespace backendRef 实验:让 gw-lab 中的 Route 引用 gw-app 中的 Service。先创建 Route:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: route-to-gw-app-backend
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /cross-backend
    backendRefs:
    - name: cross-echo
      namespace: gw-app
      port: 8080
EOF

这时通常会因为缺少 ReferenceGrant 而不被接受。查看:

kubectl -n gw-lab describe httproute route-to-gw-app-backend

在目标 namespace gw-app 创建授权:

kubectl -n gw-app apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gw-lab-route-to-service
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: HTTPRoute
    namespace: gw-lab
  to:
  - group: ""
    kind: Service
    name: cross-echo
EOF

再次验证:

kubectl -n gw-lab describe httproute route-to-gw-app-backend
curl -sS --max-time 5 "http://${GATEWAY}/cross-backend" | head -40

结论:

  • allowedRoutes 控制 Route 能不能绑定 Gateway。
  • ReferenceGrant 控制跨 namespace Route 能不能引用目标 Service。
  • 生产中建议运维管理 Gateway,业务团队管理各自 namespace 的 HTTPRoute。

9. 部署 gRPC 路由

9.1 部署基于 gRPC 的应用程序

理论点:

  • GRPCRoute 是 Gateway API 中专门描述 gRPC 路由的资源。
  • gRPC 基于 HTTP/2。
  • 如果通过 TLS Gateway 暴露 gRPC,需要关注 ALPN 和证书。

本实验使用官方示例思路,但为了降低复杂度,建议优先在测试环境安装 grpcurl:

grpcurl -version

如果没有:

curl -L -o /tmp/grpcurl.tar.gz https://github.com/fullstorydev/grpcurl/releases/download/v1.9.3/grpcurl_1.9.3_linux_x86_64.tar.gz
sudo tar -C /usr/local/bin -xzf /tmp/grpcurl.tar.gz grpcurl
grpcurl -version

部署 gRPC echo 服务。下面镜像如果拉取失败,请替换为你内部可用的 gRPC echo 镜像。

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-echo
  labels:
    app: grpc-echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grpc-echo
  template:
    metadata:
      labels:
        app: grpc-echo
    spec:
      containers:
      - name: grpc-echo
        image: gcr.io/istio-testing/app:latest
        ports:
        - name: grpc
          containerPort: 7070
---
apiVersion: v1
kind: Service
metadata:
  name: grpc-echo
spec:
  selector:
    app: grpc-echo
  ports:
  - name: grpc
    port: 7070
    targetPort: 7070
    appProtocol: kubernetes.io/h2c
EOF

检查:

kubectl -n gw-lab rollout status deployment/grpc-echo --timeout=180s
kubectl -n gw-lab get pods,svc -l app=grpc-echo

9.2 部署 gRPCRoute

这里部署明文 HTTP/2 gRPC 路由:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: grpc-route
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - backendRefs:
    - name: grpc-echo
      port: 7070
EOF

检查:

kubectl -n gw-lab get grpcroute
kubectl -n gw-lab describe grpcroute grpc-route

9.3 向后端服务发出 gRPC 请求

访问:

grpcurl -plaintext "${GATEWAY}:80" list || true
grpcurl -plaintext "${GATEWAY}:80" proto.EchoTestService/Echo || true

如果失败,排查:

kubectl -n gw-lab describe gateway cilium-gw
kubectl -n gw-lab describe grpcroute grpc-route
kubectl -n gw-lab get svc grpc-echo -o yaml
kubectl -n kube-system logs daemonset/cilium --tail=200 | grep -Ei 'grpc|http2|route|envoy' || true
hubble observe --namespace gw-lab --follow

注意:

  • 官方 TLS gRPC 示例还涉及证书和 ALPN;如果你要做 HTTPS/gRPC,按你的 Cilium 版本确认 gatewayAPI.enableAlpn 相关配置。
  • gRPC 比 HTTP 更容易因为协议、TLS、ALPN、appProtocol 配置不一致而失败。

10. 内部L7 流量管理

10.1 部署示例应用程序

理论点:

  • 北南向:外部客户端通过 Gateway 进集群。
  • 东西向:集群内 client 访问 Service。
  • Cilium 支持通过 Gateway API/GAMMA 思路让 HTTPRoute 绑定 Service,实现内部 L7 流量管理。
  • Cilium 官方文档说明 GAMMA 当前主要支持 producer route,HTTPRoute 和被绑定 Service 在同一 namespace。

部署内部 client:

kubectl -n gw-lab run internal-client \
  --image=curlimages/curl:8.8.0 \
  --labels=app=internal-client \
  -- sleep 365d

kubectl -n gw-lab get pod internal-client

先直接访问 Service:

kubectl -n gw-lab exec internal-client -- \
  curl -sS --max-time 5 http://echo-1:8080/internal | head -40

10.2 部署东西 HTTPRoute

把 HTTPRoute 的 parentRefs 指向 Service,而不是 Gateway:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: echo-1-service-route
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: echo-1
    port: 8080
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /service-route
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: x-east-west-route
          value: cilium-gamma
    backendRefs:
    - name: echo-1
      port: 8080
EOF

检查:

kubectl -n gw-lab describe httproute echo-1-service-route

10.3 验证东西向 L7 流量管理

访问:

kubectl -n gw-lab exec internal-client -- \
  curl -sS --max-time 5 http://echo-1:8080/service-route \
  | grep -i 'x-east-west-route' -A2

Hubble:

hubble observe --namespace gw-lab --protocol http --follow

如果没有生效,检查:

cilium config view | grep -E 'gateway|envoy|l7|proxy'
kubectl -n gw-lab describe httproute echo-1-service-route
kubectl -n kube-system logs daemonset/cilium --tail=200 | grep -Ei 'gamma|httproute|envoy' || true

10.4 东西向负载均衡

创建一个统一 Service,选择两个版本:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: echo-all
spec:
  selector:
    purpose: east-west-lb
  ports:
  - name: http
    port: 8080
    targetPort: 8080
EOF

kubectl -n gw-lab label pod -l app=echo-1 purpose=east-west-lb --overwrite
kubectl -n gw-lab label pod -l app=echo-2 purpose=east-west-lb --overwrite
kubectl -n gw-lab get endpointslice -l kubernetes.io/service-name=echo-all -o wide

循环访问:

for i in $(seq 1 20); do
  kubectl -n gw-lab exec internal-client -- \
    curl -sS --max-time 5 http://echo-all:8080/lb \
    | grep Hostname | head -1
done

结论:

  • 这是 Kubernetes Service 层负载均衡。
  • 如果需要 L7 权重灰度,用 HTTPRoute backendRefs 权重。

10.5 90/10 流量拆分

部署 Gateway 北南向 90/10 灰度:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: traffic-split-90-10
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /split
    backendRefs:
    - name: echo-1
      port: 8080
      weight: 90
    - name: echo-2
      port: 8090
      weight: 10
EOF

验证比例:

rm -f /tmp/cilium-gw-split.txt

for i in $(seq 1 200); do
  curl -sS --max-time 5 "http://${GATEWAY}/split" >> /tmp/cilium-gw-split.txt
done

grep -c 'Hostname: echo-1' /tmp/cilium-gw-split.txt || true
grep -c 'Hostname: echo-2' /tmp/cilium-gw-split.txt || true

Hubble 观察实际落点:

hubble observe --namespace gw-lab --follow

结论:

  • Hubble 能看到请求实际去了哪个后端 Pod。
  • Hubble 不会直接显示“这是 90/10 权重规则命中”,比例验证要靠后端响应或指标统计。

10.6 超时

理论点:

  • Gateway API HTTPRoute 支持 timeout 字段的版本和 Cilium 支持情况要按你当前版本确认。
  • 超时用于限制客户端请求总耗时或后端请求耗时。
  • 如果你当前 CRD 不支持 timeouts 字段,kubectl apply 会失败或字段被拒绝。

先确认 CRD 是否支持:

kubectl explain httproute.spec.rules.timeouts || true

如果支持,部署示例:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: timeout-demo
spec:
  parentRefs:
  - name: cilium-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /timeout
    timeouts:
      request: 5s
      backendRequest: 2s
    backendRefs:
    - name: echo-1
      port: 8080
EOF

验证:

kubectl -n gw-lab describe httproute timeout-demo
curl -i -sS --max-time 10 "http://${GATEWAY}/timeout" | head -30

如果你的 Gateway API CRD 不支持 timeouts,本章只记录理论,不强行实验。

10.7 小测验

回答下面问题,确保你真的掌握:

  1. Gateway 没有 ADDRESS,一定是 HTTPRoute 写错了吗?
  2. HTTPRoute Accepted=True,但请求 404,应该查哪些字段?
  3. 90/10 灰度比例应该用 Hubble 判断还是用后端响应统计判断?
  4. RequestRedirect 和 URLRewrite 哪个会让客户端看到 3xx?
  5. 跨 namespace 引用后端 Service 时,ReferenceGrant 应该创建在哪个 namespace?

参考答案:

  1. 不一定,可能是 LB IPAM/L2/BGP/LoadBalancer 环境问题。
  2. 查 matches.path、hostnames、parentRefs、Gateway listener、后端 Service。
  3. Hubble 看实际链路,比例用后端响应或指标统计更准确。
  4. RequestRedirect。
  5. 目标 Service 所在 namespace。

11. 最终测试

11.1 题目

你要完成一个综合实验:

1. 创建 Gateway:gw-lab/final-gw
2. 暴露 HTTP 80
3. /final/v1 只转发到 echo-1
4. /final/canary 按 90/10 转发到 echo-1/echo-2
5. /final/rewrite/demo rewrite 到 /echo/demo
6. /final/redirect 返回 302,跳转到 /final/v1
7. 给 /final/header 注入请求头 x-final-lab: ok
8. 用 Hubble 观察实际落到哪个 Pod
9. 用 curl 循环统计灰度比例

11.2 解题

部署:

kubectl -n gw-lab apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: final-gw
spec:
  gatewayClassName: cilium
  listeners:
  - name: web
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: final-route
spec:
  parentRefs:
  - name: final-gw
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /final/v1
    backendRefs:
    - name: echo-1
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /final/canary
    backendRefs:
    - name: echo-1
      port: 8080
      weight: 90
    - name: echo-2
      port: 8090
      weight: 10
  - matches:
    - path:
        type: PathPrefix
        value: /final/rewrite
    filters:
    - type: URLRewrite
      urlRewrite:
        path:
          type: ReplacePrefixMatch
          replacePrefixMatch: /echo
    backendRefs:
    - name: echo-1
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /final/redirect
    filters:
    - type: RequestRedirect
      requestRedirect:
        path:
          type: ReplaceFullPath
          replaceFullPath: /final/v1
        statusCode: 302
  - matches:
    - path:
        type: PathPrefix
        value: /final/header
    filters:
    - type: RequestHeaderModifier
      requestHeaderModifier:
        add:
        - name: x-final-lab
          value: ok
    backendRefs:
    - name: echo-1
      port: 8080
EOF

取地址:

FINAL_GATEWAY="$(kubectl -n gw-lab get gateway final-gw -o jsonpath='{.status.addresses[0].value}')"
echo "${FINAL_GATEWAY}"

检查状态:

kubectl -n gw-lab get gateway final-gw
kubectl -n gw-lab describe httproute final-route

验证 /final/v1:

curl -sS --max-time 5 "http://${FINAL_GATEWAY}/final/v1" | head -40

验证灰度:

rm -f /tmp/final-canary.txt

for i in $(seq 1 300); do
  curl -sS --max-time 5 "http://${FINAL_GATEWAY}/final/canary" >> /tmp/final-canary.txt
done

echo "echo-1 count:"
grep -c 'Hostname: echo-1' /tmp/final-canary.txt || true

echo "echo-2 count:"
grep -c 'Hostname: echo-2' /tmp/final-canary.txt || true

验证 rewrite:

curl -sS --max-time 5 "http://${FINAL_GATEWAY}/final/rewrite/demo" \
  | grep -E 'real path|request_uri|path|Request Information' -A10

验证 redirect:

curl -i -sS --max-time 5 "http://${FINAL_GATEWAY}/final/redirect" | head -20

验证 header:

curl -sS --max-time 5 "http://${FINAL_GATEWAY}/final/header" \
  | grep -i 'x-final-lab' -A2

Hubble 最终观察:

hubble observe --namespace gw-lab --protocol http --follow

综合排障:

kubectl -n gw-lab get gateway,httproute,svc,endpointslice
kubectl -n gw-lab describe gateway final-gw
kubectl -n gw-lab describe httproute final-route
kubectl -n kube-system logs deployment/cilium-operator --tail=200
kubectl -n kube-system logs daemonset/cilium --tail=200
hubble observe --namespace gw-lab --verdict DROPPED --follow

12. 清理实验

如果你只清理本实验资源:

kubectl delete namespace gw-app --ignore-not-found
kubectl delete namespace gw-lab --ignore-not-found

如果你只是临时启用了 Gateway API,不建议在生产直接关闭。测试环境请通过 Cilium CLI 持久化关闭:

cilium upgrade \
  --namespace kube-system \
  --reuse-values \
  --set gatewayAPI.enabled=false \
  --set envoyConfig.enabled=false \
  --set l2announcements.enabled=false

不要删除 Gateway API CRD,除非确认集群中没有任何 Gateway、HTTPRoute、GRPCRoute、ReferenceGrant、TLSRoute 资源。

13. 参考资料

  • Cilium Gateway API Support:https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gateway-api/
  • Cilium HTTP Example:https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/http/
  • Cilium HTTP Header Modifier Examples:https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/header/
  • Cilium Traffic Splitting Example:https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/splitting/
  • Cilium GAMMA Support:https://docs.cilium.io/en/stable/network/servicemesh/gateway-api/gamma/
  • Cilium L7-Aware Traffic Management:https://docs.cilium.io/en/stable/network/servicemesh/l7-traffic-management/
  • 原目录参考文章:https://jishuzhan.net/article/1933096962320019458