快速上手
CRD 安装
安装方式参考官网 gateway.envoyproxy.io/docs/tasks/…
➜ ~ k get svc -n envoy-gateway-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend ClusterIP 10.104.46.205 <none> 3000/TCP 96s
envoy-envoy-gateway-system-eg-5391c79d LoadBalancer 10.100.30.231 <pending> 80:32138/TCP 96s
envoy-gateway ClusterIP 10.107.140.234 <none> 18000/TCP,18001/TCP,18002/TCP,19001/TCP,9443/TCP 3m11s
为了方便测试,建议所有资源都安装到同一个ns下面即可;上面的 LoadBalancer 没有分配 IP 没有关系,检查对应的的endpoint都创建出来即可
➜ ~ k get ep -n envoy-gateway-system
NAME ENDPOINTS AGE
backend 192.168.90.229:3000 2m38s
envoy-envoy-gateway-system-eg-5391c79d 192.168.90.206:10080 2m38s
envoy-gateway 192.168.90.228:18000,192.168.90.228:18001,192.168.90.228:9443 + 2 more... 4m13s
测试应用
测试应用是否能访问,访问上面的 svc/envoy-envoy-gateway-system-eg-5391c79d 的 ClusterIP:port,有下面的结果输出即可
➜ ~ curl -H "Host: www.example.com" http://10.100.30.231:80/get
{
"path": "/get",
"host": "www.example.com",
"method": "GET",
"proto": "HTTP/1.1",
"headers": {
"Accept": [
"*/*"
],
"User-Agent": [
"curl/7.81.0"
],
"X-Envoy-External-Address": [
"10.39.35.142"
],
"X-Forwarded-For": [
"10.39.35.142"
],
"X-Forwarded-Proto": [
"http"
],
"X-Request-Id": [
"78fc52f9-224f-46c7-8172-dfddb239bd4a"
]
},
"namespace": "envoy-gateway-system",
"ingress": "",
"service": "",
"pod": "backend-55d64d8794-fz2vj"
}
权限设置
envoy-Gateway 控制面利用的 ServiceAccount 是 envoy-gateway,默认是在 envoy-gateway-system 这个 ns 下面
➜ ~ k get sa envoy-gateway -n envoy-gateway-system
NAME SECRETS AGE
envoy-gateway 0 24m
绑定了一个 ClusterRole 和 两个 Role
# 一个 ClusterRole
NAME CREATED AT
eg-gateway-helm-envoy-gateway-role 2025-11-17T12:05:31Z
# 两个 Role
NAME CREATED AT
eg-gateway-helm-infra-manager 2025-11-17T12:05:31Z
eg-gateway-helm-leader-election-role 2025-11-17T12:05:31Z
架构解析
访问架构
整个访问的架构如下
资源架构
资源架构解析如下
CRD
这里要区分两种资源
-
k8s Gateway API Resources
- GatewayClass:定义 Gateway 的一些通用配置
- Gateway:定义哪些流量需要网关路由
- Routes: HTTPRoute, GRPCRoute, TLSRoute, TCPRoute, UDPRoute:定义不同的路由规则
-
Envoy Gateway API Resources
-
EnvoyProxy:管理数据面 EnvoyProxy 的应用和配置
-
EnvoyPatchPolicy, ClientTrafficPolicy, SecurityPolicy, BackendTrafficPolicy, EnvoyExtensionPolicy, BackendTLSPolicy: Envoy Gateway 特有的其他策略和配置
-
Backend:资源简化了到集群外部后端的路由,并允许通过 Unix 域套接字访问外部进程
-
数控分离
Envoy Gateway 是一个系统,由两部分组成
- 数据面:处理实际的网络流量
- 控制面:管理配置和数据面组件
对应到创建出来的 Deployment
➜ ~ k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
backend 1/1 1 1 137d
envoy-envoy-gateway-system-eg-5391c79d 1/1 1 1 137d
envoy-gateway 1/1 1 1 137d
envoy-gateway :控制面,负责监听 Gateway API 资源、生成配置、下发给数据面
envoy-envoy-gateway-system-eg-5391c79d :数据面(Envoy Proxy 实例),实际处理流量转发
查看 HTTPRoute 配置
➜ ~ k get httproute backend -oyaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
...
spec:
hostnames:
- www.example.com
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg
rules:
- backendRefs:
- group: ""
kind: Service
name: backend
port: 3000
weight: 1
matches:
- path:
type: PathPrefix
value: /
➜ ~ k get svc backend
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend ClusterIP 10.104.46.205 <none> 3000/TCP 137d
可看到 hostname 为 www.example.com 的话会转发给后端的 backend 的 service
查看控制面的配置文件,是存储到一个 configmap 里面
➜ ~ k get cm envoy-gateway-config -oyaml
apiVersion: v1
data:
envoy-gateway.yaml: |
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyGateway
extensionApis: {}
gateway:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
logging: # 日志级别
level:
default: info
provider: # 这里指定provider的类型
kubernetes:
rateLimitDeployment: # 指定限流的服务镜像
container:
image: docker.io/envoyproxy/ratelimit:99d85510
patch:
type: StrategicMerge # 用策略合并方式 patch Deployment
value:
spec:
template:
spec:
containers:
- imagePullPolicy: IfNotPresent
name: envoy-ratelimit
shutdownManager:
image: docker.io/envoyproxy/gateway:v1.6.0
type: Kubernetes
kind: ConfigMap
注意这个里面的 EnvoyGateway 不是一个 CRD,而是控制面的配置文件,可以当做是控制面的启动配置
我们查看 envoyProxy 的所有CRD配置的话,也是没有这个 EnvoyGateway 的 CRD 的
EnvoyProxy
EnvoyProxy 主题要是用来连接 Gateway 和 GatewayClass 这两种资源的
使用 EnvoyProxy 可以允许用户自定义管理 Deployment 和 Service,比如设置pod的规格,注解以及镜像地址等
推荐先安装 EnvoyProxy,再安装 Gateway 和 GatewayClass,避免服务中断
创建如下一个 EnvoyProxy 的配置文件
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: custom-proxy-config
namespace: envoy-gateway-system
spec:
provider:
type: Kubernetes
kubernetes:
envoyDeployment:
replicas: 2
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: eg
spec:
gatewayClassName: eg
infrastructure:
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: custom-proxy-config
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: custom-proxy-config
namespace: default
之后将 Gateway 和 GatewayClass 的 spec 里面都加上 parametersRef 的配置
再来看数据面的副本数已经变成了我们设置的两个副本
限流
为什么需要限流?
- 预防恶意的攻击,比如 DDos 攻击?
- 避免系统过载对资源(比如数据库)造成压大太大
- 根据用户权限创建 API 的限制
Envoy Gateway 支持两种类型的限流:
Local rate limiting:对单个 envoy-proxy 实例设置的限流
Global rate limiting:对所有 envoy-proxy 实例设置的限流,详细参考文档
Local rate limiting 支持细粒度的限流,比如按照header,path 甚至请求的 HTTP-Method 来进行限流
BackendTrafficPolicy 就是用来进行限流的一些配置,比如下面这个就是按照 header 来进行限流
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: policy-httproute
namespace: envoy-gateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: backend
rateLimit:
local:
rules:
- clientSelectors:
- headers:
- name: x-user-id
value: one
limit:
requests: 3
unit: Hour
尝试访问多次就能得到如下报错,代表达到限流阈值
JWT认证
如果想在 envoy-gateway 里面集成 jwt 认证,所需步骤如下
- 生成 RSA 密钥对
- 将公钥抓换成 JWKS 格式
- 私钥签发 JWT
- 配置到 EnvoyGateway 里面
- 测试访问
JWKS 解析
JWKS 全称是 JSON Web Key Sets
主要包括下面几个字段
kty:全称 Key Type,代表密钥算法类型,如 RAS
use:全称 Public Key Use,代表用途,如 sig(签名)、enc(加密)
kid:全称 Key ID,密钥唯一标识
alg:全称 Algorithm,具体签名算法,如 RS256、ES256
n:全称 Modulus,RSA 公钥的模数(Base64url编码)
e:全称 Exponent,RAS 公钥的指数(Base64url编码)
key_ops:全称 Key Operations,允许的操作列表,比 use 更细粒度,如 ["verify"]、["sign"]
生成密钥对
# 生成私钥
openssl genrsa -out private-key.pem 2048
# 从私钥导出公钥
openssl rsa -in private-key.pem -pubout -out public-key.pem
生成 JWKS 文件以及 JWT
func main() {
// 1. 读取私钥
privPEM, err := os.ReadFile("private-key.pem")
if err != nil {
log.Fatal(err)
}
block, _ := pem.Decode(privPEM)
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
// 如果是 PKCS8 格式
key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes)
if err2 != nil {
log.Fatalf("parse private key failed: %v / %v", err, err2)
}
privateKey = key.(*rsa.PrivateKey)
}
// 2. 生成 JWKS(公钥)
jwk := jose.JSONWebKey{
Key: &privateKey.PublicKey,
KeyID: "my-key-1",
Algorithm: string(jose.RS256),
Use: "sig",
}
jwks := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{jwk}}
data, _ := json.MarshalIndent(jwks, "", " ")
fmt.Println("=== JWKS ===")
fmt.Println(string(data))
// 3. 签发 JWT
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.RS256, Key: privateKey},
(&jose.SignerOptions{}).WithHeader("kid", "my-key-1"),
)
if err != nil {
log.Fatal(err)
}
exp := time.Now().AddDate(10, 0, 0).Unix()
payload := fmt.Sprintf(`{"iss":"my-issuer","sub":"user1","exp":%d}`, exp)
jws, err := signer.Sign([]byte(payload))
if err != nil {
log.Fatal(err)
}
token, _ := jws.CompactSerialize()
fmt.Println("\n=== JWT Token ===")
fmt.Println(token)
}
配置 SecurityPolicy
apiVersion: v1
kind: ConfigMap
metadata:
name: my-jwks
namespace: envoy-gateway-system
data:
jwks.json: |
{
"keys": [
{
"use": "sig",
"kty": "RSA",
"kid": "my-key-1",
"alg": "RS256",
"n": "uGiavFFe7wfkecNernkljptJIqARMeJkpwtNpESqYfvhjwf5r7iFkGx-n3Y_GU7G6Y-3Tw8PDx2bzZSfKM7gjbGD9cgvHvEfC0eUzg3AUsr2GG3hBmwxOcYIF6HKGn18m9tMNZh6Dh_7pWtdQLNhhoQqPnorrA_pfAOlivIcBH7t7zBQDz0Ol9cAMtNpT1zva8fVxCzFDKWMDLG4wyE8iGNVKsfKKNTSwejgP8U8AwDCYKjH--zPUC7HhB7W-YoojxdO3UBA5wLXZOgA8IN_ddKs28J6T9jR_YStkFQQG1QINm3A6AR90L7n4po2hxIfNrZWCuEGMfcdzMZr51Huuw",
"e": "AQAB"
}
]
}
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: jwt-auth
namespace: envoy-gateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: backend
jwt:
providers:
- name: my-provider
issuer: "my-issuer"
localJWKS:
type: ValueRef
valueRef:
kind: ConfigMap
name: my-jwks
注意这里 SecurityPolicy 应用 Configmap 时不需要指定 key
这是因为 Envoy Gateway 对 JWKS 类型的 ConfigMap 有约定:它会自动去找 ConfigMap 中 key 为 jwks 的字段
测试访问
直接访问报错
加入 JWT 的 Token访问
压力测试
其实非常好奇 envoy 的性能如何,查看官网的数据
Envoy-Gateway 的性能如下 gateway.envoyproxy.io/tools/bench…
但是 Envoy-Proxy 的性能并未得到明确的信息 www.envoyproxy.io/docs/envoy/…
于是想实际压测一下到底这个组件能承受多少 QPS
VM环境搭建
部署 victor-a-metrics-k8s-stack: docs.victoriametrics.com/helm/victor…
这里 vmsingle 运行不成功是因为 PVC 没有挂载到对应的 PV 里面,需要手动创建 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: vm-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
storageClassName:
hostPath:
path: /data/vm
volumeMode: Filesystem
进入对应的 Grafana 看板
其实已经内置好了很多看板供我们使用,但是这些都是集群自带的一些看板
部署 Envoy-Gateway 的看板
这里需要注意,Envoy 的控制面和数据面的看板官方还没有更新到 Grafana 的 dashboard 的网站上
所以这里按照官网的提示去这里下载
直接复制导入到上一步我们部署的 Grafana 看板里面即可
导入后可看到一个属于控制面,一个属于数据面
采集配置
看板配好了,接下来配数据源以及采集参数配置
我们部署的两个组件,控制面和数据面分别如上图
控制面的访问接口是 /metrics,数据面的访问接口是 /stats/prometheus
我们配置 VMPodScrape 让 VM 自动采集这些指标
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: envoy-proxy-metrics-monitor
namespace: envoy-gateway-system
spec:
namespaceSelector:
matchNames:
- envoy-gateway-system
podMetricsEndpoints:
- interval: 15s
path: /stats/prometheus
port: metrics
selector:
matchLabels:
app.kubernetes.io/component: proxy
---
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: envoy-gateway-metrics-monitor
namespace: envoy-gateway-system
spec:
namespaceSelector:
matchNames:
- envoy-gateway-system
podMetricsEndpoints:
- interval: 15s
path: /metrics
port: metrics
selector:
matchLabels:
app.kubernetes.io/instance: eg
control-plane: envoy-gateway
注意上面部署的ns需要是 envoy 对应的pod所在的ns
看到如下两个监控看板均存在数据即可
压测准备
压测工具我们就采用 hey 来进行,参考 github.com/rakyll/hey
直接一键安装
go install github.com/rakyll/hey@latest
发送请求如下
curl -H "Host: www.example.com" http://10.100.30.231:80/get
转换成压测请求
hey -n 100 -c 100 -m GET -H "Content-Type: application/json" http://10.100.30.231:80/get
-n 表示发送的请求数量
-c 表示请求的并发度
注意上面如果直接指定端口来测试,curl 虽然能通;但是 hey 可能会报错 404
所有我们这里手动修改本机的 host 文件,让其解析到指定的 IP 上
10.100.30.231 www.example.com
然后使用下面的命令就能测通
hey -n 100 -c 10 http://www.example.com/get/
压测数据
10w请求,并发数 10
hey -n 100000 -c 10 http://www.example.com/get/
hey输出结果如下
Summary:
Total: 7.7556 secs
Slowest: 0.0154 secs
Fastest: 0.0002 secs
Average: 0.0008 secs
Requests/sec: 12893.8529
Total data: 53500000 bytes
Size/request: 535 bytes
Response time histogram:
0.000 [1] |
0.002 [96387] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.003 [3142] |■
0.005 [316] |
0.006 [81] |
0.008 [52] |
0.009 [10] |
0.011 [6] |
0.012 [0] |
0.014 [3] |
0.015 [2] |
Latency distribution:
10% in 0.0004 secs
25% in 0.0005 secs
50% in 0.0007 secs
75% in 0.0009 secs
90% in 0.0013 secs
95% in 0.0016 secs
99% in 0.0025 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.0002 secs, 0.0154 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0006 secs
req write: 0.0000 secs, 0.0000 secs, 0.0048 secs
resp wait: 0.0007 secs, 0.0002 secs, 0.0140 secs
resp read: 0.0000 secs, 0.0000 secs, 0.0094 secs
Status code distribution:
[200] 100000 responses
此时 QPS 大概是 1.67k
40w请求,并发数 10
hey -n 400000 -c 10 http://www.example.com/get/
hey输出结果如下
Summary:
Total: 32.6999 secs
Slowest: 0.0492 secs
Fastest: 0.0002 secs
Average: 0.0008 secs
Requests/sec: 12232.4674
Total data: 214000000 bytes
Size/request: 535 bytes
Response time histogram:
0.000 [1] |
0.005 [399453] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.010 [493] |
0.015 [39] |
0.020 [0] |
0.025 [1] |
0.030 [1] |
0.034 [1] |
0.039 [4] |
0.044 [4] |
0.049 [3] |
Latency distribution:
10% in 0.0004 secs
25% in 0.0005 secs
50% in 0.0007 secs
75% in 0.0010 secs
90% in 0.0013 secs
95% in 0.0017 secs
99% in 0.0026 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.0002 secs, 0.0492 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0011 secs
req write: 0.0000 secs, 0.0000 secs, 0.0113 secs
resp wait: 0.0008 secs, 0.0002 secs, 0.0491 secs
resp read: 0.0000 secs, 0.0000 secs, 0.0072 secs
Status code distribution:
[200] 400000 responses
此时 QPS 大概为 6k
80w请求,并发数 100
hey -n 800000 -c 100 http://www.example.com/get/
hey输出如下
Summary:
Total: 44.8930 secs
Slowest: 0.1391 secs
Fastest: 0.0002 secs
Average: 0.0056 secs
Requests/sec: 17820.1622
Total data: 428000000 bytes
Size/request: 535 bytes
Response time histogram:
0.000 [1] |
0.014 [765920] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.028 [32939] |■■
0.042 [987] |
0.056 [33] |
0.070 [23] |
0.084 [0] |
0.097 [0] |
0.111 [12] |
0.125 [40] |
0.139 [45] |
Latency distribution:
10% in 0.0011 secs
25% in 0.0024 secs
50% in 0.0048 secs
75% in 0.0076 secs
90% in 0.0109 secs
95% in 0.0135 secs
99% in 0.0195 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.0002 secs, 0.1391 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0614 secs
req write: 0.0000 secs, 0.0000 secs, 0.0634 secs
resp wait: 0.0055 secs, 0.0002 secs, 0.1385 secs
resp read: 0.0000 secs, 0.0000 secs, 0.0559 secs
Status code distribution:
[200] 800000 responses
此时 QPS 能达到 13k
150w 请求,并发数 100
hey -n 1500000 -c 100 http://www.example.com/get/
查看hey的输出
Summary:
Total: 82.2977 secs
Slowest: 0.0670 secs
Fastest: 0.0002 secs
Average: 0.0082 secs
Requests/sec: 18226.5195
Total data: 802500000 bytes
Size/request: 802 bytes
Response time histogram:
0.000 [1] |
0.007 [704872] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.014 [249018] |■■■■■■■■■■■■■■
0.020 [38246] |■■
0.027 [6209] |
0.034 [1333] |
0.040 [236] |
0.047 [73] |
0.054 [11] |
0.060 [0] |
0.067 [1] |
Latency distribution:
10% in 0.0011 secs
25% in 0.0023 secs
50% in 0.0046 secs
75% in 0.0075 secs
90% in 0.0107 secs
95% in 0.0133 secs
99% in 0.0193 secs
Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.0002 secs, 0.0670 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0283 secs
req write: 0.0000 secs, 0.0000 secs, 0.0287 secs
resp wait: 0.0081 secs, 0.0002 secs, 0.0670 secs
resp read: 0.0001 secs, 0.0000 secs, 0.0154 secs
Status code distribution:
[200] 1000000 responses
可看到输出只有 100w,说明被限流
查看 QPS,能达到 18.3k