Skip to main content

Envoy Proxy 生产实战:从反向代理、流量治理到高可用网关

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
40 MIN READ... VIEWS

如果只看一份最小配置,Envoy 很像“另一个 Nginx”:监听一个端口,把请求转发给后端。但真正让 Envoy 成为云原生基础设施核心组件的,不是反向代理本身,而是它把路由、服务发现、负载均衡、故障隔离、安全和可观测性做成了一个可动态控制的数据平面。

这也是为什么 Envoy 会出现在 API Gateway、Kubernetes Gateway、Service Mesh、gRPC 基础设施、边缘代理和多集群流量治理中。

本文沿着一条生产链路展开:

  1. Envoy 到底解决什么问题,什么时候值得使用;
  2. 一个请求如何穿过 Listener、Filter、Route、Cluster 和 Endpoint;
  3. 怎样在本地跑起一个具有灰度路由、健康检查和异常摘除能力的代理;
  4. 怎样正确设计超时、重试、熔断、限流、鉴权和动态配置;
  5. 怎样在 Kubernetes 上用 Envoy Gateway 部署一个跨节点、可扩缩、可优雅升级的高可用入口;
  6. 上线前应该监控什么、演练什么,以及故障时如何定位。

本文基于 2026 年 9 月 4 日可用的官方资料与稳定版本:Envoy 1.39.1、Envoy Gateway 1.9.1。示例固定具体版本,避免生产环境无意间拉取变化中的 latest 镜像。


一、Envoy 是什么:数据平面,而不是业务框架

Envoy 是一个高性能 L4/L7 代理。它通常部署在应用请求路径中,接收下游连接,执行一组网络或 HTTP Filter,再把请求转发到上游服务。

官方术语里:

  • Downstream:连接 Envoy 的客户端;
  • Listener:Envoy 对外监听的 IP、端口或 Unix Domain Socket;
  • Filter Chain:连接或请求依次经过的处理链;
  • Route:根据域名、路径、Header 等条件选择转发动作;
  • Cluster:一组逻辑等价的上游实例;
  • Endpoint:Cluster 中真正接收流量的实例;
  • Upstream:Envoy 最终连接的后端服务。

Envoy 本身一般是数据平面:它高频处理真实业务流量。控制面负责把 Listener、Route、Cluster、Endpoint 和 Secret 等配置下发给 Envoy。静态 YAML、Envoy Gateway、Istio、Consul 或自研 xDS 服务,都可以扮演配置来源。

一张图看懂 Envoy 的六类核心能力

Envoy 功能全景:流量入口、流量治理、可靠性、安全、动态配置与可观测性六大能力域

这六类能力并不是彼此独立的功能开关,而是在一次请求中协同工作:

  1. 流量入口负责接收 TCP、HTTP、gRPC 等连接,完成 TLS 终止、协议识别和基础请求规范化;
  2. 流量治理根据路径、域名、Header、权重或 Hash 选择目标服务,并支持灰度、镜像和会话亲和;
  3. 可靠性通过 Timeout、Retry Budget、Circuit Breaker、Health Check 和 Outlier Detection 限定故障影响;
  4. 安全把 mTLS、JWT、RBAC、外部鉴权和限流放到统一的数据路径上执行;
  5. 动态配置通过 LDS、RDS、CDS、EDS、SDS 等 xDS API,在不中断现有流量的前提下更新拓扑和策略;
  6. 可观测性为每个 Listener、Route、Cluster 和 Endpoint 输出指标、访问日志、Trace 与 Response Flags。

可以把 Envoy 理解为一个可编程的网络执行引擎:控制面声明“希望流量怎样运行”,Envoy 在每个请求上执行策略并产生运行证据。后面的章节会分别把这六个能力域拆开。

Envoy、Envoy Gateway 与 Service Mesh 不是同一个东西

名称核心职责适合场景你需要维护什么
Envoy Proxy处理 L4/L7 流量的数据平面独立代理、边缘网关、Sidecar、内部负载均衡Envoy 配置或 xDS 控制面
Envoy Gateway把 Kubernetes Gateway API 翻译成 Envoy xDS 配置Kubernetes 南北向网关Gateway API、EnvoyProxy 策略和集群资源
Service Mesh管理大量服务间东西向通信跨语言微服务、统一 mTLS 和流量策略Mesh 控制面、代理生命周期与身份体系
API Gateway 产品API 生命周期、开发者门户、Key、配额和商业策略对外开放 API、合作伙伴平台产品级 API 管理能力和数据面

一个常见误区是“用了 Envoy 就等于有了完整 API Gateway”。Envoy 提供路由、JWT、外部鉴权、限流等底层能力,但开发者门户、套餐、API Key 生命周期、计费和审批通常需要额外控制面或产品层。


二、什么时候应该使用 Envoy

2.1 高价值场景

场景Envoy 带来的价值典型能力
gRPC 网关或负载均衡原生理解 HTTP/2、gRPC 状态与长连接gRPC 路由、健康检查、重试、统计
Kubernetes Ingress / Gateway路由策略声明化,数据面动态更新Gateway API、xDS、TLS、灰度发布
多语言微服务把网络治理从各语言 SDK 中抽离mTLS、超时、熔断、可观测性
边缘反向代理高并发连接、细粒度 L7 路由HTTP/1.1、HTTP/2、HTTP/3、TLS、WAF 扩展
出口代理统一控制服务访问外部网络Forward Proxy、DNS、访问审计、策略
金丝雀与蓝绿发布不改业务代码即可按比例或条件分流Weighted Cluster、Header Match、Mirroring
多可用区容灾本地优先和跨区域故障转移Locality、Priority、健康检查、异常摘除

2.2 不一定需要 Envoy 的场景

下面这些情况,简单代理可能更划算:

  • 只有一个静态网站和一个固定后端;
  • 配置极少变化,不需要动态服务发现;
  • 团队没有维护流量策略、证书、监控和升级的能力;
  • 延迟预算极端苛刻,但又没有对真实 Filter Chain 做过压测;
  • 只是想解决某个语言框架内部的一个小问题。

Envoy 的成本不是“多一个容器”这么简单。它会引入新的配置模型、故障域、指标体系、证书生命周期和发布流程。只有当统一治理带来的收益超过这些复杂度时,才值得引入。


三、请求生命周期:看懂配置前先看懂数据流

以一个 HTTPS API 请求为例,请求大致经历以下过程:

Envoy 请求全链路:客户端经过 DNS、负载均衡、Listener、TLS、HTTP Filter、Route 和 Cluster 到达健康 Endpoint,控制面通过 xDS 下发配置,可观测平台收集指标、日志与链路

读这张图时要同时跟踪三条路径:

  • 数据路径:客户端请求从左向右经过 Listener、Filter、Route 和 Cluster,最终进入健康 Endpoint;
  • 控制路径:控制面通过 xDS 更新服务发现、路由、证书和策略,不直接承载业务请求;
  • 证据路径:Envoy 把 Metrics、Access Logs 和 Traces 输出给可观测平台,用于判断策略是否生效以及故障发生在哪里。

下面的时序图进一步展开一次请求内部的先后顺序:

这里有几个生产上非常重要的事实:

  1. Filter 顺序会改变语义。 鉴权、限流、缓存、压缩和 Router 的先后次序不是装饰;Router 通常必须位于 HTTP Filter Chain 最后。
  2. 负载均衡发生在 Cluster 内部。 Route 先选 Cluster,再由 Cluster 的负载均衡策略选 Endpoint。
  3. 连接池属于 Worker Thread。 Envoy 是单进程多线程模型,每个 Worker 处理网络事件并维护自己的上游连接池。
  4. 一次请求可能产生多次上游尝试。 重试或 Hedging 会放大真实上游流量,不能只看下游 QPS。
  5. 健康检查与异常摘除是两条信号。 主动健康检查定期探测;Outlier Detection 根据真实请求结果做被动判断。

四、第一次实战:Docker Compose 跑起灰度代理

下面搭建一个最小但不玩具化的环境:

  • Envoy 监听 10000
  • 两个 Nginx 后端分别返回 app-v1app-v2
  • 90% 流量进入 v1,10% 进入 v2;
  • 启用超时、有限重试、主动健康检查、熔断和异常实例摘除;
  • Admin 端口只映射到宿主机回环地址。

4.1 compose.yaml

services:
envoy:
image: envoyproxy/envoy:v1.39.1
command: ["-c", "/etc/envoy/envoy.yaml", "--log-level", "info"]
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
ports:
- "10000:10000"
- "127.0.0.1:9901:9901"
depends_on:
- app-v1
- app-v2

app-v1:
image: nginx:1.27-alpine
command:
- /bin/sh
- -c
- |
printf 'app-v1\n' > /usr/share/nginx/html/index.html
exec nginx -g 'daemon off;'

app-v2:
image: nginx:1.27-alpine
command:
- /bin/sh
- -c
- |
printf 'app-v2\n' > /usr/share/nginx/html/index.html
exec nginx -g 'daemon off;'

4.2 envoy.yaml

admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901
allow_paths:
- exact: /ready
- exact: /server_info
- prefix: /stats
- prefix: /clusters
- prefix: /config_dump

static_resources:
listeners:
- name: public_http
address:
socket_address:
address: 0.0.0.0
port_value: 10000
per_connection_buffer_limit_bytes: 32768
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: public_http
use_remote_address: true
normalize_path: true
merge_slashes: true
path_with_escaped_slashes_action: UNESCAPE_AND_REDIRECT
common_http_protocol_options:
idle_timeout: 60s
headers_with_underscores_action: REJECT_REQUEST
stream_idle_timeout: 30s
request_timeout: 15s
route_config:
name: local_routes
virtual_hosts:
- name: application
domains: ["*"]
routes:
- match:
prefix: "/"
route:
timeout: 5s
retry_policy:
retry_on: "5xx,connect-failure,reset"
num_retries: 2
per_try_timeout: 1s
weighted_clusters:
clusters:
- name: app_v1
weight: 90
- name: app_v2
weight: 10
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
log_format:
json_format:
timestamp: "%START_TIME%"
method: "%REQ(:METHOD)%"
path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
protocol: "%PROTOCOL%"
response_code: "%RESPONSE_CODE%"
response_flags: "%RESPONSE_FLAGS%"
duration_ms: "%DURATION%"
upstream_host: "%UPSTREAM_HOST%"
upstream_cluster: "%UPSTREAM_CLUSTER%"
request_id: "%REQ(X-REQUEST-ID)%"

clusters:
- name: app_v1
type: STRICT_DNS
connect_timeout: 1s
per_connection_buffer_limit_bytes: 32768
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: app_v1
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: app-v1
port_value: 80
health_checks:
- timeout: 1s
interval: 5s
unhealthy_threshold: 2
healthy_threshold: 2
http_health_check:
path: /
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 1000
max_pending_requests: 1000
max_requests: 5000
max_retries: 100
outlier_detection:
consecutive_5xx: 5
interval: 5s
base_ejection_time: 30s
max_ejection_percent: 50

- name: app_v2
type: STRICT_DNS
connect_timeout: 1s
per_connection_buffer_limit_bytes: 32768
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: app_v2
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: app-v2
port_value: 80
health_checks:
- timeout: 1s
interval: 5s
unhealthy_threshold: 2
healthy_threshold: 2
http_health_check:
path: /
circuit_breakers:
thresholds:
- priority: DEFAULT
max_connections: 200
max_pending_requests: 200
max_requests: 1000
max_retries: 20
outlier_detection:
consecutive_5xx: 3
interval: 5s
base_ejection_time: 30s
max_ejection_percent: 100

本地示例为了从宿主机查看 Admin API,让 Admin 监听容器内 0.0.0.0,但端口只发布到宿主机 127.0.0.1。生产环境不要把 Admin 直接暴露到业务网络或公网,因为它没有内置身份认证,并且包含修改运行状态甚至关闭进程的操作。

4.3 先验证配置,再启动

docker run --rm \
-v "$PWD/envoy.yaml:/etc/envoy/envoy.yaml:ro" \
envoyproxy/envoy:v1.39.1 \
--mode validate -c /etc/envoy/envoy.yaml

docker compose up -d

验证流量分配:

for i in $(seq 1 30); do
curl -s http://127.0.0.1:10000/
done | sort | uniq -c

样本量只有 30 时,不保证刚好是 27:3。权重路由是概率分布,不是每十个请求固定一次 v2。

查看运行状态:

curl -s http://127.0.0.1:9901/ready
curl -s 'http://127.0.0.1:9901/stats?filter=cluster\.app_.*membership'
curl -s http://127.0.0.1:9901/clusters
curl -s http://127.0.0.1:9901/config_dump

4.4 做一次真实故障实验

docker compose stop app-v2

for i in $(seq 1 20); do
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:10000/
done | sort | uniq -c

curl -s 'http://127.0.0.1:9901/stats?filter=cluster\.app_v2'

你应该观察到 v2 健康检查失败。此时被权重算法选中 v2 的请求仍可能返回 503 no healthy upstreamWeighted Cluster 负责分流,不等于跨 Cluster 故障转移,普通重试默认也不会重新选择另一个 Weighted Cluster。生产灰度应由控制面及时把 v2 权重降为 0,或显式设计 Priority、Aggregate / Composite Cluster 等 Failover 机制。

这里真正需要验证的不是“页面大部分时候还能打开”,而是:

  • v2 是否变成不健康;
  • 失败窗口内产生了多少 5xx;
  • 重试是否把请求救回;
  • 重试是否造成明显流量放大;
  • v1 的容量是否足以接管全部流量。

最后恢复并清理:

docker compose start app-v2
docker compose down

五、配置心智模型:从静态 YAML 到动态 xDS

5.1 Bootstrap 只负责“Envoy 怎样启动”

Envoy 启动时读取 Bootstrap 配置,内容通常包括:

  • Node 身份;
  • Admin 接口;
  • 静态 Listener 和 Cluster;
  • 如何连接 xDS 或 SDS;
  • Runtime 层;
  • Overload Manager;
  • Stats Sink。

小规模部署可以把所有配置写在一个文件里。规模上升后,更常见的做法是只在 Bootstrap 中保留连接控制面的最小资源,其余配置通过 xDS 动态下发。

5.2 xDS 不是一个接口,而是一组 Discovery Service

API资源解决的问题
LDSListenerEnvoy 监听什么地址,使用什么 Filter Chain
RDSRouteConfigurationHTTP 请求怎样匹配、转发、重试或重定向
CDSCluster有哪些上游服务及其连接策略
EDSClusterLoadAssignment每个 Cluster 当前有哪些 Endpoint
SDSSecretTLS 证书、私钥、信任根怎样动态轮换
RTDSRuntime不重启进程怎样调整运行时开关
ECDSExtension ConfigFilter 扩展配置怎样独立更新

标准 xDS 的 State of the World 模式会发送订阅范围内的完整状态;Delta xDS 只发送新增、修改和删除的资源,更适合资源数量很大的控制面。ADS 则把不同资源类型放在一条 gRPC 流中,方便控制更新顺序。

5.3 动态配置不等于“配置永远正确”

生产控制面至少需要处理:

  1. 版本与 ACK/NACK:记录每个 Envoy 接受或拒绝了哪个版本;
  2. 资源依赖顺序:例如先准备 Cluster,再让 Route 引用它;
  3. 最后可用配置:控制面故障时,已有 Envoy 应继续承载当前流量;
  4. 灰度下发:先给一小组代理实例更新,再逐步扩大;
  5. 快速回滚:回滚的是一组一致资源,而不是只恢复某一个 Route;
  6. 配置规模治理:避免给每个 Envoy 推送与它无关的全部资源;
  7. 身份与加密:xDS 和 SDS 通道同样需要 TLS、身份认证和权限边界。

如果团队没有能力维护这些语义,不要因为“动态配置很高级”就自研 xDS 控制面。Kubernetes 场景优先评估 Envoy Gateway 或成熟 Service Mesh。


六、高阶流量治理:四层防线必须一起设计

生产稳定性不是打开一个 retry_on: 5xx。至少要把超时、重试、熔断和负载卸载放在同一张图上设计。

6.1 第一层:Deadline 与 Timeout

常见超时包括:

  • 连接上游的 connect_timeout
  • 整个 Route 的总 timeout
  • 单次尝试的 per_try_timeout
  • 请求或响应没有进展的 idle_timeout
  • HTTP Connection Manager 的 stream_idle_timeout
  • 长连接和连接池的空闲超时。

一个可执行的预算示例:

客户端 Deadline 2000 ms
└─ 网关排队与处理 100 ms
└─ 第一次上游尝试 700 ms
└─ 退避 50 ms
└─ 第二次上游尝试 700 ms
└─ 返回链路与余量 450 ms

外层 Deadline 必须大于内部所有尝试和网络余量,但不能无限大。流式接口尤其需要注意:Envoy Route Timeout 默认语义针对完整响应,官方文档特别提醒,长流通常应关闭总 Route Timeout,并使用 Stream Duration 或 Idle Timeout 约束“长期无进展”。

6.2 第二层:有预算的重试

重试适合短暂网络错误、连接失败和明确可安全重放的操作。不要默认重试所有 5xx,更不要在网关、SDK 和服务内部同时无限重试。

retry_policy:
retry_on: "connect-failure,reset,refused-stream,retriable-status-codes"
retriable_status_codes: [503]
num_retries: 2
per_try_timeout: 300ms
retry_back_off:
base_interval: 25ms
max_interval: 250ms

生产规则:

  • GET、HEAD 等幂等请求更适合自动重试;
  • POST 只有在存在 Idempotency Key 或业务幂等约束时才考虑;
  • 使用 Retry Budget 或 max_retries 熔断限制重试比例;
  • 总 Route Timeout 包含所有重试;
  • 监控“下游请求数”和“上游尝试数”的比值;
  • 遇到容量故障时,重试可能把一次故障放大成重试风暴。

6.3 第三层:Circuit Breaker

Envoy 的 Circuit Breaker 是每个 Cluster、每个 Priority 的分布式资源上限,常见阈值包括:

  • 最大上游连接数;
  • 最大等待请求数;
  • 最大并发请求数;
  • 最大并发重试数;
  • 最大连接池数量。

熔断不是为了让请求成功,而是为了让系统在容量耗尽时快速、可预测地失败,保护上游和 Envoy 自身不进入级联故障。

阈值不能照抄示例。应从后端实例数、每实例安全并发、连接池模型、协议和峰值流量反推,并通过压测验证。

6.4 第四层:Outlier Detection 与主动健康检查

主动健康检查回答“探测请求是否成功”;Outlier Detection 回答“这个实例处理真实业务流量时,是否显著异常”。

两者结合时需要防止过度摘除:

  • max_ejection_percent 限制最多摘除多少实例;
  • base_ejection_time 决定基础隔离时间;
  • consecutive_5xx 不应小到被短暂抖动触发;
  • 恢复流量可以配合 Slow Start,避免刚恢复的实例瞬间被打满;
  • 剩余实例必须有接管容量,否则“摘除坏实例”会变成“压垮好实例”。

6.5 Envoy 自身的 Overload Manager

Circuit Breaker 主要保护上游;Overload Manager 保护 Envoy 进程自身,可以监测内存、CPU 或文件描述符压力,并采取关闭 Keep-Alive、停止接受新请求、拒绝新连接、缩短超时或释放堆内存等动作。

overload_manager:
refresh_interval: 0.25s
resource_monitors:
- name: envoy.resource_monitors.cgroup_memory
typed_config:
"@type": type.googleapis.com/envoy.extensions.resource_monitors.cgroup_memory.v3.CgroupMemoryConfig
max_memory_bytes: 1073741824
actions:
- name: envoy.overload_actions.disable_http_keepalive
triggers:
- name: envoy.resource_monitors.cgroup_memory
threshold:
value: 0.90
- name: envoy.overload_actions.stop_accepting_requests
triggers:
- name: envoy.resource_monitors.cgroup_memory
threshold:
value: 0.95

阈值必须结合容器 Memory Limit、Envoy 实际堆占用和突发流量测试确定。不要把 max_memory_bytes 设置得高于容器真正可用内存,否则可能先被 OOM Kill,Overload Manager 根本来不及工作。


七、灰度、会话保持、镜像与故障注入

7.1 按权重灰度

route:
weighted_clusters:
clusters:
- name: orders_v1
weight: 95
- name: orders_v2
weight: 5

适合整体抽样灰度,但同一个用户的多个请求可能落到不同版本。

7.2 按 Header 精准灰度

- match:
prefix: /api/orders
headers:
- name: x-canary
string_match:
exact: "true"
route:
cluster: orders_v2

- match:
prefix: /api/orders
route:
cluster: orders_v1

适合内部账号、自动化测试或指定租户。生产上必须防止外部用户伪造灰度 Header,通常由可信入口先完成身份校验,再写入内部 Header。

7.3 一致性哈希

购物车、会话缓存或分片场景可以使用 Ring Hash 或 Maglev,根据 Cookie、Header 或源地址生成 Hash Key。它能减少 Endpoint 变化时的重映射,但不能替代真正的共享会话存储;扩缩容、故障摘除仍会改变映射。

7.4 Traffic Mirroring

镜像流量可以把生产请求的副本发送给新版本,用于兼容性验证。镜像响应不会返回给用户,但副本依然会产生真实副作用。因此写请求必须在影子环境禁用外部通知、扣费、发货等动作,或由业务显式识别 Shadow 请求。

7.5 Fault Injection

Envoy 可以按比例注入延迟或错误,用于验证超时、熔断和降级链路。故障注入 Filter 不应对所有人开放,建议同时具备:

  • 只允许测试身份或指定 Header 触发;
  • 限制最大比例和最长持续时间;
  • 变更审计与自动过期;
  • 明确的停止按钮;
  • 与真实故障指标区分的标签。

八、安全:TLS 只是起点

8.1 下游 TLS 与上游 TLS 是两段连接

Envoy 可以在 Listener 终止客户端 TLS,也可以在连接上游时重新发起 TLS:

Client -- TLS A --> Envoy -- TLS B / mTLS --> Backend

不要因为入口有 HTTPS,就默认 Envoy 到后端也是加密的。

8.2 生产 TLS 基线

  • 校验证书链和 SAN,不只检查“握手成功”;
  • 明确最小 TLS 版本与允许的 Cipher;
  • 内部可控链路优先使用 mTLS;
  • 证书和私钥不要写入普通 ConfigMap 或镜像;
  • 使用 SDS、Secret 挂载或工作负载身份系统完成轮换;
  • 监控证书到期时间和 SDS 更新失败;
  • xDS、SDS、外部鉴权和限流服务本身也要加密认证。

官方 SDS 支持控制面推送证书,Envoy 可以在不重启数据面的情况下切换新证书。若使用文件轮换,采用新目录加原子切换 Symlink 的方式,避免读到只更新一半的证书与私钥。

8.3 HTTP 边缘安全基线

Envoy 官方 Edge Proxy 最佳实践特别建议:

  • use_remote_address: true,并明确可信代理跳数;
  • 拒绝含下划线的 Header,避免不同组件解释不一致;
  • 开启 Path Normalization 和 Slash 合并;
  • 对转义斜杠选择明确策略;
  • 限制请求 Header、Body、连接和 Buffer;
  • 对 HTTP/2、HTTP/3 设置并发 Stream 上限;
  • Admin 只绑定 localhost 或隔离管理网络;
  • 入口认证、授权、限流采用清晰的 Fail-Open / Fail-Closed 策略。

鉴权服务不可用时是否放行,不存在统一答案:支付、管理后台通常 Fail-Closed;低风险公共读取接口可能选择受限 Fail-Open。关键是显式配置、分路由决策,并为降级行为设置指标和告警。


九、生产部署选择:原生 Envoy 还是 Envoy Gateway

条件推荐路径
单机、VM、边缘节点,配置变化少原生 Envoy + 静态配置 + Hot Restart
VM 集群且已有服务注册中心原生 Envoy + 成熟或自研 xDS 控制面
Kubernetes 南北向网关Envoy Gateway + Gateway API
Kubernetes 东西向治理、统一身份Istio 等成熟 Mesh,Envoy 作为数据面
只需要一个很简单的静态反向代理先评估更简单的代理,避免过度建设

9.1 单独部署 Envoy 完全可行

答案是可以,而且这是 Envoy 的原生使用方式之一。Envoy Proxy 是独立的数据平面进程,不要求 Envoy Gateway、Istio、Kubernetes 或 Service Mesh 才能启动。只要提供一份 Bootstrap 配置,它就可以直接运行在物理机、VM、Docker、边缘节点或普通 Kubernetes Deployment 中。

Envoy 独立部署生产拓扑:无需 Service Mesh,多个 Envoy 实例位于负载均衡之后,通过静态配置流水线或 xDS 接收配置,并连接后端与可观测平台

不过要区分两个概念:

  • 独立部署:不依赖 Gateway 或 Mesh 控制面,由团队直接管理 Envoy;
  • 单实例部署:只有一个 Envoy 进程,进程、主机或可用区故障都会中断入口流量。

前者在生产上完全成立,后者通常只适合开发、测试或可以接受短暂停机的内部系统。生产入口更常见的结构是:外部 L4 Load Balancer 后面放置至少两个 Envoy 实例,跨节点或可用区部署;Envoy 实例保持无状态,共享同一套经过版本化的配置,但不共享进程内连接池和运行状态。

9.2 独立 Envoy 的三种配置管理方式

方式工作方式适用范围主要代价
静态 BootstrapListener、Route、Cluster 和 Endpoint 全部写入启动配置路由少、变化慢的 VM 或边缘节点变更通常需要滚动替换或 Hot Restart
静态 Bootstrap + 文件型动态资源Bootstrap 固定,RDS/CDS 等资源从本地文件读取并原子替换已有配置分发系统、变化频率中等需要自己保证文件完整性、顺序和回滚
静态 Bootstrap + xDSBootstrap 只声明 Node 身份和控制面地址,资源由 xDS 下发实例多、路由多、Endpoint 高频变化需要维护控制面 HA、ACK/NACK、版本和权限

并不是一旦独立部署就必须自研 xDS。一个务实的演进顺序是:

  1. 先用静态配置跑通数据路径,把配置和 Envoy 镜像一起版本化;
  2. 用 CI 执行 Schema、envoy --mode validate、集成测试和安全策略检查;
  3. 通过负载均衡逐台摘流,滚动替换 Envoy 实例,保留上一版本快速回滚;
  4. 当 Route、Cluster 或 Endpoint 的变化速度超过静态发布能力时,再把对应资源迁移到文件订阅或 xDS;
  5. 证书需要高频轮换时,单独引入 SDS,不必一次性把所有配置都动态化。

9.3 独立部署的生产高可用基线

如果 Envoy 是公网或核心内部流量入口,至少补齐下面这些能力:

  1. 入口冗余:两个以上 Envoy 实例位于 L4 Load Balancer、Anycast 或 DNS 流量管理之后;
  2. 故障域隔离:实例跨主机、机架或可用区,剩余实例容量能够接管故障域流量;
  3. 健康检查:业务 Listener 和管理接口分离,负载均衡器只探测专用健康端点,不把完整 Admin 暴露出去;
  4. 配置供应链:配置进入 Git,发布前验证,分批生效,并记录配置版本、ACK/NACK 和回滚证据;
  5. 证书生命周期:使用 SDS 或原子文件轮换,监控证书过期和加载失败;
  6. 优雅摘流:发布前停止接收新流量,触发 Listener Drain,等待长连接退出,再终止旧进程;
  7. 容量保护:根据后端安全并发设置 Circuit Breaker,根据 Envoy 内存和连接模型配置 Overload Manager;
  8. 统一观测:集中采集所有实例的 Metrics、Access Logs、Traces、Response Flags 和配置版本。

单机上的最小发布门禁可以从下面三项开始:

# 1. 发布前拒绝非法配置
envoy --mode validate -c /etc/envoy/envoy.yaml

# 2. 启动后只从本机检查 Admin Readiness
curl -fsS http://127.0.0.1:9901/ready

# 3. 替换实例前由本机 Supervisor 触发优雅 Drain
curl -fsS -X POST 'http://127.0.0.1:9901/drain_listeners?graceful'

第三步之后还要由 Supervisor 或发布系统观察活跃连接,并在 Drain Deadline 到达后结束旧进程。不要让外部发布系统直接访问 Admin API;更安全的做法是在主机本地运行受限的部署代理或进程管理器。

9.4 什么时候应该升级到 Envoy Gateway 或成熟控制面

下面这些信号说明“自己管理原生 Envoy”的成本开始超过收益:

  • Kubernetes 中有多个团队频繁创建和修改域名、证书与 Route;
  • 需要标准化的 GatewayClass、Gateway、HTTPRoute 权限和状态模型;
  • Envoy 实例数量很大,需要按租户或区域做精细配置下发;
  • 团队正在重复实现服务发现、证书轮换、策略 CRD、状态回写和配置回滚;
  • 需要 Service Mesh 提供的工作负载身份、东西向 mTLS 和统一服务治理。

这时引入 Envoy Gateway 或成熟 Service Mesh,不是因为 Envoy 不能单独工作,而是为了把控制面运维从业务团队手里收敛到一个标准化平台。

Envoy 的 Hot Restart 可以在新进程初始化完成后接管监听 Socket,再让旧进程 Drain 已有连接。但已有连接不会迁移到新进程,只能在 Drain 窗口内自然完成或最终终止。

Kubernetes 中通常不需要自己拼 Hot Restart 容器模型;更常见的是多副本 RollingUpdate、Readiness 摘流和 Graceful Drain。


十、Kubernetes 高可用实战:Envoy Gateway

一个生产网关至少要分别考虑三层高可用:

  1. 外部入口层:云负载均衡或硬件 LB;
  2. Envoy 数据平面:真正承载请求的 Proxy Pod;
  3. Envoy Gateway 控制平面:观察 Gateway API 并生成 xDS 配置。

Envoy Gateway 生产高可用网络拓扑:用户流量经过全局流量管理和云负载均衡进入三个可用区的 Envoy Proxy,控制面通过 xDS 下发配置,遥测统一进入可观测平台

这张拓扑图强调的是故障域分离,而不只是把副本数改成 3:

  • 外部流量先经过 DNS 或 Global Traffic Manager,再由区域负载均衡只转发到健康的 Envoy 实例;
  • Envoy Proxy 数据面跨三个可用区分布,任一区域失效时,其余区域仍有接管容量;
  • Envoy Gateway 控制面采用独立副本并通过 Leader Election 协调,但它不进入业务数据路径;
  • HPA 负责容量扩缩,PDB 约束自愿中断,Topology Spread 控制副本分散,Graceful Drain 处理发布和缩容期间的存量连接;
  • Prometheus、OpenTelemetry 和日志平台需要跨实例聚合证据,避免只观察单个 Pod 得出错误结论。

图片表达部署边界,下面的 Mermaid 图则保留可复制、可维护的逻辑拓扑:

10.1 安装两个控制面副本

helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version v1.9.1 \
--namespace envoy-gateway-system \
--create-namespace \
--set deployment.replicas=2

kubectl wait --timeout=5m \
--namespace envoy-gateway-system \
deployment/envoy-gateway \
--for=condition=Available

Envoy Gateway Kubernetes Provider 默认使用 Leader Election。两个副本解决控制面进程或节点故障,但控制面副本增加并不等于数据面副本增加,需要单独配置 EnvoyProxy。

升级前必须核对 Gateway API CRD 兼容矩阵。官方 1.9.1 文档特别要求升级时先升级兼容的 CRD,再升级 Envoy Gateway Controller,顺序反了可能导致资源无法协调。

10.2 创建高可用 EnvoyProxy 策略

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: production-edge
namespace: edge-system
spec:
provider:
type: Kubernetes
kubernetes:
envoyDeployment:
container:
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
pod:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
gateway.envoyproxy.io/owning-gateway-name: production-edge
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
gateway.envoyproxy.io/owning-gateway-name: production-edge
envoyHpa:
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
envoyPDB:
minAvailable: 2
shutdown:
drainTimeout: 90s
minDrainDuration: 15s
healthCheckFailureDelay: 5s

这里的值是一个起点,不是通用答案:

  • minReplicas: 3 让三个可用区各有机会放置一个实例;
  • PDB minAvailable: 2 限制节点维护等自愿中断期间至少保留两个;
  • HPA 以 CPU 为起点,但大流量网关最好补充 RPS、活跃连接或并发 Stream 等自定义指标;
  • DoNotSchedule 要求集群确实存在足够可用区和容量,否则 Pod 会 Pending;
  • terminationGracePeriodSeconds 必须大于 Drain Timeout 和清理余量;
  • PDB 不能阻止节点宕机等非自愿中断,也不会替代多可用区部署。

应用前先检查 CRD 是否支持字段:

kubectl explain envoyproxy.spec.provider.kubernetes.envoyHpa
kubectl explain envoyproxy.spec.provider.kubernetes.envoyPDB
kubectl apply --server-side --dry-run=server -f envoy-proxy.yaml
kubectl apply -f envoy-proxy.yaml

10.3 创建 GatewayClass、Gateway 和灰度 Route

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: eg-production
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-edge
namespace: edge-system
spec:
gatewayClassName: eg-production
infrastructure:
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: production-edge
listeners:
- name: https
port: 443
protocol: HTTPS
hostname: api.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: api-example-com-tls
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: orders
namespace: edge-system
spec:
parentRefs:
- name: production-edge
sectionName: https
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /orders
backendRefs:
- name: orders-v1
port: 8080
weight: 95
- name: orders-v2
port: 8080
weight: 5

应用并检查资源状态,而不是只看 kubectl apply 返回成功:

kubectl apply -f gateway.yaml

kubectl get gatewayclass eg-production -o yaml
kubectl -n edge-system get gateway production-edge -o yaml
kubectl -n edge-system get httproute orders -o yaml
kubectl -n envoy-gateway-system get deploy,pod,svc,hpa,pdb

重点查看 status.conditionsAcceptedProgrammedResolvedRefs 等条件。声明已经写入 API Server,不代表 Listener 已经建立,也不代表 Backend 引用有效。

10.4 优雅关闭为什么重要

Envoy Gateway 的 Shutdown Manager 在 Pod 终止时会:

  1. 触发 Listener Graceful Drain;
  2. 让 Readiness 失败,从负载均衡目标中摘流;
  3. 监控剩余连接;
  4. 等连接归零或达到 Drain Timeout 后退出。

HTTP/1.1 会通过 Connection: close 引导客户端重连,HTTP/2 会发送 GOAWAY。对于 WebSocket、gRPC Stream、SSE 和大文件上传,90 秒未必够;需要从真实连接时长分布决定 Drain Timeout,而不是盲目追求“永不断连”。


十一、可观测性:必须同时观察流量、代理、上游与控制面

11.1 四类信号

层次关键问题代表指标或证据
下游请求用户是否成功RPS、4xx/5xx、P50/P95/P99、活跃连接
Envoy 自身代理是否过载CPU、内存、FD、Worker 饱和、Overload Action
上游服务后端是否健康Healthy Endpoint、连接失败、超时、重试、Outlier Ejection
控制面配置是否一致xDS 连接、ACK/NACK、Update Rejected、配置版本

建议至少启用结构化 Access Log、Prometheus Metrics、OpenTelemetry Trace、Envoy 应用日志和控制面协调指标。

Access Log 推荐保留:

  • x-request-id 或 Trace ID;
  • 下游与上游协议;
  • Route、Cluster、Endpoint;
  • 响应码与 Response Flags;
  • 总耗时和上游耗时;
  • 重试次数;
  • TLS、SNI 或身份信息中的非敏感部分。

不要记录 Authorization、Cookie、Token、完整查询参数或个人敏感信息。

11.2 Envoy Gateway 接入 Prometheus

Envoy Gateway 默认提供控制面和数据面指标。已有 Prometheus Operator 时,可以用 PodMonitor 抓取 Proxy 的 metrics 端口和 /stats/prometheus 路径;官方示例中的 Proxy Metrics 端口为 19001

排障时可以临时端口转发:

ENVOY_POD_NAME=$(kubectl get pod \
-n envoy-gateway-system \
-l gateway.envoyproxy.io/owning-gateway-name=production-edge \
-o jsonpath='{.items[0].metadata.name}')

kubectl -n envoy-gateway-system \
port-forward pod/${ENVOY_POD_NAME} 19001:19001

curl -s http://127.0.0.1:19001/stats/prometheus | head

11.3 一组实用告警

告警意义避免的误判
5xx 比例持续升高用户错误率恶化区分 Envoy Local Reply 和 Upstream 5xx
UFUHUTUO 增长连接失败、无健康实例、超时或溢出结合 Cluster 和 Route 维度
重试率或上游/下游请求比上升故障被重试掩盖或放大不只看最终成功率
Healthy Endpoint 下降后端容量减少结合发布与健康检查日志
xDS Update Rejected / NACK新配置未生效告警中携带资源名和版本
Envoy 内存接近上限OOM 与负载卸载风险同时看 Overload Manager 是否触发
活跃连接逼近容量新连接失败风险按协议和 Listener 拆分
证书即将过期TLS 中断风险区分 Listener 与 Upstream 证书

十二、性能调优:不要相信脱离配置的 QPS 数字

官方性能建议明确指出,不存在一个可以代表 Envoy 的统一 QPS 或延迟数字。结果会被下面因素显著改变:

  • HTTP/1.1、HTTP/2、HTTP/3 或 TCP;
  • 短连接、Keep-Alive、WebSocket 或 gRPC Stream;
  • TLS、mTLS 和证书算法;
  • Filter 数量以及是否调用外部服务;
  • Access Log 格式与 Sink;
  • 请求体和响应体大小;
  • Worker 数、CPU Pinning、NUMA 和网卡;
  • Endpoint 数、连接池和负载均衡算法;
  • 重试、镜像和 Trace 采样比例。

12.1 压测方法

  1. 使用 Release 镜像和固定版本;
  2. 复刻生产协议、Filter Chain、TLS 与消息大小;
  3. 同时记录客户端、Envoy、宿主机和上游指标;
  4. 从低负载阶梯式提高,找到延迟拐点,而不是只找最大 QPS;
  5. 测正常、单实例故障、可用区故障和控制面断开;
  6. 测发布 Drain 期间长连接是否按预期结束;
  7. 以 P99、错误率、CPU、内存和连接数共同定义容量;
  8. 保留满足故障接管所需的容量余量,具体值由业务 SLO 和容灾模型决定。

12.2 --concurrency 不要随意设置

Envoy 默认按可见逻辑 CPU 数创建 Worker。容器环境必须确认 CPU Limit、CPUSet 和 Envoy 实际看到的核心数一致。Worker 太少无法利用 CPU,太多则增加连接池、内存和上下文切换成本。

对少量超长 HTTP/2 或 gRPC 连接,还要关注连接是否均匀分配到 Worker;仅提高 Worker 数不保证已有长连接重新平衡。


十三、发布与回滚:配置变更和二进制升级是两种风险

13.1 配置发布流水线

原生 Envoy 静态配置至少执行:

envoy --mode validate -c /etc/envoy/envoy.yaml

Envoy Gateway 配置至少执行:

kubectl apply --server-side --dry-run=server -f gateway.yaml
egctl x translate --from gateway-api --to xds -f gateway.yaml

egctl 的具体参数随版本变化,流水线应固定与 Envoy Gateway 相同版本并以该版本帮助信息为准。

13.2 二进制升级

  • 阅读目标版本的 Breaking Change、Deprecated 和 Security Notes;
  • 先升级非关键环境,再升级一小组数据面;
  • 检查所有扩展、Wasm、Lua、Dynamic Module 的兼容性;
  • 保持旧镜像和旧配置可快速回滚;
  • 观察配置拒绝、连接重建、TLS 握手和延迟;
  • 不要同时升级控制面、数据面、证书体系和所有流量策略。

13.3 CRD 升级不是普通应用升级

Helm 对 /crds 目录中的 CRD 不会像普通模板一样自动更新。Envoy Gateway 升级必须先核对官方兼容矩阵和版本迁移说明,保存现有资源,先升级 CRD,再升级 Controller,并验证现有 Route 状态和数据面配置。


十四、生产故障演练清单

高可用不是 YAML 属性,而是经过验证的系统行为。上线前至少演练以下故障。

14.1 后端实例故障

操作:终止一个 Backend Pod 或让健康接口返回失败。

验证:Endpoint 多久被摘除、窗口内错误数、是否出现重试风暴、剩余实例 CPU 和延迟,以及恢复后是否需要 Slow Start。

14.2 Envoy Proxy Pod 故障

操作:随机删除一个 Proxy Pod。

验证:LoadBalancer 摘除速度、连接中断数量、Deployment 补副本速度、PDB 和 Topology Spread 是否生效。

14.3 节点或可用区故障

操作:在测试环境隔离节点,或使用云平台故障演练能力。

验证:是否仍有跨区 Proxy、外部 LB 是否只发送到健康目标、后端是否有足够跨区容量、跨区带宽和延迟是否成为新瓶颈。

14.4 控制面不可用

操作:停止 Envoy Gateway Controller 或隔离 xDS 通道。

验证:已有数据面是否继续使用最后接受的配置、新 Proxy 是否能启动并获得配置、配置变更是否明确失败、xDS 断连告警是否触发、恢复后是否自动收敛。

14.5 错误配置

操作:向测试组下发引用不存在 Cluster 的 Route 或非法字段。

验证:Envoy 是否 NACK、旧配置是否继续工作、告警是否包含版本和资源名、自动化是否停止扩大发布。

14.6 证书轮换

操作:轮换 Listener、Upstream 和 xDS/SDS 证书。

验证:新旧连接是否连续、SAN 和信任根切换顺序是否正确、失败时是否可回滚、到期和加载失败指标是否有效。

14.7 过载与负载卸载

操作:逐步提高连接数、请求并发或大 Body 比例。

验证:Circuit Breaker 是否先于上游崩溃触发、Overload Manager 是否先于 OOM 触发、拒绝是否快速且可识别、故障解除后是否自动恢复。


十五、常见反模式

反模式 1:没有超时,只配重试

没有总时间预算的重试会堆积请求,并把后端变慢放大成整个系统的资源耗尽。

反模式 2:健康检查只返回进程存活

进程活着但依赖全部不可用时,继续接流量只会稳定地产生错误。Readiness 应反映“是否适合接收新流量”,Liveness 才回答“是否需要重启”。

反模式 3:把 Admin 暴露给 Prometheus 所在的大网络

Admin 不只是 Metrics 接口。优先使用独立 Metrics Listener、端口转发、Sidecar 或严格 Network Policy,不要把完整 Admin 当成普通监控端点。

反模式 4:所有路由共享同一套重试和超时

查询接口、支付写入、文件上传、SSE 与 gRPC Stream 的语义完全不同,应按路由分类设计。

反模式 5:HPA 只看 CPU,最小副本是 1

长连接、TLS 握手或连接数可能先成为瓶颈。一个副本也无法在滚动升级、节点维护或突发故障时提供高可用。

反模式 6:控制面 HA,数据面单副本

两个 Controller 不能替一个承载流量的 Envoy Pod 接管请求。控制面和数据面必须分别设计容量与容灾。

反模式 7:用流量镜像测试真实写操作

影子请求如果没有业务隔离,可能重复发消息、扣库存或触发第三方调用。

反模式 8:直接复制官方 Edge 配置上线

官方示例给出安全方向,不知道你的连接时长、Body、内存、协议和容量。所有阈值都必须经压测和故障接管测试校准。


十六、一份可执行的生产检查表

架构

  • 明确 Envoy 是 Edge、Sidecar、Egress 还是内部 LB;
  • 控制面和数据面分别有高可用设计;
  • 至少跨节点部署,关键入口跨可用区;
  • 依赖的 DNS、xDS、SDS、鉴权和限流服务有独立容灾;
  • 单区故障后剩余容量仍满足 SLO。

流量策略

  • 每类 Route 有明确 Timeout;
  • 重试只用于可安全重放的错误与请求;
  • 配置 Retry Budget 或重试熔断;
  • Circuit Breaker 阈值来自容量模型;
  • 主动健康检查和 Outlier Detection 经过故障测试;
  • 长连接、流式响应和大文件有独立策略。

安全

  • 下游和上游 TLS 分别建模;
  • 校验证书链与 SAN;
  • 证书可以自动轮换并有到期告警;
  • Admin 不暴露到公网或普通业务网络;
  • Path、Header、Body 和 Buffer 有限制;
  • 鉴权与限流依赖有明确 Fail-Open / Fail-Closed 策略。

Kubernetes

  • 数据面最小副本满足故障域要求;
  • HPA 有正确 Resource Request,并验证扩容速度;
  • PDB、Topology Spread、RollingUpdate 配置一致;
  • Drain Timeout 与 terminationGracePeriodSeconds 匹配;
  • Gateway、Route 和 Backend 的 Status Condition 进入预期状态;
  • CRD 与 Envoy Gateway 版本兼容。

可观测与发布

  • Access Log、Metrics、Trace 能用 Request ID 关联;
  • 区分 Local Reply 与 Upstream Error;
  • 监控 ACK/NACK、配置版本和 Update Rejected;
  • 配置与镜像都能独立灰度、独立回滚;
  • 已完成 Backend、Proxy、Node、Zone、Control Plane 和证书演练;
  • Runbook 包含常见 Response Flag 和排障命令。

十七、总结:Envoy 的价值是可编程的可靠性边界

学习 Envoy 最容易走进两个极端:要么只停留在反向代理 YAML,要么一开始就陷入 xDS Proto 和控制面实现。

更有效的路径是分四步:

  1. 用静态配置掌握 Listener、Filter、Route、Cluster 和 Endpoint;
  2. 加入超时、重试、熔断、健康检查和异常摘除,并主动制造故障;
  3. 建立 Metrics、Access Log、Trace、配置版本和告警体系;
  4. 当配置规模和变化频率真正需要时,再引入 Envoy Gateway、Service Mesh 或自研 xDS。

Envoy 不会自动让系统高可用。真正的高可用来自:多个独立故障域、足够的接管容量、受控的失败方式、可回滚的配置、可观察的状态,以及反复演练过的恢复流程。

当这些部分都存在时,Envoy 才不只是一个代理,而是服务与故障之间的一道可编程可靠性边界。


官方资料

Logo
RainLib

Exploring the frontiers of technology, design, and distributed systems. Building tools for the future developers.

Suggestions & Feedback

© 2026 RainLib. Built for the Future.
All rights reserved.
System Normal