一、前言
本文会在一个单节点 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 Controller:Envoy Gateway
Envoy Gateway 安装命名空间:envoy-gateway-system
测试业务命名空间:gateway-demo
GatewayClass:eg
Gateway:app-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 Gateway
3.1 拉取 Helm Chart
首先拉取 Envoy Gateway 的 Helm Chart:
helm 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 安装过程中用于生成证书的 Job,Completed 是正常状态。
只要 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
四、创建 GatewayClass
4.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
这里重点看:
ACCEPTED=True
这说明 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-v2
5.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:
- "-text=hello 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:
- "-text=hello 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
EOF
5.2 应用测试服务
执行:
kubectl apply -f 01-apps.yaml
检查 Deployment、Pod、Service:
kubectl -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
六、创建 Gateway
6.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.yaml
6.3 Gateway 字段解释
gatewayClassName: eg
表示这个 Gateway 使用前面创建的 GatewayClass eg。
也就是交给 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 为空
PROGRAMMED=False
这不一定表示 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。
因此这里的 Programmed=False 是因为:
Reason: AddressNotAssigned
Message: No addresses have been assigned to the Gateway
这不是 Gateway YAML 错误。再看 listener 状态:
Listeners:
Attached Routes: 0
Programmed=True
Accepted=True
ResolvedRefs=True
这说明:
Gateway 的 listener 配置已经成功翻译并发送到数据面。
只是当前还没有 HTTPRoute 绑定到这个 listener。
七、创建基础 HTTPRoute:/ 转发到 app-v1
7.1 HTTPRoute 的作用
HTTPRoute 用来描述 HTTP 请求如何匹配,以及匹配后转发到哪个后端 Service。
可以简单理解为:
Gateway 负责入口。
HTTPRoute 负责路由规则。
Service 负责后端访问入口。
Pod 才是真正处理请求的应用实例。
本文先创建一个最简单的 HTTPRoute:
app.example.local/
-> app-v1:8080
7.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.yaml
7.3 HTTPRoute 字段解释
parentRefs:
- name: app-gateway
sectionName: http
表示这条 HTTPRoute 绑定到:
Gateway app-gateway 的 http listener
这里的 sectionName: http 对应 Gateway 中 listener 的名字:
listeners:
- name: http
hostnames:
- "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
这说明:
Accepted=True
HTTPRoute 已经成功绑定到 Gateway。
ResolvedRefs=True
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 的 http listener 上。
八、理解 Envoy Gateway 控制面和数据面
创建 Gateway 之后,再查看 Envoy Gateway 命名空间中的 Pod:
kubectl -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.1 envoy-gateway-xxx 是控制面
envoy-gateway-6f954cd9dd-49zm7
这个 Pod 是 Helm 安装时创建的 Envoy Gateway Controller。它的职责是:
监听 GatewayClass
监听 Gateway
监听 HTTPRoute
监听 Service / EndpointSlice
把 Gateway API 资源翻译成 Envoy 配置
创建和管理 Envoy 数据面 Deployment / Service
它本身一般不直接承接业务 HTTP 请求。
8.2 envoy-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 数据面资源。
也就是说,创建一个 Gateway,Envoy 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
这里也有两个 Service:
envoy-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_SERVICE=envoy-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 同时存在导致测试结果不清晰,先删除基础 Route:
kubectl -n gateway-demo delete httproute app-route-basic
10.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-path
10.2 测试 Path 路由
测试 /api:
curl -s -H "Host: app.example.local" http://127.0.0.1:8888/api
输出:
hello from app-v1
测试 /web:
curl -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 Route:
kubectl -n gateway-demo delete httproute app-route-path
11.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-header
11.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-env=canary
才会命中第一条规则。
十二、实验三:金丝雀分流
最后测试基于权重的流量分配。目标:
app-v1 90%
app-v2 10%
先删除 Header Route:
kubectl -n gateway-demo delete httproute app-route-header
12.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-canary
12.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 weight=90
app-v2 weight=10
和:
app-v1 weight=9
app-v2 weight=1
表达的比例都近似是:
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.local"
HTTPRoute 里配置了:
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 Service
Envoy Gateway 安装后会有控制面 Service:
envoy-gateway
创建 Gateway 后又会出现数据面 Service:
envoy-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 接口。