Istio ambient 模式采用了被称为 HBONE 的方式来连接 ztunnel 和 waypoint proxy。HBONE 是 HTTP-Based Overlay Network Environment 的缩写。简单地说,ambient 模式采用了 HTTP CONNECT 方法 在 ztunnel 和 waypoint proxy 创建了一个隧道,通过该隧道来传输数据。本文将分析 HBONE 的实现机制和原理。
建立 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 请求中。
我们知道,socket 在操作系统内核接收网络数据,但 Envoy 还支持一种“用户空间 socket”。Internal Listener 就用于从该“用户空间 socket”接收数据包。
Internal Listener 需要和一个 Cluster 一起使用,配置在 Cluster 中作为接收流量的 endpoint。如下所示:
定义一个 Internal Listener:
name: demo_internal_listener
internal_listener: {}
filter_chains:
- filters: [
......
]
定义一个 Cluster,将上面定义的 Internal Listener 作为 Cluster 中的一个 endpoint。该 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
通过串联两个 Listener,可以将外部 Listener 中收到的 HTTP 请求通过 Internal Listener 创建的 HTTP 隧道发送到后端的代理服务器,如下所示(该配置文件来自 Envoy Github 中的示例文件):
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 选项,表示该 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 请求
从上面的分析可以得知,Envoy 可以作为 Tunnel Client 发起一个 HTTP CONNECT 隧道创建请求,也可以作为 Tunnel Server 来创建一个 HTTP CONNECT 隧道。因此我们可以采用两个 Envoy 来作为 HTTP CONNECT 隧道的两端,如下图所示:
采用 Envoy 来创建 HTTP CONNECT 隧道,并对隧道中的数据进行 HTTP 处理
Istio HBONE 采用了上面介绍的方法来创建 HTTP CONNET 隧道,TCP 流量在进入隧道时会进行 mTLS 加密,在出隧道时进行 mTLS 卸载。一个采用 HBONE 创建的连接如下所示:
HBONE 由于采用了 HTTP CONNECT 创建隧道,还可以在 HTTP CONNECT 请求中加入一些 header 来很方便地在 downstream 和 upstream 之间传递上下文信息,包括:
在这篇文章中,我们介绍了 Istio ambient 模式用来连接 ztunnel 和 waypoint proxy 的 HBONE 隧道的基本原理。下一篇文章中,我们将以 bookinfo demo 程序为例来深入分析 ambient 模式中 HBONE 的流量路径。