年中折腾过 istio,写了一篇 《白话 Istio》 ,简单介绍了 Istio 是一个配置组装工厂,从 k8s 等上游拉取 CRD 等配置,向下游 Envoy 供应 xDS 配置资源。
最近因为要了解增量实现机制,又翻了代码,这篇尝试从代码层面,简单记录一下的。
两句话
- 每个客户端流,一个主循环,等待两个 channel,一个
deltaReqChan
,处理来自客户端的请求,一个pushChannel
处理上游配置变更事件 - 本文只是介绍基本的工作流,更多的复杂度在于,上游配置资源,到下游 xDS 资源的转换关系,这里并没有做具体介绍
入口
首先 istio 对 Envoy 提供了一组 GRPC service
比如这个 service/method
:
envoy.service.discovery.v3.AggregatedDiscoveryService/DeltaAggregatedResources
对应于 Envoy 中的 Incremental ADS,增量聚合模式,实现入口在 pilot/pkg/xds/delta.go
:
1 | func (s *DiscoveryServer) StreamDeltas(stream DeltaDiscoveryStream) error { |
每个 stream 一个主循环,就干两件事:
- 处理请求,来自于 read 协程
- 处理推送任务,来自上游配置变更事件
处理请求
一次处理一个请求,在主循环中执行
1 | func (s *DiscoveryServer) processDeltaRequest(req *discovery.DeltaDiscoveryRequest, con *Connection) error { |
响应请求
根据 TypeURL
找对应的资源生成器,生成资源之后,封装成 DeltaDiscoveryResponse
后,发送给 Envoy
1 | func (s *DiscoveryServer) pushDeltaXds(con *Connection, |
生成资源
资源有多种,实现也分散了,具体可以看,这两个 interface 的具体实现:
XdsResourceGenerator
和 XdsDeltaResourceGenerator
主要逻辑就是,选取对应的 CRD 资源,生成 xDS 资源,以 LDS
为例:
1 | func (configgen *ConfigGeneratorImpl) BuildListeners(node *model.Proxy, |
订阅资源
讲完请求处理这条链路,再来看推送链路。
这就得先从订阅资源开始了,istio 启动之后,会从 k8s 订阅 EndpointSlice
,Pods
等资源
1 | c.registerHandlers(filteredInformer, "EndpointSlice", out.onEvent, nil) |
当有资源变更时,会触发 DiscoveryServer.ConfigUpdate
1 | func (s *DiscoveryServer) EDSUpdate(shard model.ShardKey, serviceName string, namespace string, |
debounce
ConfigUpdate
只是进入 pushChannel
队列,中间会经过 debounce
处理,才会进入真正的任务队列 pushQueue
debounce
的核心是 update 事件的合并:
1 | for { |
聚合后的事件,会为每个客户端连接,都生成一个任务,塞入全局的 pushQueue
1 | for _, p := range s.AllClients() { |
推送
另外,再有一个单独的协程,从 pushQueue
消费,来完成推送
主要逻辑就是,喂给入口主循环等待的推送任务队列 pushChannel
1 | func doSendPushes(stopCh <-chan struct{}, semaphore chan struct{}, queue *PushQueue) { |
增量机制
最后说下我了解到的增量实现:
目前的增量,是指单个资源粒度的单独更新
相对于原来按照资源类型,把所有资源全量更新的方式,是增量
单个资源的局部更新,还没有的目前 Istio 实现的增量,还仅局限于单个连接内的增量
如果断连后重连,还是要走一遍全量推送的
虽然 Envoy 是支持了跨连接的增量支持跨连接的增量是通过每个资源的版本号来的
从 xDS 协议设计上,每个资源都有版本号,Envoy 也确实会在重连时,将现有资源的版本号传给 Istio
只是 Istio 并没有处理版本号这块细节,发给 Envoy 的版本号只是默认的空字符串