Envoy 代理深度解析
当 Airbnb 的工程师在 2016 年决定从 Nginx + Hystrix 的组合迁移到服务网格时,他们面临一个选择:自己实现边车代理,还是选择一个现成的高性能代理?
他们选择了后者,这个选择后来成为了 Envoy。Airbnb 把 Envoy 捐赠给了 Lyft,后来 Lyft 的工程师们把它发展成了今天云原生时代最重要的代理之一。
Envoy 之所以被广泛采用,是因为它在高性能和可配置性之间找到了绝佳的平衡点。
Envoy 的设计哲学
Envoy 的设计围绕三个核心目标:
- 应用透明:不需要修改应用代码,所有流量治理功能都在代理层实现
- 动态配置:配置可以从控制平面实时推送,无需重启代理
- 可观察性:内置丰富的指标、日志、追踪接口
flowchart LR
subgraph App["应用层"]
A1[服务 A]
A2[服务 B]
A3[服务 C]
end
subgraph Envoy["Envoy 代理层"]
E1[代理 A]
E2[代理 B]
E3[代理 C]
end
subgraph ControlPlane["控制平面"]
CP[Pilot / 配置中心]
end
A1 <--> E1
A2 <--> E2
A3 <--> E3
E1 <--> E2
E1 <--> E3
E2 <--> E3
CP -.->|xDS| E1 & E2 & E3
Envoy 核心概念
Envoy 的配置由几个核心概念组成:Listener(监听器)、Route(路由)、Cluster(集群)、Endpoint(端点)。
Listener:监听入口
Listener 定义了 Envoy 监听的端口和如何处理入站流量:
Listener
static_resources:
listeners:
- name: http_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
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: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/api/"
route:
cluster: backend_cluster
Cluster:上游服务定义
Cluster 定义了一组提供相同功能的上游服务实例:
Cluster
static_resources:
clusters:
- name: backend_cluster
type: STRICT_DNS
lb_policy: LEAST_REQUEST
circuit_breakers:
thresholds:
- max_connections: 100
max_pending_requests: 100
max_requests: 100
max_retries: 3
health_checks:
- timeout: 5s
interval: 10s
unhealthy_threshold: 3
healthy_threshold: 2
http_health_check:
path: "/health"
load_assignment:
cluster_name: backend_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: backend-svc.default.svc.cluster.local
port_value: 8080
Route:路由规则
Route 定义了如何把请求路由到目标集群:
Route
route_config:
name: service_route
virtual_hosts:
- name: service_vhost
domains:
- "service.example.com"
routes:
# 基于前缀匹配
- match:
prefix: "/users"
route:
cluster: user_service
timeout: 5s
retry_policy:
retry_on: "5xx,reset,connect-failure"
num_retries: 3
# 基于 header 匹配(灰度发布)
- match:
prefix: "/api"
headers:
- name: "X-Canary"
exact_match: "true"
route:
cluster: user_service_canary
weight: 30
# 重定向
- match:
prefix: "/old-path"
redirect:
path_redirect: "/new-path"
response_code: MOVED_PERMANENTLY
L7 过滤器链
Envoy 的核心能力之一是L7 过滤器链(Filter Chain)。过滤器链允许在请求处理的不同阶段插入自定义逻辑。
flowchart TB
subgraph FilterChain["HTTP 过滤器链"]
Direction[入站/出站]
Auth[认证过滤器]
RateLimit[限流过滤器]
Router[路由过滤器]
CORS[CORS 过滤器]
Tracing[追踪过滤器]
end
subgraph Request["请求处理"]
Parse[协议解析]
Match[路由匹配]
Forward[转发]
end
Direction --> Parse
Parse --> Auth --> RateLimit --> Router --> CORS --> Tracing --> Forward
常用过滤器
过滤器配置示例
外部授权过滤器
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport:
api_version: V3
grpc_service:
google_grpc:
target_uri: auth-service:50051
stat_prefix: ext_authz
with_request_body:
max_request_bytes: 8192
allow_partial_message: true
failure_mode_allow: false
本地限流过滤器
- name: envoy.filters.http.local_rate_limit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 1000
tokens_per_fill:
replenishes_on_second: 100
fill_interval: 1s
filter_enabled:
runtime_key: local_rate_limit_enabled
default_value:
numerator: 100
denominator: HUNDRED
熔断器与限流
Envoy 内置了熔断器(Circuit Breaker)和限流(Rate Limiting)功能,无需依赖外部库。
熔断器配置
熔断器配置
circuit_breakers:
thresholds:
# 普通熔断配置
- max_connections: 100 # 最大连接数
max_pending_requests: 100 # 最大等待请求数
max_requests: 100 # 最大并发请求数
max_retries: 3 # 最大重试次数
track_remaining: true
# 高优先级熔断配置(可选)
- priority: HIGH
max_connections: 200
max_pending_requests: 200
熔断器的工作原理:
- 连接熔断:当上游连接数达到
max_connections,新请求返回 503
- 等待熔断:当等待队列达到
max_pending_requests,新请求返回 503
- 请求熔断:当并发请求达到
max_requests,新请求返回 503
- 异常值剔除(Outlier Detection):连续失败的实例自动移出负载均衡池
异常值剔除配置
cluster:
name: backend_cluster
outlier_detection:
consecutive5xx: 5 # 连续 5 个 5xx,移出集群
interval: 30s # 检测间隔
base_ejection_time: 30s # 移出最少时间
max_ejection_percent: 50 # 最多移出 50% 实例
enforce_host_health_check: true # 被健康检查通过的实例不会被移出
限流配置
全局限流(Redis
- name: envoy.filters.http.global_rate_limit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimitService
failure_mode_deny: true
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: ratelimit_cluster
transport_api_version: V3
请求映射配置
- name: envoy.filters.http.local_rate_limit
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
stat_prefix: http_local_rate_limiter
token_bucket:
max_tokens: 100
tokens_per_fill:
replenishes_on_second: 10
fill_interval: 1s
descriptors:
- entries:
- key: remote_address
value: "{downstream_remote_address}"
- key: header_match
value: "{request_header:x-api-key}"
热配置更新
Envoy 最强大的特性之一是热配置更新(Hot Reload):可以在不中断连接的情况下更新配置。
xDS 动态配置
通过 xDS 协议,Envoy 可以实时接收配置更新:
sequenceDiagram
participant CP as 控制平面
participant Envoy as Envoy 代理
Note over CP,Envoy: 连接建立
Envoy->>CP: 发起 gRPC 连接,请求配置
CP-->>Envoy: 返回完整配置(LDS/RDS/CDS/EDS)
Note over CP,Envoy: 配置变更
CP->>Envoy: 推送 EDS 更新(新增实例)
Note over Envoy: 立即生效,无需重启
CP->>Envoy: 推送 RDS 更新(路由规则变更)
Note over Envoy: 立即生效,存量连接不受影响
热更新的优势
热更新配置示例
EDS
{
"version_info": "2024-01-01T00:01:00Z",
"type_url": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"resources": [{
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"cluster_name": "backend_cluster",
"endpoints": [{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "10.0.0.3",
"port_value": 8080
}
}
}
}
]
}]
}]
}
Warning
热更新不是万能的:某些配置变更仍然需要重启才能生效,比如 worker_processes、listener 的端口变更等。xDS 热更新主要针对路由、集群、端点等动态资源。
性能特性
Envoy 在设计时就把性能放在首位:
低延迟
- 单进程多线程模型:避免进程间通信开销
- 连接池复用:减少 TCP 连接建立开销
- 无锁队列:减少线程竞争
低资源消耗
零拷贝(Zero Copy)
Envoy 支持 sendfile,减少数据在内核态和用户态之间的拷贝:
启用零拷贝
static_resources:
listeners:
- name: http_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
# 启用内核级零拷贝
freebind: true
transparent: true
Envoy 的权衡矩阵
常见问题与反模式
反模式一:配置过于复杂
Envoy 的配置非常灵活,但也很容易写出过于复杂的配置。
错误示例:一个 Listener 里有 50+ 过滤器,配置层级嵌套 10 层以上。
正确做法:拆分配置,使用 <runtime> 和 <layered_runtime> 简化配置结构。
反模式二:忽略健康检查配置
不配置健康检查,熔断器无法正确工作。
正确做法:为每个 Cluster 配置合理的健康检查策略:
health_checks:
- timeout: 5s
interval: 10s
unhealthy_threshold: 3
healthy_threshold: 2
http_health_check:
path: "/health"
expected_status: 200
反模式三:限流阈值设置不当
限流阈值太高,起不到保护作用;太低,影响正常请求。
正确做法:根据压测数据和业务峰值设置阈值,并留有一定的缓冲空间(如 1.2x 峰值)。
Envoy 配置最佳实践
使用统一定义
共享定义示例
- name: common_lib
type: STATIC
connect_timeout: 5s
circuit_breakers:
thresholds:
- max_connections: 100
max_pending_requests: 100
clusters:
- name: user_service
inherit_load_balancer: common_lib
type: STRICT_DNS
load_assignment:
cluster_name: user_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: user-service
port_value: 8080
合理的超时配置
超时配置
route:
timeout: 5s
retry_policy:
retry_on: "5xx,reset,connect-failure"
per_try_timeout: 2s
num_retries: 3
idle_timeout: 30s
完善的监控配置
监控配置
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901
# 启用监控端点
stats_config:
use_all_default_tags: true
# 监控路径
route_config:
virtual_hosts:
- name: admin
domains:
- "*"
routes:
- match:
prefix: "/stats"
route:
cluster: admin_cluster
术语表
延伸思考
Envoy 的设计理念对整个服务网格生态产生了深远影响。它的核心贡献不只是「做了一个好代理」,而是定义了边车代理应该如何工作:
- 控制平面和数据平面分离:通过 xDS 协议实现
- 运行时动态配置:无需重启代理
- 丰富的可观测性:内置指标、追踪、日志接口
- L7 过滤器架构��灵活扩展处理逻辑
今天当你使用 Istio、Linkerd 或其他服务网格时,它们的数据平面大概率都是 Envoy(或基于 Envoy)。理解 Envoy 的工作原理,可以帮助你在遇到问题时快速定位、在选择服务网格时做出更明智的决策。
下一步可以思考:如果 Envoy 是边车代理,那控制平面如何管理成百上千个 Envoy 实例? 这涉及到配置分发、版本管理、故障处理等更复杂的问题。