内容简介:为实现服务网格,Istio Proxy 需要在网点互连握手时作元信息交换。故 Istio 在原生 Envoy 上再定制了传输层的规约,以实现元信息交换。本文主要讲 基于 ALPN/TLS 握手的 HTTP 元信息交换。
📚 摘录说明:
本文摘自一本我在写作中的开源书《Istio & Envoy 内幕》 中 基于 ALPN/TLS 握手的 HTTP 元信息交换 与 因配置 Envoy 保留 HTTP/1 header 大小写,在混合 HTTP1 和 HTTP2 的 Istio 网格上意外禁用了 HTTP/2 两节。如果你发现转载图片不清,请回到原书。
💂 关于本文封面:Churchill, Truman and Stalin
After country’s leaders come together to resolve conflict, this often ends up in a historic handshake - and the end of World War Two was no different.
This picture shows the moment that British Prime Minster Winston Churchill, US President Harry Truman and Soviet Union leader General Stalin shook hands during the Potsdam Conference which took place between 17 and 25 July 1945.
By the end of the conference, it had been decided what was going to happen with Germany at the end of World War Two.
The three countries working together like this was significant as Soviet Union would not have traditionally allied itself with the US and Britain.
Once World War Two had finished, there would be conflict between these nations as the Cold War got under way.
缘起
假如你是一家大公司的 HR,每天都要和不同的同事联系沟通。那么你在开始正式的沟通内容前,要做是什么? 自我介绍和让对方自我介绍。各自表明自己的身份。这些信息可以来源于正式对话开始前的打招呼中,也来在开始对话前看看对方在 Outlook 上的个人信息。
同样,Istio 为实现服务网格,Istio Proxy 需要在网点互连握手时作元信息交换。故 Istio 在原生 Envoy 上再定制了传输层的规约,以实现元信息交换。
让我们从一个例子开始:
[serviceA app --h2c--> serviceA istio-proxy] ----(http over mTLS)---> [serviceB istio-proxy --h2c--> serviceB app]
Istio Proxy 的一个基本设计原则是尽早知道新连接的 downstream/upstream 元数据。因为这样就能尽早做出一些决定。
对于 client side(Outbound) Istio proxy
:
如果它事先知道 upstream cluster 的元数据:
- upstream cluster 是同一个 Istio 网格上的另一个 “Istio Proxy”?
- upstream cluster 支持哪种类型的 TLS?Istio 网格的自动 mTLS 还是手动传统 TLS?
- upstream cluster 支持哪种应用级协议:http1.1、http2 还是 http3?
client side(Outbound) Istio Proxy 也可以根据 downstream(同一 pod 上的应用程序)连接决定 upstream 的 HTTP 版本,参见 use_downstream_protocol_config。
client side(Outbound) Istio Proxy 可以根据 upstream 服务器支持的 HTTP 版本覆盖(overwrite) ALPN 协议列表。
对于 server side(Inbound) Istio proxy
:
例如,当 Istio Proxy
接受来自 downstream 的新连接时,它想知道 downstream 的一些元数据:
- downstream 是同一个 Istio 网格上的另一个 Istio Proxy 吗?
- downstream 支持哪种类型的 TLS?Istio 网格的自动 mTLS 还是手动传统 TLS?
- downstream 支持哪种应用级协议:http1.1、http2 还是 http3?
作为服务器的 “Istio Proxy” 将根据上述 downstream 元数据决定 Network Filter Chains
和 Http Filter Chains
的选择。
术语
- h2c -
基于 TCP 的 HTTP/2
或HTTP/2 明文(Cleartext)
- h2 -
基于 TLS 的 HTTP/2
(使用 ALPN 作协议协商) - ALPN - 基于 TLS 的
ALPN(Application-Layer Protocol Negotiation 应用层协议协商)
outbound istio-proxy 与 inbound istio-proxy 的协作
上面说过,inbound 和 outbound istio-proxy 需要合作协商 HTTP 版本。下图为一个示例的协作流程:
图: HTTP 协议元数据交换概述
-
Outbound istio-proxy
基于
istio.alpn HTTP Filter
和Upstream Cluster
元数据。 -
Inbound istio-proxy
基于
Listener->filter_chains->filter_chain_match->application_protocols
配置和 Outbound istio-proxy 提供的 ALPN 。
下面深入探究了 Envoy Proxy 和 Istio Proxy 的相关源码。尝试展示背后的实现原理。
问题解决示例
示例说明:因配置 Envoy 保留 HTTP/1 header 大小写,在混合 HTTP1 和 HTTP2 的 Istio 网格上意外禁用了 HTTP/2 。
动机
简单说:我们希望在长期运行 HTTP/1.1 的 Istio 环境中支持 HTTP/2。所以我们需要一个 HTTP/1.1 和 HTTP2 混合网格。
我们希望在服务之间的以下 API 流中支持 HTTP/2:
[serviceA app --h2c--> serviceA istio-proxy] ----(http2 over mTLS)---> [serviceB istio-proxy --h2c--> serviceB app]
环境:
service A:
Pod A:
ip addr: 192.168.88.94
service B: 10.110.152.25
Pod B: serviceB-ver-6b54d8c7bc-6vclp
ip addr: 192.168.33.5
症状
所以我们在 Pod A 上尝试下面的 curl :
|
|
Pod A 上运行的应用程序似乎使用 HTTP/2。
让我们检查 Pod B 是否使用 HTTP/2 :
|
|
[2024-05-07T07:18:41.470Z] "GET /resource1?p1=v1 HTTP/1.1" 200 - via_upstream - "-" 0 48 16 14 "-" "curl/8.0.1" "6add007-7242-4983-9862-63cd108e5" "serviceB:8080" "[p8]192.168.88.94[/p8]:8080" outbound|8080|ver|serviceB.ns.svc.cluster.local [p8]192.168.33.5[/p8]:48344 [p8]10.110.152.25[/p8]:8080 [p8]192.168.33.5[/p8]:36650 - -
我们可以看到 serviceB 的 istio-proxy 使用 HTTP/1.1 协议。
调查
我们知道流量的路径:
[serviceA app --h2c--> serviceA istio-proxy] ----(http2 over mTLS)---> [serviceB istio-proxy --h2c--> serviceB app]
我们知道 serviceA istio-proxy
使用 ALPN 来与 serviceB istio-proxy
协商使用哪个版本的 HTTP。请参阅 Better Default Networking – Protocol sniffing 。
因此,我们在 “serviceA istio-proxy” 上运行 tcpdump 来探视两个 istio-proxy 之间的 ALPN:
ss -K 'dst 192.168.88.94'
tcpdump -i eth0@if3623 'host 192.168.88.94' -c 1000 -s 65535 -w /tmp/tcpdump.pcap
tshark -r /tmp/tcpdump.pcap -d tcp.port==8080,ssl -2R "ssl" -V | less
...
Transport Layer Security
TLSv1.3 Record Layer: Handshake Protocol: Client Hello
Content Type: Handshake (22)
Version: TLS 1.0 (0x0301)
Length: 2723
Handshake Protocol: Client Hello
Handshake Type: Client Hello (1)
Extension: application_layer_protocol_negotiation (len=32)
Type: application_layer_protocol_negotiation (16)
Length: 32
ALPN Extension Length: 30
ALPN Protocol
ALPN string length: 14
ALPN Next Protocol: istio-http/1.1
ALPN string length: 5
ALPN Next Protocol: istio
ALPN string length: 8
ALPN Next Protocol: http/1.1
...
未找到预期的 “istio-h2” 或 “h2” 。
outbound istio-proxy 的 debug log
打开 outbound istio-proxy 的调试日志:
|
|
可以看到日志输出:
{"level":"debug","time":"2024-05-07T07:18:41.471107Z","scope":"envoy filter","msg":"override with 3 ALPNs"}
Evnoy Listener
因此,我们 dump serviceA istio-proxy
的 Envoy 配置:
serviceA istio-proxy
上的 Envoy Listener:
|
|
如果您在搜索引擎中找 “istio alpn filter”,您可能找不到任何有意义的东西。仅部分文章:
所以现在我们知道:
- 当 upstream cluster 支持
HTTP11
时,TLS 流量中的 ALPN 将提供以下 HTTP 协议:
alpn_override:
- istio-http/1.1
- istio
- http/1.1
- 当 upstream cluster 支持
HTTP2
时,TLS 流量中的 ALPN 将提供以下 HTTP 协议:
- upstream_protocol: HTTP2
alpn_override:
- istio-h2
- istio
- h2
回顾上面的 tcpdump 输出,我们知道,“serviceA istio-proxy” 认为 upstream cluster 只支持 “HTTP/1.1”。为什么?
Envoy upstream cluster 的 meta-data
在 “serviceA istio-proxy” 上的关于 upstream cluster “service B” 的元数据:
|
|
Envoy 的 Upstream HTTP 协议选择有 3 种方法:
- explicit_http_config : To explicitly configure either HTTP/1 or HTTP/2 (but not both!) use
explicit_http_config
- use_downstream_protocol_config : This allows switching on protocol based on what protocol the downstream connection used.
- auto_config : This allows switching on protocol based on ALPN. If this is used, the cluster can use either HTTP/1 or HTTP/2, and will use whichever protocol is negotiated by ALPN with the upstream. Clusters configured with
AutoHttpConfig
will use the highest available protocol; HTTP/2 if supported, otherwise HTTP/1. If the upstream does not support ALPN,AutoHttpConfig
will fail over to HTTP/1.
问题根源
现在我们知道直接原因是 “serviceA istio-proxy” 假设 upstream cluster 只支持 “HTTP/1.1”,这是由上面的“explicit_http_config” 及其子项 “http_protocol_options” 引起的。 但为何 upstream cluster 元数据中存在“explicit_http_config” ? 它是由原生 Istio 生成的?
我们看一下 Istio 的 EnvoyFilter:
|
|
看来上面的配置是从 HTTP1.1 Header Casing - Envoy 官方文档 复制的。但 Envoy 的开发者可能没有想到 “explicit_http_config” 和 “http_protocol_options” 应用于 Istio 时的影响。
Istio 上有很多关于保留 HTTP/1.1 header 大小写的 github issue,下面按时间顺序列出了这些问题:
-
PR: add support for preserving header key case #33030 - Fixes #32008
-
Istio Technical Oversight Committee Meeting Notes - 2021/06/7
-
[howardjohn][10 min] Guidance on breaking change - header casing (https://github.com/istio/istio/pull/33030/)
-
-
tl;dr - preserve HTTP key header casing (ie FooBar -> FooBar instead of FooBar -> foobar)
-
My 2c:
-
- For new users, it seems better in all cases to preserve the case. I don’t see a need to allow an API to lowercase it
- For existing users, they might have come to rely on the lowercasing. It seems a bit odd, as most apps probably assume title casing if anything, so hopefully when they adopt Istio they fix to being case insensitive instead of assuming lowercase, but I am sure in practice it may break users
-
-
-
PR: Revert “add support for preserving header key case” #33122 - Reverts #33030
讨论的结论是 Istio 不会正式支持保留 HTTP/1.1 header 大小写:
Issue: Enable preserve HTTP Header casing #32008
We do not intend to ever merge this feature into Istio, as we have medium term plans to use HTTP2 ~everywhere and any http2 hop destroys casing. You can apply EnvoyFilter at your own risk, with the knowledge that it will break sooner or later
因此,我们必须支持通过 “Istio Envoy Filter” 保留 header 大小写。但对于 HTTP/1.1 和 HTTP2 混合网格,如果您遵循 [HTTP/1.1 Header Casing - Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/ header_casing#stateful-formatters) ,并使用 explicit_http_config
,您可能最终会意外禁用 HTTP/2。所以一般来说 use_downstream_protocol_config
是一个更具兼容性和更安全的选择。
所以我们现在可以修复它:
|
|
我们在这里使用 “use_downstream_protocol_config” ,因为我们希望 upstream 协议遵循 downstream 协议。
下图深入探究了 Envoy Proxy 和 Istio Proxy 的相关源码。尝试展示背后的原因:
图: upstream http protocol selection troubleshooting
用 Draw.io 打开
问题解决反思
阅读 Istio Envoy Filter 官方文档:
EnvoyFilter
provides a mechanism to customize the Envoy configuration generated by Istio Pilot. Use EnvoyFilter to modify values for certain fields, add specific filters, or even add entirely new listeners, clusters, etc. This feature must be used with care, as incorrect configurations could potentially destabilize the entire mesh.
也许我们应该在将其应用到 Istio 之前,通过 Envoy 的文档来检查 Istio Envoy Filters 的所有配置项。
参考
稀| |也|隱|裡|聽|要|看|要|看|
稀| |難|隱|苦|聽|大|看|身|看|
疏| |以|的|苦|那|煞|那|臨|那|
疏| |安|覺|待|飄|風|變|其|天|
的| |寧|悟|命|渺|景|色|境|色|
它| | |了| |的| |的| |的|
終| | |平| |暴| |風| |樣|
於| | |凡| |雷| |雲| |子|
也| | |的| |在| |它| |它|
降| | |人| |不| |變| |似|
臨| | |生| |遠| |得| |乎|
如| | |裡| |的| |像| |已|
預| | |終| |局| |突| |註|
料| | |究| |勢| |然| |定|
| | |的|越|的|越|如|嘩|般|
| | |躲|來|浩|來|大|啦|中|
| | |個|越|劫|越|夢|啦|的|
| | |分|難|本|看|初|啦|無|
| | |明|學|性|起|醒|啦|情|
| | | |習| |來| |的| |
| | | |如| |像| |是| |
| | | |何| |一| |翻| |
| | | |的| |場| |雲| |
| | | |全| |未| |覆| |
| | | |身| |完| |雨| |
| | | |而| |的| |使| |
| | | |左| |天| |人| |
| | | |右| |災| |們| |