TT Lab
开始
学习 学习路径 课程

Envoy 内部结构

为什么 NACK 的 version_info 不是被拒绝的版本

在 TT Lab 中继续学习

一句话总结

ADS 是在 Envoy 与控制平面之间打开一条 gRPC 流,所有类型(CDS、LDS 等)的配置都在这条流上收发的方式。响应中附带版本和 nonce,Envoy 把试用之后的结果,以带有相同 nonce 的下一个请求告知对方——原因为空就是 ACK,有内容就是 NACK。

为什么需要它

文件订阅同样能无中断地更改配置,但它没有办法把文件分发给数千个代理,也无从得知代理是否接受了这份配置。而这恰恰是控制平面最先需要知道的——我发送的配置是生效了,还是被拒绝了,如果被拒绝,又是为什么。

所以 xDS 有流的方式。代理先打开连接(这是从代理一侧发出的连接,所以防火墙内侧的代理也能连上),控制平面在需要时就通过这条流推送。虽然也可以为每种类型单独打开一条流,但那样无法保证顺序,比如“先收到集群,再收到使用它的监听器”。ADS(Aggregated Discovery Service,聚合发现服务)把所有类型汇集到一条流中,由控制平面来决定顺序。Istio 的 istiod 和 sidecar 使用的就是这种方式。

工作原理

一次收发——以全量状态(State of the World,SotW)方式为准。

Envoy → 요청  type=Cluster  version_info=""   response_nonce=""     (처음)
서버  → 응답  type=Cluster  version_info="1"  nonce="1"  resources=[pool, slow]
Envoy → 요청  type=Cluster  version_info="1"  response_nonce="1"    ← ACK
          … 파일이 바뀌어 서버가 밀어 넣는다 …
서버  → 응답  type=Cluster  version_info="3"  nonce="5"  resources=[…]
Envoy → 요청  type=Cluster  version_info="2"  response_nonce="5"
              error_detail="…ConnectTimeout: value must be greater than 0s"   ← NACK

读取的方法有三点。

字段 含义
response_nonce 这是对哪个响应的回复。为空则是新的订阅请求
error_detail 有内容就是 NACK。原因以字符串形式给出
version_info 最后一次接受的版本。即使是 NACK,也不是被拒绝的版本

最后一行最容易混淆。NACK 请求中的 version_info 不是被拒绝的 3,而是仍在使用的 2。被拒绝的响应通过 nonce 来指明。

在全量状态方式中,响应会包含该类型的全部资源。哪怕只改了一个,也要全部重新发送。在资源很多的网格中,这是一种负担,所以另有只发送变化部分的增量(Delta)方式。

每种类型单独应用。在同一个快照版本 3 中,如果监听器没有问题,只有集群有错,那么 LDS 会接受 3,而 CDS 停留在 2。这就是统计 cluster_manager.cds.version_text 和 listener_manager.lds.version_text 显示出不同值的时刻。

控制平面消失时,代理继续用当前配置运行(control_plane.connected_state 为 0)。重新连上时,Envoy 不会空手开始,而是在第一个请求中携带每种类型最后接受的版本——服务器据此可以判断需要重新发送什么。

热重启——这是更换代理自身(替换二进制文件、更改 bootstrap)时使用的机制。新进程(epoch N+1)用相同的 --base-id 找到旧进程,并接管监听器套接字,旧进程在 --drain-time-s 期间把正在处理的请求做完,然后在 --parent-shutdown-time-s 时退出。端口一刻都不会关闭。作为代价,新进程不会继承 xDS 状态,而是重新连接控制平面,从头开始接收。

在现场相遇的样子

“改了配置,只有部分代理还是旧的行为。”最常见的原因是 NACK。在 Istio 中,istioctl proxy-status 会显示每个代理的 CDS、LDS 是否为 SYNCED,如果有拒绝,原因会留在 istiod 日志中。流量仍在流动,所以如果没有人去看,几周就这样过去了。

“重启 istiod 之后,所有 sidecar 同时重新连上了。”重新连接时,各自都会说明自己最后的版本,所以控制平面必须回答这数千个第一个请求。这就是为什么要部署多台控制平面。

“重启 Envoy 会断开连接。”在像网关这样长期保持连接很多的地方,需要经过热重启或 drain 的依次替换。Kubernetes 的 sidecar 是整个 Pod 一起更换的,所以不使用热重启——Istio 用 --disable-hot-restart 启动它,也是这个原因。

官方文档:xDS REST and gRPC protocol · Aggregated Discovery Service · Hot restart · Command line options

下一项实验要做什么

用 Python 亲手编写一个读取快照文件并推送 CDS、LDS 的 ADS 服务器,并启动一个不设静态配置、从该服务器接收一切的 Envoy。去掉一个端点,观察它无需重启就生效;故意发送一个错误的集群,观察 NACK 及其原因返回;在日志中读取服务器挂掉又恢复时 Envoy 所声明的版本。最后对这个 Envoy 进行热重启,观察正在处理的请求不会中断。