匹配 API

Envoy 使用一个 匹配 API 来允许各个子系统表达应该基于传入数据执行的操作。

匹配 API 被设计为树状结构,以允许进行亚线性匹配算法,从而实现比 Envoy HTTP 路由中看到的线性列表匹配更好的性能。它大量使用扩展点,使其易于扩展到基于协议或环境数据以及自定义亚线性匹配器和直接匹配器的不同输入。

输入和匹配算法

匹配输入定义了一种提取用于匹配的输入值的方法。输入函数是上下文敏感的。例如,HTTP 标头输入仅适用于 HTTP 上下文,例如用于匹配 HTTP 请求。

HTTP 输入函数

这些输入函数可用于匹配 HTTP 请求

网络输入函数

这些输入函数可用于匹配 TCP 连接、UDP 数据报和 HTTP 请求

这些输入函数可用于匹配 TCP 连接和 HTTP 请求

这些输入函数可用于匹配 TCP 连接

SSL 输入函数

这些输入函数可用于匹配 TCP 连接和 HTTP 请求

通用输入函数

这些输入函数在任何上下文中都可用

自定义匹配算法

除了内置的精确和前缀匹配器之外,这些自定义匹配器在某些上下文中可用

匹配操作

匹配器框架中的操作通常指按名称选择的资源。

网络过滤器链匹配支持以下扩展

action:
  name: foo
  typed_config:
    "@type": type.googleapis.com/envoy.config.core.v3.SubstitutionFormatString
    text_format_source:
      inline_string: "%DYNAMIC_METADATA(com.test_filter:test_key)%"

过滤器集成

在受支持的环境中(目前仅限 HTTP 过滤器),可以使用包装器协议来实例化与包装结构关联的匹配过滤器

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    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
          http_filters:
          - name: with-matcher
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
              extension_config:
                name: envoy.filters.http.fault
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
                  abort:
                    http_status: 503
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
                  delay:
                    fixed_delay: 3s
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
              xds_matcher:
                matcher_tree:
                  input:
                    name: request-headers
                    typed_config:
                      "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
                      header_name: some-header
                  exact_match_map:
                    # Note this additional indirection; this is a workaround for Protobuf oneof limitations.
                    map:
                      some_value_to_match_on:  # This is the header value we're trying to match against.
                        action:
                          name: skip
                          typed_config:
                            "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            virtual_hosts:
            - name: default
              domains: ["*"]
              routes:
              - match: {prefix: "/"}
                route:
                  cluster: service_foo
  clusters:
  - name: service_foo
    load_assignment:
      cluster_name: some_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080

以上示例将 HTTP 过滤器(HTTPFault 过滤器)包装在 ExtensionWithMatcher 中,允许我们定义一个匹配树,该匹配树将与包装过滤器的评估一起进行评估。在数据可用之前,它将被提供给匹配树,然后匹配树将尝试使用提供的数据评估匹配规则,如果匹配评估导致操作,则会触发操作。

在上面的示例中,我们指定要匹配传入请求标头 some-header,方法是将 input 设置为 HttpRequestHeaderMatchInput 并配置要使用的标头键。使用此标头中包含的值,提供的 exact_match_map 指定我们关心的值:我们已经配置了一个值 (some_value_to_match_on) 来匹配。因此,此配置意味着如果我们收到包含 some-header: some_value_to_match_on 作为标头的请求,则将解析 SkipFilter 操作(导致相关 HTTP 过滤器被跳过)。如果没有这样的标头,则不会解析任何操作,过滤器将照常应用。

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    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
          http_filters:
          - name: with-matcher
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
              extension_config:
                name: envoy.filters.http.fault
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
                  abort:
                    http_status: 503
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
                  delay:
                    fixed_delay: 3s
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
              xds_matcher:
                # The top level matcher is a matcher tree which conceptually selects one of several subtrees.
                matcher_tree:
                  input:
                    name: request-headers
                    typed_config:
                      "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
                      header_name: some-header
                  exact_match_map:
                    # Note this additional indirection; this is a workaround for Protobuf oneof limitations.
                    map:
                      some_value_to_match_on:  # This is the header value we're trying to match against.
                        # The OnMatch resulting on matching with this branch of the exact matcher is another matcher,
                        # allowing for recursive matching.
                        matcher:
                          # The inner matcher is a matcher list, which attempts to match a list of predicates.
                          matcher_list:
                            matchers:
                            - predicate:
                                or_matcher:
                                  predicate:
                                  - single_predicate:
                                      input:
                                        name: request-headers
                                        typed_config:
                                          "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
                                          header_name: second-header
                                      value_match:
                                        exact: foo
                                  - single_predicate:
                                      input:
                                        name: request-headers
                                        typed_config:
                                          "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
                                          header_name: second-header
                                      value_match:
                                        exact: bar
                              on_match:
                                action:
                                  name: skip
                                  typed_config:
                                    "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            virtual_hosts:
            - name: default
              domains: ["*"]
              routes:
              - match: {prefix: "/"}
                route:
                  cluster: service_foo
  clusters:
  - name: service_foo
    load_assignment:
      cluster_name: some_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080

以上是一个稍微复杂的示例,它将顶层树匹配器与线性匹配器结合起来。虽然树匹配器提供了非常有效的匹配,但它们并不十分灵活。列表匹配器可以用来提供更丰富的匹配 API,并且可以按任意顺序与树匹配器结合使用。该示例描述了以下匹配逻辑:如果 some-header: skip_filter 存在且 second-header 设置为 foobar,则跳过过滤器。

HTTP 过滤器迭代影响

以上示例只演示了对请求标头的匹配,由于它发生在相关过滤器接收任何数据之前,因此最终成为最简单的情况。支持对其他 HTTP 输入源进行匹配(例如响应标头),但需要对这在过滤器级别如何工作进行一些讨论。

目前,HTTP 过滤器的匹配评估不会影响控制流程:如果可用数据不足以进行匹配,则会像往常一样向相关过滤器发送回调。一旦有足够的数据可以匹配操作,就会提供给过滤器。这样做的一个结果是,如果过滤器希望对匹配结果进行一些行为限制,它必须自行管理停止迭代。

当涉及到像 SkipFilter 这样的操作时,这意味着如果跳过条件基于除请求标头以外的任何内容,过滤器可能会被部分应用,这可能会导致意外行为。一个例子是,有一个匹配树尝试基于响应标头跳过 gRPC-Web 过滤器:客户端假设如果它们向 Envoy 发送 gRPC-Web 请求,过滤器将在将该请求代理到上游之前将其转换为 gRPC 请求,然后在编码路径上将其转换回 gRPC-Web 响应。通过基于响应标头跳过过滤器,将发生正向转换(上游接收到 gRPC 请求),但响应永远不会转换回 gRPC-Web。结果,客户端将从 Envoy 收到无效的响应。如果跳过操作是在尾部解析的,则同一个 gRPC-Web 过滤器将消耗所有数据,但永远不会将其写回(因为这发生在它看到尾部时),导致 gRPC-Web 响应具有空主体。

HTTP 路由集成

匹配 API 可以与 HTTP 路由一起使用,方法是将匹配树指定为虚拟主机的一部分,并指定一个 RouteRouteList 作为结果操作。见 示例 如何配置匹配树。

匹配树验证

由于匹配树结构非常灵活,一些过滤器可能需要对可以使用哪些匹配树进行额外限制。此系统目前有点不灵活,只支持将输入源限制为特定集合。例如,过滤器可能会指定它只适用于请求标头:在这种情况下,尝试匹配请求尾部或响应标头的匹配树将在配置加载期间失败,并报告哪个数据输入无效。

这是为了限制 上一节 中讨论的问题,或者帮助用户了解匹配树可以在什么上下文中用于特定过滤器。由于当前验证框架的限制,它没有用于所有过滤器。

对于 HTTP 过滤器,限制由过滤器实现指定,因此请参阅各个过滤器文档以了解是否存在限制。

例如,在下面的示例中,匹配树不能与仅允许使用 HttpRequestHeaderMatchInput 的过滤器一起使用。

static_resources:
  listeners:
  - address:
      socket_address:
        address: 0.0.0.0
        port_value: 443
    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
          http_filters:
          - name: with-matcher
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
              extension_config:
                name: envoy.filters.http.fault
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
                  abort:
                    http_status: 503
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
                  delay:
                    fixed_delay: 3s
                    percentage:
                      numerator: 0
                      denominator: HUNDRED
              xds_matcher:
                matcher_list:
                  matchers:
                  - predicate:
                      or_matcher:
                        predicate:
                        - single_predicate:
                            input:
                              name: request-headers
                              typed_config:
                                "@type": type.googleapis.com/envoy.type.matcher.v3.HttpRequestHeaderMatchInput
                                header_name: request-header
                            value_match:
                              exact: foo
                        - single_predicate:
                            input:
                              name: request-headers
                              typed_config:
                                "@type": type.googleapis.com/envoy.type.matcher.v3.HttpResponseHeaderMatchInput
                                header_name: response-header
                            value_match:
                              exact: bar
                    on_match:
                      action:
                        name: skip
                        typed_config:
                          "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
          route_config:
            virtual_hosts:
            - name: default
              domains: ["*"]
              routes:
              - match: {prefix: "/"}
                route:
                  cluster: service_foo
  clusters:
  - name: service_foo
    load_assignment:
      cluster_name: some_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 8080