张海东, 多点生活(成都)云原生开发工程师。
Istio
作为目前 Servic Mesh 方案中的翘楚,吸引着越来越多的企业及开发者。越来越多的团队想将其应用于微服务的治理,但在实际落地时却因为不了解 Istio
黑盒中的运行机制而左右为难,本文将基于 1.7 的源码讲解 Istio
的核心组件 Pilot
的结构及运行流程,希望对读者应用 Istio
有所助益。
注:本文基于 istio release-1.7
分支分析,其他版本的代码结构会有所不同。
背景
随着 Istio
1.7 的发布,内部组件精简后的 istiod
日趋稳定,越来越多的公司将其应用到自身微服务的流量治理、安全通信及监测中。多点也不例外,应用 Istio
来落地业务系统所有 Dubbo
服务的网格化,下沉 SDK
逻辑,解决基础中间件与业务系统过于耦合等痛点。目前,我们是通过自己开发的 Controller
组件对接 Zookeeper
等注册中心,将注册到 Zookeeper
的节点实时转化为 ServiceEntry
及 WorkloadEntry
等 Istio
配置类型写入 kube-apiserver
,再由 Pilot
转化为 xDS
协议下发至数据面,同时对集群、虚拟机中的服务进行治理。随着公司服务网格化的逐步落地,对 Istio
及数据面组件源码级掌握的诉求越来越高,没有足够的深度及广度很难解决开发过程中遇到的难题,让我们一起揭开 Istio
神秘的面纱,看看黑箱内部是如何运作的。
本文作为 Istio
控制面组件 Pilot
的源码分析系列,主要面向刚接触 Istio
或仅停留在使用 Istio
基本配置类型(如 VirtualService
、DestinationRule
等)的同学,需要熟悉 Istio
的一些 基础概念及名词[1] 。文章会涉及较多的代码细节,我们会以不同的篇幅分别介绍以下内容:
1.pilot-discovery
宏观架构及启动流程梳理2.pilot-discovery
接口设计及关键接口分析3.pilot-discovery xDS
生成及下发流程梳理4.pilot-agent
流程梳理5.pilot
中的身份认证及安全通信解析
相信通过源码一步一步分析,能消除读者对 Pilot
的陌生感,在基于 Pilot
做适配开发时会更加清楚的了解其底层运行逻辑,碰到问题时也能更好的定位。
Pilot
的代码主要分为两部分:
•pilot-discovery
•pilot-agent
其中 pilot-agent
负责数据面 Sidecar
实例的生命周期管理,而 pilot-discovery
负责控制面流量管理配置及路由规则的生成和下发。
宏观架构
pilot-discovery
的核心组件如图:
![null](https://i-blog.csdnimg.cn/blog_migrate/c3c58360204b34797732b2fa1474fa2a.png)
其中 Server
为 pilot-discovery
的主服务,包含了三个比较重要的组件:
•Config Controller
:从不同来源接收流量控制和路由规则等 Istio
的配置,并响应各类事件。•Service Controller
:从不同注册中心同步服务及实例,并响应各类事件。•EnvoyXdsServer
:核心的 xDS
协议推送服务,根据上面组件的数据生成 xDS
协议并下发。
Config Controller
比较核心的就是对接 Kubernetes
,从 kube-apiserver
中 Watch
集群中的 VirtualService
、ServiceEntry
、DestinationRules
等配置信息,有变化则生成 PushRequest
推送至 EnvoyXdsServer
中的推送队列。除此之外,还支持对接 MCP(Mesh Configuration Protocol)
协议的 gRPC Server
,如 Nacos
的 MCP
服务等,只需要在 meshconfig
中配置 configSources
即可。最后一种是基于内存的 Config Controller
实现,通过 Watch
一个文件目录,加载目录中的 yaml
文件生成配置数据,主要用来测试。
Service Controller
目前原生支持 Kubernetes
和 Consul
,注册在这些注册中心中的服务可以无痛接入 Mesh
,另外一种比较特殊,就是 ServiceEntryStore
,它本质是储存在 Config Controller
中的 Istio
配置数据,但它描述的却是集群外部的服务信息,详情可阅读文档 ServiceEntry[2],Istio
通过它将集群外部,如部署在虚拟机中的服务、非 Kubernetes
的原生服务同步到 Istio
中,纳入网格统一进行流量控制和路由,所以 ServiceEntryStore
也可以视为一种注册中心。还有一种就是 Mock Service Registry
,主要用来测试。
ServiceEntryStore
从 Config Controller
到 Service Controller
的转化流程大致如图(后续会做详细的代码分析,这里简单了解一下即可):
![null](https://i-blog.csdnimg.cn/blog_migrate/d7b5d0ad7db2a5ca19ddcdd677ea5d5a.png)
ConfigStores
是一个列表,里面存储了各类 Istio
配置文件,包括 ServiceEntry
、WorkloadEntry
等服务数据,也包括 VirtualService
、DestinationRules
、Sidecar
等流量控制、路由规则的配置数据,pilot-discovery
将这些 ConfigStores
聚合成一个 configController
统一进行管理,之后再从其中衍生出 IstioConfigStore
,将其作为 serviceEntryStore
的配置源。serviceEntryStore
其实就是 ServiceEntry Controller
,响应 ServiceEntry
和 WorkloadEntry
这类服务信息的变化。
EnvoyXdsServer
比较核心,一切与 xDS
协议相关的接收、转换、下发操作都由它完成。EnvoyXdsServer
对接所有集群中的边车代理,如 Envoy
、MOSN
等,当配置或服务发生变化时主动推送,也会响应代理发送的请求,依据请求的信息下发相应的 xDS
配置。
理解了这三个核心组件的定义,就能比较好的理解下面分析的各类流程了。
pilot-discovery
的整个业务流程梳理如下,可以先大概浏览一遍,之后我们逐一进行分析:
![null](https://i-blog.csdnimg.cn/blog_migrate/e677590aba10eb70e7097f47f748d4fc.png)
启动流程梳理
首先详细看一下 pilot-discovery
的启动流程。pilot-discovery
组件的入口代码在 istio/pilot/cmd/pilot-discovery
中。该目录中包含两个文件: main.go
和 request.go
。main.go
中定义了 pilot-discovery
根命令及 discovery
命令,是启动服务发现及配置下发的主流程; 另一个文件 request.go
中定义了 request
命令,用来请求 Pilot
中的 metrics/debug
接口,多用来调试。
main.go
中 discoveryCmd
的 RunE
函数定义了启动过程,代码如下:
// 创建一个接收空结构的 stop channel 用来停止所有 servers
stop := make(chan struct{})
// 创建服务发现的 Server
discoveryServer, err := bootstrap.NewServer(serverArgs)
if err != nil {
return fmt.Errorf("faile