在定下本文的标题之前,我推敲了几遍。最终决定以“浅谈”开头,是因为本文将专注于 istio 下发配置给 Envoy 的功能,尤其是关注 Ingress 场景的配置下发(NodeType 为 Router),不祈求涉及 ztunnel、grpc 等边边角角。
由于 istio 配置下发相当复杂,即使说是“浅谈”,也需要分成两篇来写。否则就太考验读者耐心和笔者精力了。本文是上篇,讲 istio 和 Envoy 交互部分。后面有空再写一下 k8s(或别的输入,如 MCP)到 istio 这段路径。
delta xDS
从 istio 1.22 开始,istio 下发配置给 Envoy 都是走的 delta xDS。不要被这个名字迷惑了,走 delta xDS 不代表一定会是增量下发。Envoy 启动后会定期向 istio 建立一个 gRPC bidirectional 的连接,订阅当前的 xDS。istio 会用这条连接回复,并且之后有新的 xDS 都通过该连接来响应。Envoy 会返回 ACK 表示接受该 xDS,或 NACK 表示拒绝该 xDS。过一段 idle time 后,Envoy 会关闭该连接,再建立一个新连接。Envoy 走 delta xDS 意味着新连接订阅完当前的 xDS 后,后续来自 isito 的响应都是对于第一次响应的增量。
但这不意味着 istio 每个后续响应都是增量。假设 istio 上有三个资源 {k: A, v: 1}
和 {k: B, v: 1}
、{k: C, v: 1}
,其中因为配置变化,B 的值变成了 2,C 被删掉了。对于没有办法来判断最小增量的资源类型,它可能直接返回一个 {add: [{k: A, v: 1}, {k: B, v: 2}], remove:[{k: C, v: 1}]}
这样的配置,而不是 {add: [{k: B, v: 2}], remove:[{k: C, v: 1}]}
。不过由于 Envoy 对每一种 xDS 资源都有 hash cache,即使收到响应里有重复的资源 A,也不会在数据面上重新发起 A 的事件处理。
那么哪些资源是有办法判断最小增量,支持只返回最少所需数据?
CELRS、EC
xDS 资源可以分成很多种,比如配置上游的 CDS、配置上游节点的 EDS、配置监听的 LDS、配置路由的 RDS 等等。istio 和 Envoy 的所支持的 xDS 是一个交集:istio 不支持 Envoy 的 VHDS 和 SRDS,同时 istio 又多了一些 ztunnel 用到的 xDS 如 WDS。如果按能否成为配置的根来分,xDS 可以分成两种:一种是根资源,如 CDS 和 LDS;另一种被根资源引用的叶资源,如 EDS 和 RDS。根资源在订阅时用的是 wildcard subscription,xDS server 需要返回全部的根配置;相对的,叶资源在订阅时用的是资源名字,这些名字来自于前面根资源的内容。举个例子,Envoy 会订阅全部的 CDS,然后 istio 返回的是 CDS 列表 [A, B]
。后续 Envoy 就会订阅来对应 CDS A 和 B 的 EDS 资源。
因为资源之间有依赖关系,所以根资源必须先响应,然后轮到叶资源。在 istio 里面,CELRS 五种资源的下发顺序是固定的:CDS -> EDS -> LDS -> RDS -> SDS。剩下的资源的顺序不固定,会在 SDS 后面下发。对于南北向的配置下发的场景,我们只关心这五种核心资源,外加 ECDS。列表如下(缩进表示依赖关系):
CDS 配置上游
- EDS 配置上游节点
LDS 配置监听
- RDS 配置路由
- SDS 配置证书
- ECDS 提供 KV 配置
我不会细讲每种资源的内容,毕竟这不是本文重点。回到前面的问题,“那么哪些资源是有办法判断最小增量,支持只返回最少所需数据?”:只有 CDS 和 EDS 支持,其他的资源都是把当前已有的全部配置数据直接返回。判断是否支持很简单,看 https://github.com/istio/istio/blob/master/pilot/pkg/xds/某个xds.go 里面有没有实现 GenerateDeltas 方法即可。
注意虽然 cds 的注释里说“GenerateDeltas for CDS currently only builds deltas when services change. todo implement changes for DestinationRule, etc”:
但实际上对于 DestinationRule 也是支持的,见 https://github.com/istio/istio/pull/44891。
不同 Envoy,不同配置
前面的描述可能会让读者产生错觉,认为 istio 的配置下发,就是生成全量 xDS 后遍历每个 Envoy 连接,下发的量是 配置数 x Envoy 数。对于南北向网关,我们可以认为每个数据面的配置是一样的,所以能够只生成一份给每个数据面用。但 istio 是针对 Mesh 场景设计的,而从一开始,不同 sidecar 的配置就很有可能不一样。真实情况里,istio 会遍历每个 Envoy 连接,然后根据不同的数据面下发不同的配置。步骤如下:
对于每个数据面,在生成每种 xDS 之前都会判断下面几个条件:
- 所在的数据面上是否需要对应的 xDS
- 当前变更是否影响对应的 xDS
所在的数据面上是否需要对应的 xDS:对于南北向的数据面,主要的影响因素是 Gateway 资源。Gateway 资源可以选择在哪些数据面上生效。同时 Gateway 资源对应 xDS 中的 LDS,是大多数 xDS 的根。根的位置确定下来,上面的 RDS、SDS、ECDS 什么的也就跟着确定下来。如果开启了环境变量 PILOT_FILTER_GATEWAY_CLUSTER_CONFIG,则只有路由里配置的上游才会下发,更进一步剪裁了另一个根资源 CDS。
当前变更是否影响对应的 xDS:并非所有的 k8s 资源变更都应该触发全部 CELRS 等 xDS 的变化。比如 Gateway 的变化不会造成 CDS 变化。有的 xDS 使用白名单:只有名单内的资源种类变化才会触发 xDS 生成;有的则使用黑名单:只有名单内的资源种类变化才会跳过 xDS 生成。下表列出主要 k8s 资源和对应的 xDS 的推送关系。✖️ 表示该资源变更时可能会推送对应 xDS。
Gateway | VirtualService | DestinationRule | ServiceEntry | Secret | EnvoyFilter | |
---|---|---|---|---|---|---|
ECDS | ✖️ | ✖️ | ✖️ | |||
SDS | ✖️ | |||||
RDS | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ | |
LDS | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ | |
EDS | ✖️ | ✖️ | ✖️ | |||
CDS | ✖️ | ✖️ | ✖️ | ✖️ |
另外 istio 还针对 endpoint only 的推送(req.Full == false
)做了特殊处理:这类改动就像走上绿色通道,跳过了大部分的 xDS 生成。
前面提到,istio 下发 xDS 是按 CDS -> EDS -> LDS -> RDS -> SDS -> ECDS 这样的顺序。生成 xDS 的顺序也是一样的,每次生成完 xDS 后就会下发。而且下发是 fire & forget,不会等待下发的结果,直接处理下一个 xDS。对 Envoy 返回的 ACK/NACK,istio 会异步接收。如果一直都没有收到 Envoy 返回结果,istio 不会重发 xDS,只能依赖 Envoy 重新建立连接来获取最新的 xDS。
总结
- Envoy 定期与 istio 建立双向 gRPC 连接来订阅和接收配置更新。istio 从 1.22 版本开始默认使用 delta xDS 向 Envoy 增量下发配置。但能够判断最小增量更新(通常用户理解的增量更新)的 xDS 资源只有 CDS 和 EDS,其他资源会直接返回全量配置。
istio 针对每个 Envoy 实例都会生成对应的 xDS 配置,不同 Envoy 可能会收到不同的配置。在为 Envoy 生成 xDS 配置之前,istio 会先判断:
- 该 Envoy 是否需要该 xDS 配置。在 Ingress 场景往往就是基于 Gateway 资源做筛选。
- 当前变更是否影响该 xDS。取决于引起变更具体是哪一种 k8s 资源。
- xDS 配置生成和下发的顺序是 CDS->EDS->LDS->RDS->SDS->ECDS 和其他。下发采用 fire & forget 模式,不等待 Envoy 确认是否收到。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。