Envoy

技术栈
工具链
微服务服务网格Sidecar代理L7负载均衡可观测性

概览

Envoy

Envoy 是 Lyft 开源的高性能 C++ L7 代理和服务网格数据平面,是 CNCF 毕业项目,也是 Istio 的默认 Sidecar Proxy。

是什么

Envoy = L7 反向代理 + 服务发现 + 负载均衡 + 可观测性 + TLS 终止。它被设计为服务网格的"数据平面",与每个服务实例并行运行(Sidecar 模式),透明地处理所有入站和出站流量。

解决什么问题

  • 东西向流量管理:服务间调用的路由、重试、超时、熔断
  • 南北向 API 网关:替代 Nginx 作为边缘代理
  • 可观测性:自动生成 L7 指标(请求量/延迟/状态码)、分布式追踪
  • 零信任安全:自动 mTLS + RBAC 授权
  • 协议升级:HTTP/2、gRPC、WebSocket 透明代理

关键特性

  • xDS 动态配置:通过控制平面热更新,无需重启
  • 非侵入式:Sidecar 模式对应用代码零侵入
  • 多协议:HTTP/1.1、HTTP/2、gRPC、TCP、MongoDB、Redis、Postgres
  • 高级负载均衡:区域感知、优先级路由、重试策略、异常检测
  • 一流可观测性:Prometheus 指标、OpenTelemetry 追踪、访问日志

安装

Envoy 安装与初始化

1. 环境准备

要求 说明
操作系统 Linux(推荐)/ macOS / Docker
Docker 推荐方式(官方不提供二进制包)
架构 x86_64 / ARM64
端口 按配置(默认 listener 端口由配置文件决定,常见 10000/8080)

2. 安装命令

方式一:Docker(推荐)

# 拉取官方镜像
docker pull envoyproxy/envoy:v1.29-latest

# 最小运行
docker run --rm -p 10000:10000 envoyproxy/envoy:v1.29-latest

方式二:Linux 手动安装

# Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y apt-transport-https gnupg2 curl lsb-release
curl -sL 'https://deb.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key' | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/envoy.gpg
echo "deb [arch=amd64] https://deb.dl.getenvoy.io/public/deb/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/envoy.list
sudo apt-get update
sudo apt-get install -y getenvoy-envoy

方式三:macOS

brew install envoy

验证安装

envoy --version
# envoy  version: .../1.29.0/.../RELEASE/BoringSSL

3. 最小配置示例

创建 envoy-demo.yaml

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      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: "/" }
                          route: { cluster: my_service }
                http_filters:
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
    - name: my_service
      type: STRICT_DNS
      connect_timeout: 5s
      load_assignment:
        cluster_name: my_service
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: httpbin.org, port_value: 80 }

运行

envoy -c envoy-demo.yaml

# 测试(新终端)
curl http://localhost:10000/anything
# 返回 httpbin.org 的响应

4. 常见安装问题

问题 解决方案
配置文件解析失败 使用 envoy --mode validate -c config.yaml 校验配置
端口已被占用 修改 listener 中的 port_value,或 lsof -i :10000 查看占用
Docker 无法访问外部服务 检查容器网络;host.docker.internal 可访问宿主机服务
macOS 网络性能差 Docker Desktop 下网络绕行,生产环境请用 Linux
权限不足 Envoy 绑定 1024 以下端口需要 root 或 CAP_NET_BIND_SERVICE
xDS 连接失败 检查控制平面地址和网络连通性;日志级别设为 debug

示例

目标

用 Envoy 搭建反向代理,实现流量分割(金丝雀发布):90% 流量到 v1,10% 到 v2。演示 L7 路由、加权分流的完整配置。

完整代码

架构

Browser → Envoy(:10000) ──90%──→ backend-v1(:8081)
                         ──10%──→ backend-v2(:8082)

1. 后端服务 v1(Python)

# backend_v1.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import os

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"version":"v1","message":"This is stable version"}')

HTTPServer(("0.0.0.0", 8081), Handler).serve_forever()

2. 后端服务 v2(Python)

# backend_v2.py
from http.server import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"version":"v2","message":"This is canary version"}')

HTTPServer(("0.0.0.0", 8082), Handler).serve_forever()

3. Envoy 配置(envoy-canary.yaml)

static_resources:
  listeners:
    - name: main_listener
      address:
        socket_address: { address: 0.0.0.0, port_value: 10000 }
      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
                route_config:
                  name: canary_routes
                  virtual_hosts:
                    - name: app
                      domains: ["*"]
                      routes:
                        # 精确匹配:/health 走 v1
                        - match: { path: "/health" }
                          route:
                            cluster: backend_v1
                        # 前缀匹配 + 超时配置
                        - match: { prefix: "/" }
                          route:
                            weighted_clusters:
                              clusters:
                                - name: backend_v1
                                  weight: 90
                                - name: backend_v2
                                  weight: 10
                              total_weight: 100
                          retry_policy:
                            retry_on: "5xx"
                            num_retries: 2
                            per_try_timeout: 1s
                http_filters:
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
    - name: backend_v1
      type: STRICT_DNS
      connect_timeout: 2s
      load_assignment:
        cluster_name: backend_v1
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: 127.0.0.1, port_value: 8081 }
      # 异常检测(熔断)
      circuit_breakers:
        thresholds:
          - priority: DEFAULT
            max_connections: 100
            max_pending_requests: 100
            max_requests: 100
            max_retries: 10
      health_checks:
        - timeout: 1s
          interval: 5s
          unhealthy_threshold: 3
          healthy_threshold: 1
          http_health_check:
            path: "/health"

    - name: backend_v2
      type: STRICT_DNS
      connect_timeout: 2s
      load_assignment:
        cluster_name: backend_v2
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address: { address: 127.0.0.1, port_value: 8082 }
      circuit_breakers:
        thresholds:
          - priority: DEFAULT
            max_connections: 100
            max_pending_requests: 100
            max_requests: 100
            max_retries: 10

4. 管理接口配置

# admin 接口(添加到 envoy-canary.yaml 顶层)
admin:
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

运行步骤

# 1. 启动后端服务
python backend_v1.py &;
python backend_v2.py &;

# 2. 启动 Envoy
envoy -c envoy-canary.yaml

# 3. 验证流量分割
for i in {1..20}; do curl -s http://localhost:10000/api; done | sort | uniq -c
# 预期: 约 18 次 v1, 2 次 v2

# 4. 查看 Envoy 管理接口
curl http://localhost:9901/stats | grep cluster.backend_v1
curl http://localhost:9901/clusters
curl http://localhost:9901/config_dump

# 5. 健康检查
curl http://localhost:10000/health

预期输出

$ for i in {1..20}; do curl -s http://localhost:10000/api; done | sort | uniq -c
     18 {"version":"v1","message":"This is stable version"}
      2 {"version":"v2","message":"This is canary version"}

# 管理接口
$ curl http://localhost:9901/clusters
# backend_v1::observability_name::backend_v1
# backend_v2::observability_name::backend_v2

关键点

  • weighted_clusters 实现加权流量分割,零代码金丝雀发布
  • retry_policy 配置自动重试,retry_on: 5xx 只在服务端错误时重试
  • circuit_breakers 防止连接池耗尽
  • health_checks 自动剔除不健康实例
  • Admin 接口提供运行时诊断能力

教程

前言

在微服务架构中,服务间通信变得极其复杂。Envoy 扮演着"透明的中间人"角色——它不改变你的代码,但完全接管了网络层。理解 Envoy 是理解 Service Mesh 的关键。


第一章:Sidecar 模式

1.1 什么是 Sidecar

传统架构:
  Service A → 直接 → Service B

Service Mesh(Sidecar):
  Service A → Envoy Sidecar → mTLS → Envoy Sidecar → Service B
              (outbound)                   (inbound)

每个服务实例旁边(同一个 Pod/VM)都有一个 Envoy,拦截所有进出流量。

1.2 为什么用 Sidecar

方面 传统 SDK Sidecar (Envoy)
语言绑定 每种语言都需 SDK 零侵入,任何语言
升级 更新所有服务 只升级 Sidecar
故障隔离 库 bug 影响服务 独立进程,故障隔离
治理能力 SDK 决定 Envoy 统一提供

第二章:Envoy 核心概念

2.1 四大组件

┌─────────────────────────────────┐
│  Listener(监听器)               │
│  ↓ 接收连接,应用 Filter Chain   │
├─────────────────────────────────┤
│  Filter(过滤器)                 │
│  ↓ HTTP/Router/RBAC/限流...     │
├─────────────────────────────────┤
│  Route(路由表)                  │
│  ↓ 匹配规则 → 选择 Cluster       │
├─────────────────────────────────┤
│  Cluster(集群)                  │
│  ↓ 负载均衡 + 熔断 → Endpoint    │
└─────────────────────────────────┘

2.2 xDS 协议

Envoy 配置通过 xDS(Discovery Service)动态下发:

xDS 全称 功能
LDS Listener DS 监听器配置
RDS Route DS 路由表
CDS Cluster DS 后端集群
EDS Endpoint DS 具体实例地址
SDS Secret DS TLS 证书

2.3 两种配置模式

静态配置(static_resources)
  → 适合开发/简单场景
  → 修改需重启

动态配置(dynamic_resources + xDS)
  → 适合生产/Service Mesh
  → 控制平面(Istio/自定义)实时下发

第三章:Envoy 高级负载均衡

3.1 负载均衡策略

# 加权轮询(默认)
lb_policy: ROUND_ROBIN

# 最少请求
lb_policy: LEAST_REQUEST

# 随机
lb_policy: RANDOM

# 一致性哈希(会话保持)
lb_policy: RING_HASH
hash_policy:
  - header: { header_name: "X-User-Id" }

# 区域感知(优先同区域)
lb_policy: ROUND_ROBIN
locality_weighted_lb_config: {}

3.2 异常检测(主动健康检查)

outlier_detection:
  consecutive_5xx: 5          # 连续 5 次 5xx → 剔除
  interval: 10s               # 每 10s 检查一次
  base_ejection_time: 30s     # 剔除 30s
  max_ejection_percent: 50    # 最多剔除 50%

3.3 优先级路由

P0: 本地 zone 的实例(低延迟)
P1: 同 region 的实例
P2: 其他 region(降级)

当 P0 健康比例 < threshold 时自动降级到 P1

第四章:可观测性

4.1 内置统计

# 查看所有指标
curl http://localhost:9901/stats

# 关键指标
cluster.backend_v1.upstream_rq_total       # 请求总数
cluster.backend_v1.upstream_rq_2xx         # 2xx 数量
cluster.backend_v1.upstream_rq_time        # 请求耗时直方图
cluster.backend_v1.upstream_cx_connect_fail # 连接失败

4.2 Prometheus 集成

# 在 admin 下配置(注意:仅在纯 Envoy 模式下)
stats_sinks:
  - name: envoy.stat_sinks.metrics_service
    typed_config:
      "@type": type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig
      transport_api_version: V3
      grpc_service:
        envoy_grpc:
          cluster_name: prometheus_stats

第五章:Envoy vs Nginx vs HAProxy

特性 Envoy Nginx HAProxy
动态配置 ✅ xDS 原生 ❌ 需 Plus 版 ✅ Runtime API
gRPC 代理 ✅ 原生 ❌ 需 Plus ❌ 有限
Service Mesh ✅ Istio 默认
可观测性 ✅ 极其丰富 ⭐ 基础 ⭐⭐
C++ 实现
Lua 扩展 ❌(WASM) ✅ Lua
学习曲线 陡峭 中等

思考题

  1. Envoy 为什么要用 xDS 动态配置而不是配置文件热加载?xDS 有什么独特优势?
  2. Sidecar 模式增加了网络链路(多一跳),如何最小化这个延迟开销?
  3. Istio 使用 Envoy 作为 Sidecar,但 Envoy 也可以独立作为 API 网关使用。这两种场景下配置有什么不同?
  4. Envoy 的异常检测和熔断有什么区别?为什么需要两个独立的机制?

下一步

  • 学习 Istio + Envoy 搭建完整 Service Mesh
  • 学习 Envoy WASM 插件开发
  • 学习 Envoy 作为 Kubernetes Ingress Controller

参考资料

暂无参考文献