在 Envoy Proxy 中,HTTP 路由是通过一个 HTTP 路由过滤器(Router Filter)来实现的,该过滤器可以执行高级路由任务。HTTP 路由机制允许 Envoy 将传入的 HTTP 请求映射到上游集群(服务),从而充当反向代理或构建服务网格(通常通过在 Host/Authority HTTP 头上进行路由以到达特定的上游服务集群)。

HTTP 路由的结构

  1. Router Filter(路由过滤器):Envoy 的 HTTP 路由是通过一个名为 Router Filter 的过滤器实现的,该过滤器可以在 HTTP 连接管理器中配置,以便在请求到达上游服务之前进行高级路由决策。

  2. 虚拟主机(Virtual Hosts)和集群(Clusters):虚拟主机将域名(Domain)或授权(Authority)映射到一组路由规则。虚拟主机的配置可以包含正则表达式匹配,用于生成集群级别的额外统计信息。

  3. 路径、前缀和头匹配(Path, Prefix, and Header Matching):支持基于前缀或精确路径的匹配(区分大小写和不区分大小写),以及更复杂的正则表达式路径匹配。可以根据任意请求头匹配路由。

  4. 路径和主机重写(Path and Host Rewriting):支持重写请求的路径或主机信息,允许进行自动主机重写(基于所选上游主机的 DNS 名称)或显式主机重写。

  5. 请求重定向(Request Redirection):支持基于路径或主机的请求重定向,以及虚拟主机级别的 TLS 重定向。

  6. 请求超时、重试和对冲(Request Timeouts, Retries, and Hedging):支持在路由配置或 HTTP 请求头级别配置请求重试和超时。还可以配置请求对冲,在请求超时时触发多个上游请求,并返回第一个符合条件的响应。

  7. 流量转移与拆分(Traffic Shifting and Splitting):支持通过运行时值在不同的上游集群之间转移流量,或通过基于权重/百分比的路由在多个上游集群之间拆分流量。

  8. 策略路由(Policy-Based Routing):支持基于优先级或哈希策略的路由,以便进行高级流量管理。

  9. 直接响应(Direct Responses):可以发送非代理的 HTTP 响应(直接响应),不需要将请求代理到上游服务器。

  10. 路由作用域(Route Scope):支持限定路由搜索空间,通过 Scoped Route Configuration 限制搜索空间以优化性能。

路由匹配过程

在 Envoy 中进行 HTTP 路由匹配时,主要依赖于一系列规则和步骤来决定一个请求应该被转发到哪一个后端。

下图展示了具体的匹配过程。

image
HTTP 路由匹配过程

要点:

  • Envoy 首先根据 host:authority 头来匹配虚拟主机。
  • 在虚拟主机中,路由条目按顺序检查,直到找到一个匹配的路由。
  • 如果路由较多,可以通过路由匹配器来提高匹配效率。
  • 虚拟集群用于更细粒度的流量监控。

1. 基于主机(Host)或 :authority 头的匹配

Envoy 首先会检查 HTTP 请求中的 host:authority 头,然后将它与虚拟主机(virtual host)进行匹配。虚拟主机定义了一组路由规则,用于处理特定的域名或主机名。

2. 路由条目的匹配

接下来,Envoy 会根据匹配的虚拟主机,按顺序检查其中定义的路由条目(route entry)。如果找到匹配的路由,Envoy 将直接使用该路由来处理请求,而不会再继续检查其他路由。

3. 路由匹配器的使用

在一些场景下,如果有大量路由条目,逐个匹配会显得效率较低。这时,虚拟主机可以使用路由匹配器(matcher entry)。通过构建高效的匹配树结构,Envoy 可以快速找到合适的路由。

4. 虚拟集群(Virtual Cluster)独立匹配

独立于路由条目的匹配,Envoy 还可以根据虚拟集群(virtual cluster)的配置来匹配请求。这些虚拟集群通常用于监控或统计,可以帮助你更好地观察不同路径或请求模式的流量。

路由规则示例

以下是一个简单的路由匹配示例配置:

virtual_hosts:
  - name: backend
    domains:
      - "example.com"
    routes:
      - match:
          prefix: "/"
        route:
          cluster: service_backend

在这个配置中:

  • domains: example.com 代表虚拟主机将匹配所有请求的 Hostexample.com 的流量。
  • routes: 定义了请求路径前缀为 / 的路由规则,将该请求路由到 service_backend 集群。

HTTP 路由的工作原理

  1. 接收请求:Envoy 监听传入的 HTTP 请求。

  2. 路由匹配:根据请求头(如 HostPath)匹配相应的路由规则。通过虚拟主机和路径匹配等规则选择目标上游集群。

  3. 连接池获取:匹配到合适的上游集群后,Envoy 获取到该集群中某个主机的连接池。

  4. 请求转发:将请求转发到所选的上游集群中的一个主机。

  5. 响应处理:上游服务处理请求后,返回响应,Envoy 通过已建立的连接将响应返回给客户端。

下图展示的是 HTTP 路由结构:

image
HTTP 路由结构
  • HTTP Connection Manager (HCM):管理 HTTP 连接的组件,负责调用路由过滤器。
  • Router Filter:执行路由决策的过滤器,基于请求特征选择目标上游集群。
  • Match Virtual Host and Route:根据虚拟主机和路由规则匹配请求路径和头。
  • Path, Prefix, Header Matching:执行路径、前缀、头匹配,找到最佳路由。
  • Route Action:确定路由操作(如重定向、超时、重试等)。
  • Acquire Connection Pool:获取到上游集群中某个主机的连接池以建立连接。
  • Forward Request to Upstream Cluster:将请求转发到选定的上游集群中的一个主机。
  • Receive Response from Upstream:接收来自上游服务的响应。
  • Return Response to Client:将响应返回给客户端。
  • Direct Responses, Request Redirection, Traffic Shifting/Splitting, Request Timeout and Retries:不同的路由操作,如直接响应、请求重定向、流量拆分、请求重试等。

HTTP 路由示例

为了更好地理解 Envoy 中的 HTTP 路由机制,下面我们通过一个简单的配置示例,展示如何设置基本的 HTTP 路由规则。该示例将演示如何根据请求的路径将流量路由到不同的上游集群。

配置示例

以下是一个基本的 Envoy 配置文件,展示了如何根据请求路径将流量路由到不同的服务集群:

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
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
              log_format:
                text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(:PATH)% %PROTOCOL%\" %RESPONSE_CODE% \"%RESPONSE_FLAGS%\"\n"
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            name: local_route
            virtual_hosts:
            - name: service_virtual_host
              domains: ["*"]
              routes:
              - match:
                  prefix: "/serviceA"
                route:
                  cluster: service_A_cluster
                  prefix_rewrite: "/"
              - match:
                  prefix: "/serviceB"
                route:
                  cluster: service_B_cluster
                  prefix_rewrite: "/"
              - match:
                  prefix: "/"
                route:
                  cluster: default_service_cluster
  clusters:
  - name: service_A_cluster
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_A_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: serviceA.example.com
                port_value: 80
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: serviceA.example.com
  - name: service_B_cluster
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: service_B_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: serviceB.example.com
                port_value: 80
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: serviceB.example.com
  - name: default_service_cluster
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: default_service_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: default.example.com
                port_value: 80
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: default.example.com
admin:
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 9901

解释

监听器(Listener)

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
      ...
  • address: Envoy 在所有网络接口(0.0.0.0)的 10000 端口上监听传入的 HTTP 请求。
  • filter_chains: 使用 http_connection_manager 过滤器来管理 HTTP 连接。

HTTP 连接管理器(HttpConnectionManager)

typed_config:
  "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  stat_prefix: ingress_http
  access_log:
  - name: envoy.access_loggers.stdout
    ...
  http_filters:
  - name: envoy.filters.http.router
    ...
  route_config:
    name: local_route
    virtual_hosts:
    - name: service_virtual_host
      domains: ["*"]
      routes:
      - match:
          prefix: "/serviceA"
        route:
          cluster: service_A_cluster
          prefix_rewrite: "/"
      - match:
          prefix: "/serviceB"
        route:
          cluster: service_B_cluster
          prefix_rewrite: "/"
      - match:
          prefix: "/"
        route:
          cluster: default_service_cluster
  • access_log: 将访问日志输出到标准输出(stdout),日志格式包含请求方法、路径、协议、响应状态码等信息。
  • http_filters: 使用 router 过滤器来执行路由决策。
  • route_config: 定义路由配置,包含一个虚拟主机和多个路由规则。

虚拟主机(Virtual Host)和路由规则(Routes)

virtual_hosts:
- name: service_virtual_host
  domains: ["*"]
  routes:
  - match:
      prefix: "/serviceA"
    route:
      cluster: service_A_cluster
      prefix_rewrite: "/"
  - match:
      prefix: "/serviceB"
    route:
      cluster: service_B_cluster
      prefix_rewrite: "/"
  - match:
      prefix: "/"
    route:
      cluster: default_service_cluster
  • domains: 匹配所有域名("*")。
  • routes: 定义了三个路由规则:
    • 第一个路由

      • match.prefix: 匹配以 /serviceA 开头的路径。
      • route.cluster: 将匹配的请求路由到 service_A_cluster 集群。
      • prefix_rewrite: 将请求路径重写为 /,即移除 /serviceA 前缀。
    • 第二个路由

      • match.prefix: 匹配以 /serviceB 开头的路径。
      • route.cluster: 将匹配的请求路由到 service_B_cluster 集群。
      • prefix_rewrite: 将请求路径重写为 /,即移除 /serviceB 前缀。
    • 第三个路由

      • match.prefix: 匹配所有以 / 开头的路径(即默认路由)。
      • route.cluster: 将匹配的请求路由到 default_service_cluster 集群。

集群(Clusters)

clusters:
- name: service_A_cluster
  type: LOGICAL_DNS
  dns_lookup_family: V4_ONLY
  load_assignment:
    cluster_name: service_A_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: serviceA.example.com
              port_value: 80
  transport_socket:
    name: envoy.transport_sockets.tls
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
      sni: serviceA.example.com

- name: service_B_cluster
  ...
  
- name: default_service_cluster
  ...
  • service_A_cluster:

    • type: 使用逻辑 DNS 方式发现上游服务。
    • dns_lookup_family: 仅使用 IPv4 地址进行 DNS 解析。
    • load_assignment: 定义上游服务的地址和端口,此处为 serviceA.example.com:80
    • transport_socket: 配置 TLS 传输套接字,使用 SNI 指定服务器名称为 serviceA.example.com
  • service_B_clusterdefault_service_cluster:

    • 配置方式与 service_A_cluster 类似,分别指向不同的上游服务地址。

管理接口(Admin Interface)

admin:
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 9901
  • address: 管理接口仅绑定到本地地址 127.0.0.1 的 9901 端口,确保只能本地访问,提升安全性。

工作流程

  1. 接收请求

    • Envoy 在 0.0.0.0:10000 端口监听传入的 HTTP 请求。
  2. 路由匹配

    • 根据请求的路径前缀 /serviceA/serviceB,Envoy 选择对应的集群 service_A_clusterservice_B_cluster
    • 如果请求路径不匹配上述前缀,则使用默认路由将请求转发到 default_service_cluster
  3. 路径重写

    • 在将请求转发到上游集群之前,Envoy 会将请求路径中的前缀 /serviceA/serviceB 重写为 /,确保上游服务接收到的路径是预期的。
  4. 请求转发

    • Envoy 通过配置的集群信息,将请求转发到对应的上游服务地址 serviceA.example.comserviceB.example.comdefault.example.com
  5. 响应处理

    • 上游服务处理请求后,响应通过 Envoy 返回给客户端。

示例场景

假设有以下上游服务:

  • Service A:运行在 serviceA.example.com:80,提供 / 路径下的功能。
  • Service B:运行在 serviceB.example.com:80,提供 / 路径下的功能。
  • Default Service:运行在 default.example.com:80,作为默认服务处理所有其他请求。

通过上述配置:

  • 当客户端请求 http://envoy_proxy:10000/serviceA/api 时,Envoy 会将请求路由到 serviceA.example.com:80,并将路径重写为 /api
  • 当客户端请求 http://envoy_proxy:10000/serviceB/home 时,Envoy 会将请求路由到 serviceB.example.com:80,并将路径重写为 /home
  • 当客户端请求 http://envoy_proxy:10000/about 时,Envoy 会将请求路由到 default.example.com:80,路径保持为 /about

总结

通过这个简单的示例,我们展示了如何在 Envoy 中配置基本的 HTTP 路由规则,根据请求路径将流量路由到不同的上游服务集群。关键点包括:

  • 路由匹配:基于请求路径前缀进行匹配,选择合适的集群。
  • 路径重写:在转发请求前,重写请求路径以符合上游服务的预期。
  • 集群配置:定义上游服务的地址、端口和传输配置,确保与上游服务的安全通信。