HTTP

简要概述

Envoy 具有一个内置的网络层过滤器,称为 HTTP 连接管理器,它实现了大量的 HTTP 特定功能,接口定义在文件 http_connection_manager.proto 中。

因为是网络层过滤器,所以使用上是配置在 “filter_chains” 且类型名称为:

- name: envoy.filters.network.http_connection_manager
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager

示例配置

static_resources:
  listeners:
  - name: listener_0
    address:
       ...
    # 监听器过滤器
    listener_filters:
       ...
    # 网络层过滤器
    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
       ...

  clusters:
    ...

功能特点

  1. HTTP 协议原生支持:HTTP/1.1, HTTP/2 and HTTP/3
  2. HTTP 头清理:为安全原因,会自动修正一些请求头
  3. HTTP 路由表配置:支持静态或者动态 RDS 接口
  4. HTTP 重试插件配置:多种充实机制可供选择

功能定义在:

github.com/envoyproxy/envoy/api/envoy/extensions/retry
  1. HTTP 内部重定向
  2. HTTP 超时处理

HTTP 过滤器

过滤器类型

类似网络层过滤器链(filter_chains),在 HTTP 连接管理器内,还支持专门处理该层协议的过滤器链配置(http_filters)。

HTTP 级别过滤器的API允许过滤器在不了解底层协议的情况下运行。

与网络级别过滤器一样,HTTP过滤器可以停止并继续迭代到后续过滤器。这允许更复杂的场景,如处理健康检查、调用速率限制服务、缓冲、路由、为应用程序流量生成DynamoDB等的统计信息。

编写过滤器无需过多底层协议知识,主要有三种类型:

  1. Decoder(解码器)

当连接管理器解码请求流的部分(请求头、请求体和尾部)时,将调用解码器过滤器。

  1. Encoder(编码器)

当连接管理器即将对部分响应流(响应头、响应体和尾部)进行编码时,将调用编码器过滤器。

  1. Decoder/Encoder (解码器/编码器)

当连接管理器解码请求流的部分以及当连接管理器即将编码响应流的部分时,将同时调用解码器/编码器过滤器。

过滤器执行顺序

假设以下三个过滤器均是 “Decoder/Encoder” 类型:

http_filters:
- name: example.filters.a
  ...
- name: example.filters.b
  ...
- name: example.filters.c
  ...

当请求进入后,连接管理器按照以下顺序调用解码器过滤器:

example.filters.a
->
example.filters.b
->
example.filters.c

响应用户时按照相反的顺序调用编码器过滤器:

example.filters.c
->
example.filters.b
->
example.filters.a

HTTP 路由规则

基础结构

基础路由规则一般适合应用在处理边缘流量(传统的反向代理请求处理)以及服务端至服务端间组成的网格,它的配置在 “route_config” 部分:

static_resources:
  listeners:
  - name: listener_0
    address:
       ...
    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

          # 路由配置
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:

注意 “scoped_routes” 与 “route_config” 只能二选一,见以下 proto 定义文件。

虚拟主机和集群

将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:

  1. 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
  2. 匹配下游请求的所有 url 地址,配置 “routes.match[0].prefix” 为 “/”
route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"
      route:
        cluster: oneops-syncds-v1

上游集群 “oneops-syncds-v1”:

  clusters:
  - name: oneops-syncds-v1
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    load_assignment:
      cluster_name: oneops-syncds-v1
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.0.1
                port_value: 8080

客户端请求重定向

将匹配以下规则的请求转发重定向特定站点:

  1. 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
  2. 匹配下游请求的 url 前缀为 “/baidu”,配置 “routes.match[0].prefix” 为 “/baidu”
  3. 重定向站点配置 “host_redirect” 为 “www.baidu.com
  4. 重定向站点使用 https 配置 “https_redirect” 为 true
route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    # 支持 "NONE" "EXTERNAL_ONLY" "ALL"
    require_tls: NONE
    routes:
    - match:
        prefix: "/baidu"
      redirect:
        https_redirect: true
        host_redirect: www.baidu.com
        path_redirect: /

服务端响应 301 重定向:

*   Trying 192.168.0.2:80...
* Connected to 192.168.0.2 (192.168.0.2) port 80 (#0)
> GET /baidu HTTP/1.1
> Host: 192.168.0.2
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< location: https://192.168.0.2/baidu
< date: Mon, 22 Jan 2024 08:45:29 GMT
< server: envoy
< content-length: 0
<
* Connection #0 to host 192.168.0.2 left intact

路径前缀与请求匹配

将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:

  1. 匹配下游请求的所有域名,域名支持正则,配置 “domains” 为 “*”
  2. 匹配下游请求的 url 为 “/",配置 “routes[0].match.prefix” 为 “/”
  3. 匹配下游请求的 url 不区分大小写,配置 “routes[0].match.case_sensitive” 为 “true”
  4. 匹配下游请求头中 “user-agent” 值为 “SpecialClient”
route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"
        case_sensitive: true
        headers:
          - name: user-agent
            exact_match: "SpecialClient"
      route:
        cluster: oneops-syncds-v1
curl -vvv -H "user-agent: SpecialClient" http://192.168.0.2:80

路径前缀与主机重写

将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:

  1. 匹配下游请求为特定域名,配置 “domains” 为 “test.example.com”
  2. 匹配下游请求的 url 为 “/",配置 “routes[0].match.prefix” 为 “/”
route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["test.example.com"]
    routes:
    - match:
        prefix: "/"
      route:
        cluster: oneops-syncds-v1
        prefix_rewrite: "/ds/"
        host_rewrite_literal: "app.example.com"

同时转发上游集群时重写:

  1. 参数 “prefix_rewrite” 重写原始请求前缀为 “/ds/”
  2. 参数 “host_rewrite_literal” 重写原始请求域名为 “app.example.com”

请求超时重试与对冲

TODO;

未成功实现

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"
      route:
        cluster: oneops-syncds-v1
        retry_policy:
          retry_on: connect-failure,refused-stream,retriable-status-codes
          num_retries: 5
          retriable_status_codes:
          - "500"
          - "502"
          - "503"

请求流量分流与转移

根据权重负载到多个上游集群

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"
      route:
        weighted_clusters:
          clusters:
          - name: oneops-syncds-v1
            weight: 2
          - name: oneops-syncds-v2
            weight: 8

在两个上游集群进行流量转移

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"

        runtime_fraction:
          default_value:
            numerator: 20

      route:
        cluster: oneops-syncds-v1
    - match:
        prefix: "/"
      route:
        cluster: oneops-syncds-v2

支持基于策略的路由

TODO;

支持直接响应客户端

通过指令 “direct_response” 配置响应内容,这样无需从上游集群获取数据。

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"

      direct_response:
        status: 200
        body:
          inline_string: "hello world"

非 TLS 请求绝对转发

将匹配以下规则的请求转发至上游集群 “oneops-syncds-v1”:

  1. 匹配下游请求的所有域名,配置 “domains” 为 “*”
  2. 匹配下游请求的所有 url 地址,配置 “routes.match[0].prefix” 为 “/”
route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]
    routes:
    - match:
        prefix: "/"
      route:
        cluster: oneops-syncds-v1

通用匹配树指定路由表

使用 matcher 为通用匹配树,相比 routes 更具表达力的匹配引擎,允许在任意头部执行线性匹配,它提供了更大的灵活性,使得可以根据请求的各种头部信息进行更复杂的路由决策。

route_config:
  name: local_route
  virtual_hosts:
  - name: local_service
    domains: ["*"]

    matcher:
      matcher_tree:
        input:
          name: request-headers
          typed_config:
            "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
            header_name: :path
        exact_match_map:
          map:
            "/ds/v1":
              action:
                name: route_ds_v1
                typed_config:
                  "@type": type.googleapis.com/envoy.config.route.v3.Route
                  match:
                    prefix: /
                  route:
                    cluster: oneops-syncds-v1
            "/ds/v2":
              action:
                name: route_ds_v1
                typed_config:
                  "@type": type.googleapis.com/envoy.config.route.v3.Route
                  match:
                    prefix: /
                  route:
                    cluster: oneops-syncds-v2

详细使用见官方文档 Generic Matching

HTTP 路由作用域

基础结构

路由作用域(Route Scope)用于将特定路由规则与某个作用域(scope)相关联,每个请求都会计算一个作用域键(scope key),并根据这个键来选择相应的路由规则,涉及以下两个概念:

  1. Scope Key(作用域键): 对于每个请求,HTTP 连接管理器会动态计算一个作用域键,这个键用于决定选择哪个路由表。作用域键的计算可能基于请求的某些属性或上下文信息。
  2. Route Table(路由表): 路由表是一组与特定作用域键相关联的路由规则。每个路由表定义了一组路由规则,当请求的作用域键与路由表相关联时,Envoy 将使用这个路由表来确定请求的最终路由。
static_resources:
  listeners:
  - name: listener_0
    address:
       ...
    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

          # 路由作用域
          scoped_routes:
            name: scope_by_addr
            scope_key_builder:
              ......
            scoped_route_configurations_list:
              scoped_route_configurations:
                 ......

注意 “scoped_routes” 与 “route_config” 只能二选一,在 “http_connection_manager.proto” 文件中定义的 “oneof route_specifier {}” 类型。

特定请求头

scoped_routes:
  name: scope_by_addr
  scope_key_builder:
    fragments:
    - header_value_extractor:
	    name: Addr
	    element_separator: ";"
	    element:
		  key: x-foo-key
		  separator: "="

  scoped_route_configurations_list:
    scoped_route_configurations:
    - on_demand: true
	  name: scoped_route_0
	  key:
	    fragments:
	    - string_key: bar
	  route_configuration:
	    name: local_route
	    virtual_hosts:
	    - name: local_service
		  domains: ["*"]
		  routes:
		  - match:
			  prefix: "/"
		    route:
			  cluster: oneops-syncds-v2
			  prefix_rewrite: "/ds/"

内置键中缺少片段(视为 NULL)会使请求无法匹配任何范围,即找不到该请求的路由条目。

HTTP 动态正向代理

下游使用明文访问

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        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: dynamic_forward_proxy_cluster
          http_filters:
          - name: envoy.filters.http.dynamic_forward_proxy
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
              dns_cache_config:
                name: dynamic_forward_proxy_cache_config
                dns_lookup_family: V4_ONLY
                typed_dns_resolver_config:
                  name: envoy.network.dns_resolver.cares
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: dynamic_forward_proxy_cluster
    lb_policy: CLUSTER_PROVIDED
    cluster_type:
      name: envoy.clusters.dynamic_forward_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
        dns_cache_config:
          name: dynamic_forward_proxy_cache_config
          dns_lookup_family: V4_ONLY
          typed_dns_resolver_config:
            name: envoy.network.dns_resolver.cares
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        common_tls_context:
          validation_context:
            trusted_ca:
              filename: /etc/ssl/certs/ca-certificates.crt

下游使用 TLS 加密访问

static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 443
    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
          ......
          http_filters:
          - name: envoy.filters.http.dynamic_forward_proxy
          ......

      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
          common_tls_context:
            tls_certificates:
            # The following self-signed certificate pair is generated using:
            # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out  a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy'
            #
            # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy
            # via filename. Reference: https://envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource.
            #
            # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via
            # Secret Discovery Service (SDS). Reference: https://envoyproxy.io/docs/envoy/latest/configuration/security/secret.
            - certificate_chain:
                inline_string: |
                  -----BEGIN CERTIFICATE-----

                  -----END CERTIFICATE-----                  
              private_key:
                inline_string: |
                  -----BEGIN PRIVATE KEY-----

                  -----END PRIVATE KEY-----                  

  clusters:
  - name: dynamic_forward_proxy_cluster
    ......



最后修改 2024.01.30: docs: udpate envoy (c9017e6)