匹配 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 篩選器),可以使用包裝器 proto 來實例化與包裝結構相關聯的匹配篩選器

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_filtersecond-header 設定為 foobar,則略過篩選器。

HTTP 篩選器迭代影響

以上範例僅示範如何匹配請求標頭,這最終是最簡單的情況,因為它發生在相關篩選器收到任何資料之前。支援匹配其他 HTTP 輸入來源(例如回應標頭),但需要討論這如何在篩選器層級運作。

目前,HTTP 篩選器的匹配評估完全不會影響控制流程:如果沒有足夠的資料來執行匹配,則會照常將回呼傳送至相關篩選器。一旦有足夠的資料來匹配動作,就會將其提供給篩選器。這樣做的結果是,如果篩選器想要閘道一些關於匹配結果的行為,它必須自行管理停止迭代。

當涉及到 SkipFilter 等動作時,這表示如果略過條件不是基於請求標頭,則篩選器可能會被部分套用,這可能會導致令人驚訝的行為。一個範例是,擁有一個匹配樹,嘗試根據回應標頭略過 gRPC-Web 篩選器:用戶端假設如果他們將 gRPC-Web 請求傳送至 Envoy,則篩選器會在將其代理到上游之前將其轉換為 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