4. Envoy深度解析:Envoy核心概念、Listener与Cluster、Filter链机制、xDS协议

好,咱们进入第四章。Envoy,这是整个服务网格的数据面核心。说白了,你前面学的Istio控制面再牛,最后干活儿的还是Envoy。今天我就带你把它扒开看看,里面到底是怎么转的。

4.1 Envoy的核心概念:它到底是个啥?

Envoy不是普通的反向代理。它是个七层网络代理,专门为云原生环境设计的。我刚开始接触它的时候,第一反应是“这不就是个Nginx吗?”后来发现,差远了。

Envoy有几个核心身份:

  • L3/L4/L7代理:从TCP到HTTP/gRPC,它全管
  • 可编程的Filter链:每个请求进来,走一串过滤器,像流水线一样
  • 动态配置:通过xDS协议,配置可以热更新,不用重启

我个人习惯把Envoy理解成一个“可编程的流量路由器”。你给它配好Listener和Cluster,它就知道从哪接流量、往哪发流量。

核心要点:Envoy的配置模型是“向下监听,向上转发”。Listener负责接客,Cluster负责找后端。

4.2 Listener与Cluster:流量的一进一出

咱们先看Listener。Listener就是Envoy监听的端口或Unix Socket。比如你配一个Listener监听80端口,所有到80的流量就都进了Envoy。

一个典型的Listener配置长这样:

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 80
    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: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: service_cluster
          http_filters:
          - name: envoy.filters.http.router

你看,这个Listener做了三件事:

  1. 监听0.0.0.0:80
  2. 用HTTP连接管理器处理请求
  3. 把请求路由到service_cluster这个Cluster

Cluster呢?它就是后端服务的集合。Envoy会把流量负载均衡到Cluster里的各个Endpoint上。

clusters:
- name: service_cluster
  connect_timeout: 0.25s
  type: STRICT_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: service_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 10.0.0.1
              port_value: 8080
      - endpoint:
          address:
            socket_address:
              address: 10.0.0.2
              port_value: 8080

这里我提个坑。我曾经在生产环境遇到过一个问题:Cluster的type配成了STATIC,结果后端扩容了,Envoy死活不认新Pod。后来改成STRICT_DNS或者EDS才解决。嗯,动态发现才是云原生的正确姿势。

小技巧:如果你用Kubernetes,Cluster类型建议用EDS(Endpoint Discovery Service),这样Pod上下线Envoy能自动感知。

4.3 Filter链机制:请求的流水线加工

Filter链,这是Envoy最灵活的地方。每个请求进来,会经过一串Filter,每个Filter可以做自己的事:解析、修改、拦截、转发。

Filter分两层:

  • 网络层Filter(L4):处理原始TCP连接,比如TLS终止、TCP代理
  • HTTP层Filter(L7):处理HTTP请求,比如路由、限流、故障注入

你想想看,这就像一条流水线。每个Filter是一个工位,请求从第一个工位走到最后一个。任何一个Filter说“停”,请求就停了。

我举个例子,一个常见的HTTP Filter链:

http_filters:
- name: envoy.filters.http.fault
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.Fault
    abort:
      http_status: 503
      percentage:
        numerator: 10
        denominator: HUNDRED
- name: envoy.filters.http.router

这个配置的意思是:10%的请求直接返回503,剩下的走正常路由。我在做混沌工程的时候,就靠这个来模拟服务故障。

注意:Filter的顺序很重要!比如限流Filter必须在路由Filter之前,否则限流就失效了。我曾经因为顺序搞反,排查了半天。

4.4 xDS协议:Envoy的“神经中枢”

xDS,全称是“Discovery Service”。它是一组API的统称,包括:

  • LDS:Listener Discovery Service,发现Listener配置
  • RDS:Route Discovery Service,发现路由规则
  • CDS:Cluster Discovery Service,发现Cluster配置
  • EDS:Endpoint Discovery Service,发现后端实例
  • SDS:Secret Discovery Service,发现证书

说白了,xDS就是Envoy和控制面之间的“对话协议”。控制面(比如Istiod)通过xDS把配置推给Envoy,Envoy收到后立即生效,不用重启。

我画个简单的流程:

控制面 (Istiod)  --xDS-->  Envoy
  |
  |-- LDS: "监听8080端口"
  |-- RDS: "/api/* 路由到 service-a"
  |-- CDS: "service-a 有3个实例"
  |-- EDS: "实例IP: 10.0.0.1, 10.0.0.2, 10.0.0.3"

为什么xDS这么重要?因为它是动态配置的基础。没有xDS,你改个路由还得重启Envoy,这在生产环境是不可接受的。

我记得有一次,线上服务需要紧急切流量。我直接在控制面改了路由规则,几秒钟后所有Envoy都收到了新配置,流量就切过去了。整个过程零停机。这就是xDS的价值。

核心总结:Listener决定“从哪接”,Cluster决定“往哪发”,Filter链决定“怎么处理”,xDS决定“怎么动态更新”。这四个概念,就是Envoy的骨架。

4.5 实战:用Envoy做个简单的反向代理

光说不练假把式。咱们写个完整的Envoy配置,跑一个反向代理。

static_resources:
  listeners:
  - name: ingress
    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: backend
          http_filters:
          - name: envoy.filters.http.router
  clusters:
  - name: backend
    connect_timeout: 0.25s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: backend
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.example.com
                port_value: 80

这个配置做了啥?Envoy监听10000端口,所有请求都转发到www.example.com:80。你启动Envoy后,访问localhost:10000,就能看到example.com的内容。

嗯,这里要注意:type: STRICT_DNS会让Envoy定期解析域名,如果IP变了,它会自动更新。这就是动态发现的雏形。

动手试试:把type改成STATIC,然后改一下www.example.com的IP,看看Envoy会不会自动感知?答案是不会。这就是为什么生产环境要用xDS。

好了,Envoy的核心概念就讲到这里。下一章咱们会深入Istio,看看它怎么用Envoy来实现服务网格。到时候你会发现,理解了Envoy,Istio就懂了一半。