Gateway API 实战:在单节点 Kubernetes 上使用 Envoy Gateway 跑通 HTTPRoute、Header 匹配和金丝雀分流
一、前言本文会在一个单节点 Kubernetes 测试环境中安装 Envoy Gateway然后通过 Gateway API 完成几组 HTTP 路由实验1. 安装 Envoy Gateway 2. 创建 GatewayClass 3. 部署 app-v1 / app-v2 两个测试服务 4. 创建 Gateway 和 HTTPRoute 5. 验证基础路由/ - app-v1 6. 验证 Path 路由/api - app-v1/web - app-v2 7. 验证 Header 匹配带 x-env: canary 的请求 - app-v2 8. 验证 backendRefs.weight 金丝雀分流app-v1 90%app-v2 10%二、实验环境与整体链路本文实验环境是一个单节点 Kubernetes 测试集群。实验中使用的主要资源如下Gateway ControllerEnvoy Gateway Envoy Gateway 安装命名空间envoy-gateway-system 测试业务命名空间gateway-demo GatewayClasseg Gatewayapp-gateway 测试域名app.example.local 后端服务 - app-v1 - app-v2由于本文是单节点 Kubernetes 测试环境没有云厂商 LoadBalancer也没有安装 MetalLB所以 Envoy Gateway 创建出来的LoadBalancer类型 Service 的EXTERNAL-IP会是pending。因此本文使用kubectl port-forward的方式在本机验证 Gateway API 的转发链路。最终请求链路如下curl - kubectl port-forward - Envoy Gateway 数据面 Service - Envoy Proxy 数据面 Pod - Gateway - HTTPRoute - Service - Pod三、安装 Envoy Gateway3.1 拉取 Helm Chart首先拉取 Envoy Gateway 的 Helm Charthelm pull oci://docker.io/envoyproxy/gateway-helm --version v1.8.1如果网络可以正常访问 Docker Hub这一步会在当前目录生成类似下面的文件gateway-helm-v1.8.1.tgz如果测试机无法访问 Docker Hub也可以在能访问外网的机器上下载后再上传到测试机。3.2 使用 Helm 安装 Envoy Gateway执行安装命令helm install envoygateway ./gateway-helm-v1.8.1.tgz \ -n envoy-gateway-system \ --create-namespace这里需要注意两个名字envoygateway 是 Helm release 名称。 envoy-gateway-system 是 Envoy Gateway 安装所在的命名空间。Helm release 名称和后面创建的 GatewayClass 名称不是一回事。也就是说这里的envoygateway只是 Helm 管理这次安装的 release 名字后面我们创建的GatewayClass eg是 Gateway API 里的资源名字。3.3 验证 Envoy Gateway Pod执行kubectl -n envoy-gateway-system get pod实验输出如下NAME READY STATUS RESTARTS AGE envoy-gateway-6f954cd9dd-49zm7 1/1 Running 0 26s envoygateway-gateway-helm-certgen-z9xzq 0/1 Completed 0 30s这里有两个对象需要理解envoy-gateway-xxx Envoy Gateway Controller状态 Running说明控制平面正常运行。 envoygateway-gateway-helm-certgen-xxx Helm 安装过程中用于生成证书的 JobCompleted 是正常状态。只要envoy-gateway-xxx是1/1 Running说明 Envoy Gateway 控制平面已经正常起来了。3.4 验证 Gateway API CRD执行kubectl get crd | grep gateway.networking.k8s.io实验输出如下backendtlspolicies.gateway.networking.k8s.io gatewayclasses.gateway.networking.k8s.io gateways.gateway.networking.k8s.io grpcroutes.gateway.networking.k8s.io httproutes.gateway.networking.k8s.io listenersets.gateway.networking.k8s.io referencegrants.gateway.networking.k8s.io tcproutes.gateway.networking.k8s.io tlsroutes.gateway.networking.k8s.io udproutes.gateway.networking.k8s.io这说明 Gateway API 相关 CRD 已经安装到集群中。对于 Gateway API 来说CRD 非常关键。只有 CRD 存在Kubernetes 才认识下面这些资源GatewayClass Gateway HTTPRoute ReferenceGrant GRPCRoute TCPRoute TLSRoute UDPRoute本文主要使用这三个核心资源GatewayClass Gateway HTTPRoute四、创建 GatewayClass4.1 GatewayClass 的作用GatewayClass是集群级资源用来表示一类 Gateway 由哪个 Gateway Controller 处理。可以这样理解GatewayClass 选择使用哪种 Gateway Controller本文使用 Envoy Gateway所以 GatewayClass 的controllerName写成gateway.envoyproxy.io/gatewayclass-controller这是 Envoy Gateway 对应的 controllerName。4.2 创建 GatewayClass YAML创建文件cat 00-gatewayclass.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: eg spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller EOF应用kubectl apply -f 00-gatewayclass.yaml检查kubectl get gatewayclass实验输出NAME CONTROLLER ACCEPTED AGE eg gateway.envoyproxy.io/gatewayclass-controller True 5s这里重点看ACCEPTEDTrue这说明 Envoy Gateway Controller 已经接受了这个 GatewayClass。后续 Gateway 里写gatewayClassName: eg就表示这个 Gateway 交给 Envoy Gateway 处理。五、部署 app-v1 / app-v2 测试服务为了隔离实验资源先创建一个测试命名空间。后续 Deployment、Service、Gateway、HTTPRoute 都放在这个命名空间中。kubectl create ns gateway-demo为了测试不同路由规则这里部署两个简单 HTTP 服务app-v1 返回hello from app-v1 app-v2 返回hello from app-v25.1 创建测试应用 YAML创建文件cat 01-apps.yaml EOF apiVersion: apps/v1 kind: Deployment metadata: name: app-v1 namespace: gateway-demo spec: replicas: 1 selector: matchLabels: app: app-v1 template: metadata: labels: app: app-v1 spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: app image: hashicorp/http-echo:1.0 args: - -texthello from app-v1 ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: app-v1 namespace: gateway-demo spec: selector: app: app-v1 ports: - name: http port: 8080 targetPort: 5678 --- apiVersion: apps/v1 kind: Deployment metadata: name: app-v2 namespace: gateway-demo spec: replicas: 1 selector: matchLabels: app: app-v2 template: metadata: labels: app: app-v2 spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: app image: hashicorp/http-echo:1.0 args: - -texthello from app-v2 ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: app-v2 namespace: gateway-demo spec: selector: app: app-v2 ports: - name: http port: 8080 targetPort: 5678 EOF5.2 应用测试服务执行kubectl apply -f 01-apps.yaml检查 Deployment、Pod、Servicekubectl -n gateway-demo get deploy,pod,svc实验输出NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/app-v1 1/1 1 1 15s deployment.apps/app-v2 1/1 1 1 15s NAME READY STATUS RESTARTS AGE pod/app-v1-6dbdb7dc7-947vw 1/1 Running 0 15s pod/app-v2-6f5444c457-7l25x 1/1 Running 0 15s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/app-v1 ClusterIP 10.103.180.185 none 8080/TCP 15s service/app-v2 ClusterIP 10.96.61.62 none 8080/TCP 15s继续检查后端端点kubectl -n gateway-demo get endpoints实验输出Warning: v1 Endpoints is deprecated in v1.33; use discovery.k8s.io/v1 EndpointSlice NAME ENDPOINTS AGE app-v1 10.244.0.252:5678 15s app-v2 10.244.0.253:5678 15s六、创建 Gateway6.1 Gateway 的作用Gateway用来描述具体的入口配置例如使用哪个 GatewayClass 监听哪个端口 使用什么协议 匹配哪个 hostname 允许哪些 Route 绑定本文创建一个名为app-gateway的 Gateway监听 HTTP 80 端口并匹配app.example.local。6.2 创建 Gateway YAML创建文件cat 02-gateway.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: app-gateway namespace: gateway-demo spec: gatewayClassName: eg listeners: - name: http port: 80 protocol: HTTP hostname: app.example.local allowedRoutes: namespaces: from: Same EOF应用kubectl apply -f 02-gateway.yaml6.3 Gateway 字段解释gatewayClassName: eg表示这个 Gateway 使用前面创建的 GatewayClasseg。也就是交给 Envoy Gateway Controller 处理。listeners: - name: http定义一个 listener名字叫http。后面的 HTTPRoute 可以通过sectionName: http精确绑定到这个 listener。port: 80 protocol: HTTP表示这个 listener 处理 HTTP 80 端口流量。hostname: app.example.local表示这个 listener 只匹配 Host 为app.example.local的请求。allowedRoutes: namespaces: from: Same表示只允许和 Gateway 同 namespace 的 Route 绑定。因为这个 Gateway 在gateway-demo命名空间所以只有gateway-demo命名空间里的 HTTPRoute 可以绑定到它。6.4 检查 Gateway执行kubectl -n gateway-demo get gateway实验输出NAME CLASS ADDRESS PROGRAMMED AGE app-gateway eg False 2m1s这里可以看到ADDRESS 为空 PROGRAMMEDFalse这不一定表示 Gateway YAML 写错。继续查看详情kubectl -n gateway-demo describe gateway app-gateway关键状态如下Reason: Accepted Status: True Type: Accepted Reason: AddressNotAssigned Status: False Type: Programmed这说明Gateway 已经被 Envoy Gateway 接受。 但是 Gateway 没有分配到外部地址。由于本文是单节点 Kubernetes没有云厂商 LoadBalancer也没有 MetalLB所以 Envoy Gateway 创建出来的 LoadBalancer Service 无法获得 EXTERNAL-IP。因此这里的ProgrammedFalse是因为Reason: AddressNotAssigned Message: No addresses have been assigned to the Gateway这不是 Gateway YAML 错误。再看 listener 状态Listeners: Attached Routes: 0 ProgrammedTrue AcceptedTrue ResolvedRefsTrue这说明Gateway 的 listener 配置已经成功翻译并发送到数据面。 只是当前还没有 HTTPRoute 绑定到这个 listener。七、创建基础 HTTPRoute/ 转发到 app-v17.1 HTTPRoute 的作用HTTPRoute用来描述 HTTP 请求如何匹配以及匹配后转发到哪个后端 Service。可以简单理解为Gateway 负责入口。 HTTPRoute 负责路由规则。 Service 负责后端访问入口。 Pod 才是真正处理请求的应用实例。本文先创建一个最简单的 HTTPRouteapp.example.local/ - app-v1:80807.2 创建基础 HTTPRoute YAML创建文件cat 03-route-basic.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: app-route-basic namespace: gateway-demo spec: parentRefs: - name: app-gateway sectionName: http hostnames: - app.example.local rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: app-v1 port: 8080 EOF应用kubectl apply -f 03-route-basic.yaml7.3 HTTPRoute 字段解释parentRefs: - name: app-gateway sectionName: http表示这条 HTTPRoute 绑定到Gateway app-gateway 的 http listener这里的sectionName: http对应 Gateway 中 listener 的名字listeners: - name: httphostnames: - app.example.local表示这条 HTTPRoute 只匹配 Host 为app.example.local的请求。matches: - path: type: PathPrefix value: /表示匹配所有以/开头的路径。backendRefs: - name: app-v1 port: 8080表示匹配成功后转发到app-v1这个 Service 的 8080 端口。7.4 检查 HTTPRoute执行kubectl -n gateway-demo get httproute实验输出NAME HOSTNAMES AGE app-route-basic [app.example.local] 24s继续查看详情kubectl -n gateway-demo describe httproute app-route-basic重点看Reason: Accepted Status: True Type: Accepted Reason: ResolvedRefs Status: True Type: ResolvedRefs这说明AcceptedTrue HTTPRoute 已经成功绑定到 Gateway。 ResolvedRefsTrue HTTPRoute 引用的后端 Service 已经成功解析。也就是说app-route-basic已经成功绑定到app-gateway并且能够找到后端app-v1:8080。7.5 查看 Gateway 上绑定的 Route 数量执行kubectl -n gateway-demo describe gateway app-gateway | grep Attached实验输出Attached Routes: 1说明app-route-basic已经成功挂到app-gateway的httplistener 上。八、理解 Envoy Gateway 控制面和数据面创建 Gateway 之后再查看 Envoy Gateway 命名空间中的 Podkubectl -n envoy-gateway-system get pod实验输出NAME READY STATUS RESTARTS AGE envoy-gateway-6f954cd9dd-49zm7 1/1 Running 0 68m envoy-gateway-demo-app-gateway-c2617110-5df694555c-smmfb 2/2 Running 0 83s这里很多初学者容易迷惑为什么多了一个envoy-gateway-demo-app-gateway开头的 Pod这不是重复安装了 Envoy Gateway而是控制面和数据面的区别。8.1envoy-gateway-xxx是控制面envoy-gateway-6f954cd9dd-49zm7这个 Pod 是 Helm 安装时创建的 Envoy Gateway Controller。它的职责是监听 GatewayClass 监听 Gateway 监听 HTTPRoute 监听 Service / EndpointSlice 把 Gateway API 资源翻译成 Envoy 配置 创建和管理 Envoy 数据面 Deployment / Service它本身一般不直接承接业务 HTTP 请求。8.2envoy-gateway-demo-app-gateway-xxx是数据面envoy-gateway-demo-app-gateway-c2617110-5df694555c-smmfb这个 Pod 是 Envoy Gateway 根据我们创建的Gateway app-gateway自动创建出来的数据面 Envoy Proxy Pod。可以拆开理解这个名字envoy gateway-demo app-gateway c2617110 随机 Pod 后缀其中gateway-demo 是 Gateway 所在 namespace app-gateway 是 Gateway 名字所以看到这个名字就可以判断这是 gateway-demo 命名空间里的 app-gateway 对应的数据面 Envoy Pod。真正处理业务 HTTP 请求的是这个数据面 Envoy Pod。8.3 是否每创建一个 Gateway 都会创建一个新 Pod默认情况下Envoy Gateway 通常会为每个 Gateway 创建一套独立的 Envoy Proxy 数据面资源。也就是说创建一个 GatewayEnvoy Gateway Controller 监听到它后默认会创建一套对应的数据面资源例如Deployment Pod Service不过 Envoy Gateway 也支持合并模式可以把多个 Gateway 的 listener 合并到同一套 Envoy Proxy fleet 中。本文没有配置合并模式所以使用的是默认模式。可以简单记住默认模式 一个 Gateway - 一套独立 Envoy 数据面 合并模式 多个 Gateway - 可以合并到一套 Envoy 数据面8.4 控制面 Service 和数据面 Service 的区别执行kubectl -n envoy-gateway-system get svc可以看到类似NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) envoy-gateway ClusterIP 10.97.106.78 none 18000/TCP,18001/TCP,18002/TCP,19001/TCP,9443/TCP envoy-gateway-demo-app-gateway-c2617110 LoadBalancer 10.99.157.94 pending 80:31738/TCP这里也有两个 Serviceenvoy-gateway 控制面 Service给 Envoy Gateway Controller 自己使用。 envoy-gateway-demo-app-gateway-c2617110 数据面 Service给外部流量进入 Gateway 使用。业务流量应该打到数据面 Service而不是控制面 Service。所以后面 port-forward 的对象是service/envoy-gateway-demo-app-gateway-c2617110而不是service/envoy-gateway九、使用 port-forward 验证基础路由9.1 为什么需要 port-forward由于本文是单节点 Kubernetes没有云厂商 LoadBalancer也没有安装 MetalLB所以数据面 Service 的EXTERNAL-IP是pending这表示外部地址没有分配成功。为了在本机验证 Gateway API 转发链路可以使用kubectl port-forward。port-forward可以理解成在本机端口和 Kubernetes 集群内部某个 Pod/Service 端口之间建立一条临时转发通道。9.2 执行 port-forward先设置变量export ENVOY_SERVICEenvoy-gateway-demo-app-gateway-c2617110执行端口转发kubectl -n envoy-gateway-system port-forward service/${ENVOY_SERVICE} 8888:80这条命令的含义是把当前机器的 127.0.0.1:8888 转发到 envoy-gateway-system 命名空间下 envoy-gateway-demo-app-gateway-c2617110 这个 Service 的 80 端口。格式可以理解为本地端口:目标端口所以8888:80表示本机 8888 - 集群内目标 Service/Pod 的 80该命令会一直占用当前终端。不要关闭这个终端。9.3 curl 验证基础路由重新开一个终端执行curl -s -H Host: app.example.local http://127.0.0.1:8888/实验输出hello from app-v1这说明请求已经成功到达app-v1。这里必须带-H Host: app.example.local原因是 Gateway 和 HTTPRoute 都配置了hostname: app.example.local hostnames: - app.example.local如果直接访问curl http://127.0.0.1:8888/请求里的 Host 会是127.0.0.1:8888它不匹配app.example.local路由就可能不会命中。9.4 基础路由链路基础路由成功后请求链路如下curl -H Host: app.example.local http://127.0.0.1:8888/ | v kubectl port-forward | v Envoy Gateway 数据面 Service | v Envoy 数据面 Pod | | Envoy 根据 Gateway listener 和 HTTPRoute 规则匹配请求 v Service app-v1:8080 | v app-v1 Pod:5678 | v hello from app-v1十、实验一Path 路由基础路由已经跑通后开始测试 Path 路由。目标/api - app-v1 /web - app-v2为了避免多个 HTTPRoute 同时存在导致测试结果不清晰先删除基础 Routekubectl -n gateway-demo delete httproute app-route-basic10.1 创建 Path 路由 YAML创建文件cat 04-route-path.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: app-route-path namespace: gateway-demo spec: parentRefs: - name: app-gateway sectionName: http hostnames: - app.example.local rules: - matches: - path: type: PathPrefix value: /api backendRefs: - name: app-v1 port: 8080 - matches: - path: type: PathPrefix value: /web backendRefs: - name: app-v2 port: 8080 EOF应用kubectl apply -f 04-route-path.yaml检查kubectl -n gateway-demo get httproute kubectl -n gateway-demo describe httproute app-route-path10.2 测试 Path 路由测试/apicurl -s -H Host: app.example.local http://127.0.0.1:8888/api输出hello from app-v1测试/webcurl -s -H Host: app.example.local http://127.0.0.1:8888/web输出hello from app-v2说明/api 成功转发到 app-v1 /web 成功转发到 app-v2这里使用的是type: PathPrefix表示按路径前缀匹配。例如/api /api/ /api/v1都属于/api前缀。十一、实验二Header 匹配接下来测试基于请求头的匹配。目标普通请求 - app-v1 带 x-env: canary 的请求 - app-v2这类能力可以用于测试版本访问、灰度验证等场景。先删除上一条 Path Routekubectl -n gateway-demo delete httproute app-route-path11.1 创建 Header 匹配 YAML创建文件cat 05-route-header.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: app-route-header namespace: gateway-demo spec: parentRefs: - name: app-gateway sectionName: http hostnames: - app.example.local rules: - matches: - path: type: PathPrefix value: / headers: - type: Exact name: x-env value: canary backendRefs: - name: app-v2 port: 8080 - matches: - path: type: PathPrefix value: / backendRefs: - name: app-v1 port: 8080 EOF这里把带 Header 的规则写在前面把普通默认规则写在后面便于理解和排查。带 x-env: canary 的请求会匹配第一条规则普通请求会匹配后面的默认规则。应用kubectl apply -f 05-route-header.yaml检查kubectl -n gateway-demo get httproute kubectl -n gateway-demo describe httproute app-route-header11.2 测试普通请求执行curl -s -H Host: app.example.local http://127.0.0.1:8888/输出hello from app-v1普通请求没有带x-env: canary所以命中第二条规则转发到app-v1。11.3 测试带 Header 的请求执行curl -s -H Host: app.example.local \ -H x-env: canary \ http://127.0.0.1:8888/输出hello from app-v2这说明带有x-env: canary的请求命中了第一条规则并被转发到app-v2。这里需要注意同一个 matches 中的 path 和 headers 是共同匹配条件。也就是说请求需要同时满足PathPrefix / Header x-envcanary才会命中第一条规则。十二、实验三金丝雀分流最后测试基于权重的流量分配。目标app-v1 90% app-v2 10%先删除 Header Routekubectl -n gateway-demo delete httproute app-route-header12.1 创建金丝雀分流 YAML创建文件cat 06-route-canary.yaml EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: app-route-canary namespace: gateway-demo spec: parentRefs: - name: app-gateway sectionName: http hostnames: - app.example.local rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: app-v1 port: 8080 weight: 90 - name: app-v2 port: 8080 weight: 10 EOF应用kubectl apply -f 06-route-canary.yaml检查kubectl -n gateway-demo get httproute kubectl -n gateway-demo describe httproute app-route-canary12.2 测试金丝雀分流执行 50 次请求for i in {1..50}; do curl -s -H Host: app.example.local http://127.0.0.1:8888/ done | sort | uniq -c实验输出45 hello from app-v1 5 hello from app-v2这说明流量大致按照 90/10 分到了app-v1和app-v2。这里要注意weight是相对权重不是必须加起来等于 100。例如app-v1 weight90 app-v2 weight10和app-v1 weight9 app-v2 weight1表达的比例都近似是app-v1 90% app-v2 10%另外请求次数较少时结果不一定严格等于 90/10。如果想让统计结果更稳定可以把请求次数增加到 200 次for i in {1..200}; do curl -s -H Host: app.example.local http://127.0.0.1:8888/ done | sort | uniq -c十三、常见问题总结13.1 为什么 curl 必须加 Host header因为 Gateway 里配置了hostname: app.example.localHTTPRoute 里配置了hostnames: - app.example.local所以 curl 测试时要带-H Host: app.example.local否则请求 Host 是127.0.0.1:8888不会匹配app.example.local。13.2 为什么 port-forward 到数据面 Service不是 envoy-gateway ServiceEnvoy Gateway 安装后会有控制面 Serviceenvoy-gateway创建 Gateway 后又会出现数据面 Serviceenvoy-gateway-demo-app-gateway-c2617110它们的作用不同envoy-gateway 是控制面 Service给 Envoy Gateway Controller 自己使用。 envoy-gateway-demo-app-gateway-c2617110 是数据面 Service给外部流量进入 Gateway 使用。业务流量应该进入数据面 Service而不是控制面 Service。所以正确的 port-forward 是kubectl -n envoy-gateway-system port-forward service/envoy-gateway-demo-app-gateway-c2617110 8888:80十四、总结本文在单节点 Kubernetes 测试环境中完成了 Envoy Gateway 和 Gateway API 的基础实战。完成的内容包括1. 使用 Helm 安装 Envoy Gateway 2. 检查 Gateway API CRD 3. 创建 GatewayClass 4. 部署 app-v1 / app-v2 两个测试服务 5. 创建 Gateway 6. 创建基础 HTTPRoute 7. 使用 port-forward 本机访问 Envoy Gateway 数据面 8. 验证基础路由/ - app-v1 9. 验证 Path 路由/api - app-v1/web - app-v2 10. 验证 Header 匹配x-env: canary - app-v2 11. 验证金丝雀分流app-v1 90%app-v2 10%通过这次实验可以把 Gateway API 的核心链路串起来GatewayClass - Gateway - HTTPRoute - Service - Pod也可以把 Envoy Gateway 的控制面和数据面关系理清楚envoy-gateway-xxx - 控制面 Controller负责监听 Gateway API 资源并管理 Envoy 配置 envoy-gateway-demo-app-gateway-xxx - 数据面 Envoy Proxy负责真正接收和转发业务流量最终请求链路如下真实流量链路 curl - port-forward - Envoy 数据面 Service - Envoy 数据面 Pod - Service - Pod 配置匹配逻辑 Gateway listener - HTTPRoute host/path/header 匹配 - backendRefs 选择后端 Service这篇文章只是 Gateway API 的普通 HTTP 服务实验。后续可以继续把后端app-v1/app-v2替换成 vLLM 模型服务通过 Gateway API 暴露/v1/chat/completions接口。