Istio Ambient 模式 HBONE 隧道原理詳解 - 上

語言: CN / TW / HK

作者:趙化冰,原文地址:http://www.zhaohuabing.com/post/2022-09-11-ambient-hbone/

Istio ambient 模式採用了被稱為 HBONE [1] 的方式來連線 ztunnel 和 waypoint proxy。HBONE 是 HTTP-Based Overlay Network Environment 的縮寫。雖然該名稱是第一次看到,其實 HBONE 並不是 Istio 創建出來的一個新協議,而只是利用了 HTTP 協議標準提供的隧道能力。簡單地說,ambient 模式採用了  HTTP 的 CONNECT 方法  [2] 在 ztunnel 和 waypoint proxy 建立了一個隧道,通過該隧道來傳輸資料。本文將分析 HBONE 的實現機制和原理。

HTTP 隧道原理

建立 HTTP 隧道的常見形式是採用 HTTP 協議的 CONNECT 方法。在這種機制下,客戶端首先向 HTTP 代理伺服器傳送一個 HTTP CONNECT 請求,請求中攜帶需要連線的目的伺服器。代理伺服器根據該請求代表客戶端連線目的伺服器。和目的伺服器建立連線後,代理伺服器將客戶端 TCP 資料流直接透明地傳送給目的伺服器。在這種方式中,只有初始連線請求是 HTTP,之後代理伺服器處理的是 TCP 資料流。

HTTP CONNECT 隧道

通過這種方法,我們可以採用 HTTP CONNECT 建立一個隧道,該隧道中可以傳輸任何型別的 TCP 資料。

例如在一個內網環境中,我們只允許通過 HTTP 代理來訪問外部的 web 伺服器。但我們可以通過 HTTP 隧道的方式來連線到一個外部的 SSH 伺服器上。。

客戶端連線到代理伺服器,傳送 HTTP CONNECT 請求通過和指定主機的 22 埠建立隧道。

CONNECT for.bar.com:22 HTTP/1.1

如果代理允許連線,並且代理已連線到指定的主機,則代理將返回 2XX 成功響應。

HTTP/1.1 200 OK

現在客戶端將通過代理訪問遠端主機。傳送到代理伺服器的所有資料都將原封不動地轉發到遠端主機。

客戶端和伺服器開始 SSH 通訊。

SSH-2.0-OpenSSH_4.3\r\n
... ggg

備註:除了 HTTP CONNECT 以外,採用 HTTP GET 和 POST 也可以建立 HTTP 隧道,這種方式建立的隧道的原理是將 TCP 資料封裝到 HTTP 資料包中傳送到外部伺服器,該外部伺服器會提取並執行客戶端的原始網路請求。外部伺服器收到此請求的響應後,將其重新打包為 HTTP 響應,併發送回客戶端。在這種方式中,客戶端所有流量都封裝在 HTTP GET 或者 POST 請求中。

Envoy 的 Internal Listener 機制

我們知道, socket [3] 在作業系統核心接收網路資料,但 Envoy 還支援一種 “使用者空間 socket”。 Internal Listener [4] 就用於從該 “使用者空間 socket” 接收資料包。

Internal Listener 需要和一個 Cluster 一起使用,配置在 Cluster 中作為接收流量的 endpoint。如下所示:

定義一個 Internal Listener:

name: demo_internal_listener
internal_listener: {}
filter_chains:
- filters: [
  ......
]

採用一個 Cluster 來連線 Egress Listener 和 Internal Listener。該 Cluster 配置在 Egress Listener 的 HCM 中,其 endpoint 是 Internal Listener 的 name。

name: encap_cluster
load_assignment:
  cluster_name: encap_cluster
  endpoints:
  - lb_endpoints:
    - endpoint:
        address:
          envoy_internal_address:
            server_listener_name: demo_internal_listener

通過這種方式, 可以將兩個 Listener 串聯起來,第一個 Listener 從作業系統核心接收網路資料,然後再經過 interal_listener_cluster 傳遞給 demo_internal_listener 處理,如下面的配置所示:

name: ingress
address:
  socket_address:
    protocol: TCP
    address: 127.0.0.1
    port_value: 9999
filter_chains:
- filters:
  - name: tcp
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
      stat_prefix: ingress
      cluster: encap_cluster

Envoy 的 HTTP Tunnel

我們可以採用 Envoy 來作為客戶端建立一個到 HTTP Proxy 的 HTTP Tunnel,也可以採用 Envoy 來作為 HTTP Proxy 伺服器接收來自客戶端的 HTTP CONNECT 請求。

Envoy 作為 HTTP 隧道客戶端

通過串聯兩個 Listener,可以將外部 Listener 中收到的 HTTP 請求通過 Internal Listener 建立的 HTTP 隧道傳送到後端的代理伺服器,如下所示(該配置檔案來自 Envoy Github 中的示例檔案  [5] ):

Egress(入口) Listener,從埠 1000 接收來自客戶端的 HTTP 請求

name: http
address:
  socket_address:
    protocol: TCP
    address: 127.0.0.1
    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: local_service
          domains: ["*"]
          routes:
          - match:
              prefix: "/"
            route:
              cluster: encap_cluster
      http_filters:
      - name: envoy.filters.http.router
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

Internal Listener,其 filter chain 中配置的是一個 TcpProxy。該 TcpProxy 中設定了 tunneling_config [6] 選項,表示該 TcpProxy 將同 upstream 建立一個 HTTP 隧道,將收到的 TCP 資料通過該 HTTP 隧道傳送到 upstream。Envoy 支援採用 HTTP/1.1 和 HTTP/2 兩種方式建立隧道,具體採用哪種協議取決於 upstream cluster 配置中的 typed_extension_protocol_options 部分。

name: encap
internal_listener: {}
filter_chains:
- filters:
  - name: tcp
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
      stat_prefix: tcp_stats
      cluster: cluster_0
      # 表示該 TcpProxy 將採用 HTTP 隧道的方式代理資料
      tunneling_config: 
        hostname: host.com:443

該 Cluster 配置在 Egress Cluster 的 HCM 中,用於關聯 Egress Listener 和 Internal Listener。

clusters:
- name: encap_cluster
  load_assignment:
    cluster_name: encap_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            envoy_internal_address:
              server_listener_name: encap

該 Cluster 配置在 Internal Cluster 中,是 HTTP 隧道連線的 Upstream。

  - name: cluster_0
    # 該選項表示將採用 HTTP2 CONNECT 來建立隧道
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}
    load_assignment:
    # 隧道連線的 upstream server 地址
      cluster_name: cluster_0
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 10001
採用 Internal Listener 建立 HTTP 隧道,代理 downstream 的 HTTP 請求

上面的示例中 Egress Listener 的 filter chain 中配置的是 HCM。由於 HTTP 隧道是透明傳輸 TCP 資料流的,因此其中可以是任意七層協議的資料,Egress Listener 中的 filter chain 中也可以配置為 Tcp Proxy。

Envoy 作為 HTTP 隧道伺服器

當然,我們可以採用 Envoy 來作為 HTTP Proxy 來接收 HTTP CONNECT 請求,建立和客戶端的 HTTP 隧道。Envoy 不能在同一個 Listener 裡面建立隧道並將從 HTTP 資料從隧道中解封出來。要實現這一點,我們需要兩層 listener,第一層 listener 中的 HCM 負責建立 HTTP CONNECT 隧道並從隧道中拿到 TCP 資料流,然後將該 TCP 資料流交給個 listener 中的 HCM 進行 HTTP 處理。

下面的配置將 Envoy 作為一個 HTTP CONNECT 隧道伺服器端,並採用一個 Internal Listen 對隧道中的資料進行 HTTP 處理。(該配置檔案來自 Envoy Github 中的示例檔案  [7]

Egress Listener,從 10001 埠接收來自隧道客戶端的 HTTP CONNECT 請求,並將隧道中的資料遞交給 Internal Listener 進行下一步處理。注意其中 HCM 的 upgrade_type: CONNECT 選項表示支援 HTTP CONNECT 隧道, http2_protocol_options 表示採用 HTTP/2。

listeners:
- name: listener_0
  address:
    socket_address:
      protocol: TCP
      address: 127.0.0.1
      port_value: 10001
  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:
                connect_matcher:
                  {}
              route:
                # 資料將被髮送給 decap_cluster
                cluster: decap_cluster
                upgrade_configs:
                - upgrade_type: CONNECT
                  connect_config:
                    {}
        http_filters:
        - name: envoy.filters.http.router
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
        http2_protocol_options:
          allow_connect: true
        upgrade_configs:
        # 該選項標準支援採用 HTTP CONNECT 請求來建立隧道
        - upgrade_type: CONNECT

Internal Listener,從隧道中拿到的 TCP 流解析出 HTTP 請求,並返回一個 HTTP 200 響應。

  - name: decap
    internal_listener: {}
    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: "/"
                direct_response:
                  status: 200
                  body:
                    inline_string: "Hello, world!\n"
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

採用一個 Cluster 來連線 Egress Listener 和 Internal Listener。該 Cluster 配置在 Egress Listener 的 HCM 中,其 endpoint 是 Internal Listener 的 name。

  clusters:
  - name: decap_cluster
    load_assignment:
      cluster_name: decap_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              envoy_internal_address:
                server_listener_name: decap
採用 Internal Listener 對來自 HTTP CONNECT 隧道的資料進行 HTTP 處理

採用 Envoy 來建立一個端到端的 HTTP CONNECT 隧道

從上面的分析可以得知,Envoy 可以作為 Tunnel Client 發起一個 HTTP CONNECT 隧道建立請求,也可以作為 Tunnel Server 來建立一個 HTTP CONNECT 隧道。因此我們可以採用兩個 Envoy 來作為 HTTP CONNECT 隧道的兩端,如下圖所示:

採用 Envoy 來建立 HTTP CONNECT 隧道,並對隧道中的資料進行 HTTP 處理

Istio 的 HBONE 隧道

Istio HBONE 採用了上面介紹的方法來建立 HTTP CONNET 隧道,TCP 流量在進入隧道時會進行 mTLS 加密,在出隧道時進行 mTLS 解除安裝。一個採用 HBONE 建立的連線如下所示:

HBONE 連線

HBONE 由於採用了 HTTP CONNECT 建立隧道,還可以在 HTTP CONNECT 請求中加入一些 header 來很方便地在 downstream 和 upstream 之間傳遞上下文資訊,包括:

  • • authority - 請求的原始目的地址,例如 1.2.3.4:80。

  • • X-Forwarded-For(可選) - 請求的原始源地址,用於在多跳訪問之間保留源地址。

  • • baggage (可選) - client/server 的一些元資料,在 telemetry 中使用。

在這篇文章中,我們介紹了 Istio ambient 模式用來連線 ztunnel 和 waypoint proxy 的 HBONE 隧道的基本原理。下一篇文章中,我們將以 bookinfo demo 程式為例來深入分析 ambient 模式中 HBONE 的流量路徑。

參考資料

  • • http://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT

  • http://zh.wikipedia.org/wiki/HTTP%E9%9A%A7%E9%81%93 [8]

  • • http://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/upgrades

  • • http://www.envoyproxy.io/docs/envoy/latest/configuration/other_features/internal_listener

  • • http://docs.google.com/document/d/1Ofqtxqzk-c_wn0EgAXjaJXDHB9KhDuLe-W3YGG67Y8g

  • • http://docs.google.com/document/d/1ubUG78rNQbwwkqpvYcr7KgM14kEHwitSsuorCZjR6qY/edit#

引用連結

[1] HBONE:  http://www.zhaohuabing.com/post/2022-09-08-introducing-ambient-mesh/# 構建一個 - ambient-mesh

[2] HTTP 的 CONNECT 方法:  http://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT

[3] socket:  http://man7.org/linux/man-pages/man2/socket.2.html

[4] Internal Listener:  http://www.envoyproxy.io/docs/envoy/latest/configuration/other_features/internal_listener

[5] Envoy Github 中的示例檔案:  http://github.com/envoyproxy/envoy/blob/8537d2a29265e61aaa0349311e6fc5d592659b08/configs/encapsulate_http_in_http2_connect.yaml

[6] tunneling_config:  http://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto#envoy-v3-api-msg-extensions-filters-network-tcp-proxy-v3-tcpproxy-tunnelingconfig

[7] Envoy Github 中的示例檔案:  http://github.com/envoyproxy/envoy/blob/8537d2a29265e61aaa0349311e6fc5d592659b08/configs/terminate_http_in_http2_connect.yaml

[8] http://zh.wikipedia.org/wiki/HTTP%E9%9A%A7%E9%81%93:  http://zh.wikipedia.org/wiki/HTTP 隧道