背景
服务网格解决方案Istio的架构从逻辑上分为数据平面和控制平面。
-
数据平面 由一组智能代理(Envoy)组成,被部署为 边车。这些代理负责协调和控制微服务之间的所有网络通信。它们还收集和报告所有网格流量的遥测数据。
-
控制平面 管理并配置代理来进行流量路由。
其中控制面与数据面之间的通信协议为 XDS (X Discovery Service),其中x代表其他单一类型资源的API,如LDS(Listener DS),RDS(Router DS),CDS(Cluster DS)等。
XDS通信协议API由Envoy提出,用于实现其动态配置,来应对不断变化的基础架构。
在通过XDS进行不同单一类型资源配置时,存在顺序一致性上的问题,可能导致部分临时流量的丢失。
因此Envoy又推出了ADS (Aggregated Discovery Service),ADS 允许单一管理服务器通过单个 gRPC 流,提供所有的 API 更新。配合仔细规划的更新顺序,ADS 可规避更新过程中流量丢失。
本文就服务网格Istio中如何与Envoy进行XDS协议通信为主体内容进行讲解。
go-control-plan项目
项目go-control-plane是Envoy基于Go语言实现的服务发现API工程。
项目中提供了两个库:
-
API服务: 基于GRPC实现了xDS API的服务端,该服务负责推送、更新Envoy配置。
-
配置缓存: 为了能够快速响应Envoy请求,代码库会在内存中缓存一份Envoy配置。
样例
Envoy在其github源码仓库提供了一个xDS Server的样例,本节进行讲解。
源码修改
由于测试网络限制,对部分源码进行修改:
编译与运行
运行命令为make example,为了方便查看日志,将envoy与xDS Server两个进程运行分成两个命令。
xDS Server
编译二进制:
go build -race -o bin/example internal/example/main/main.go
运行xDS Server:
其中打印的部分配置缓存json内容如下:
启动之后,xDS Server阻塞在监听端口18000上,等待请求的连接。
envoy
启动配置文件部分释义如下:
# sample/bootstrap-xds.yaml
admin:
address:
socket_address:
address: 127.0.0.1
port_value: 19000 # envoy admin端口为19000
dynamic_resources: # 动态资源配置(xds配置)
cds_config: # cds
api_config_source:
api_type: GRPC # GRPC通信方式
grpc_services:
- envoy_grpc:
cluster_name: xds_cluster # xDS Server集群名称为'xds_cluster'
lds_config: # lds
# ......
cluster_name: xds_cluster
node:
id: test-id # envoy本机信息ID
static_resources: # 静态态资源配置
clusters:
- load_assignment:
cluster_name: xds_cluster # xds_cluster的静态地址
endpoints:
- lb_endpoints:
socket_address:
address: 127.0.0.1
port_value: 18000 # 端口18000上的服务
layered_runtime: # 运行时相关配置
- name: runtime-0
通过该启动配置文件可知,envoy将上节启动的Server作为其动态资源配置的服务端。
使用命令与启动配置文件启动envoy:
envoy -c sample/bootstrap-xds.yaml --drain-time-s 1 -l debug
由于启动设置了日志级别为debug,因此可以查看到很多日志信息,其中部分日志如下:
通过该日志可以发现,envoy通过GRPC向xds_cluster发送了一个POST请求,并且收到了状态码为200的响应,代表与xDS Server成功建立连接。
此时xDS Server部分运行日志如下:
其中建立了四个stream,分别响应了envoy启动配置文件中的Cluster Listener RouteConfiguration Runtime,并且节点ID均为test-id,即本次启动的envoy节点ID。
测试
上述两个进程正常运行之后,可在终端中发送测试请求:
[root@linux]# curl 10.20.144.164:10000/ -i HTTP/1.1 302 Found Transfer-Encoding: chunked Connection: keep-alive Date: Tue, 28 Sep 2021 08:33:51 GMT Keep-Alive: timeout=4 Location: https://synergy.hundsun.com/ Proxy-Connection: keep-alive Server: envoy X-Envoy-Upstream-Service-Time: 2
返回了重定向状态码,此时也可以在浏览器中再次发送该请求:
此时也可以观察envoy运行日志:
上述样例中,Envoy服务作为一个代理或网关监听着外部请求,XDS server是其配置生成与下发的中心,其直接为Envoy服务生成了一个转发所有请求到synergy.hundsun.com地址的配置,通过测试验证了其配置的有效性。
这只是一个简单的样例,后文将继续探索XDS协议如何在Istio网格中发挥其作用。
pilot组件
在背景一节中可知,Pilot是Istio控制面中的一个组件,Pilot组件是Istio网格中与Envoy代理进行XDS通信的关键所在。
Pilot组件及其源码解析等参见Istio Pilot代码深度解析,虽然该文基于Istio 1.5.0版本一个commit,但是很多概念仍然与Istio 1.10.5版本相通。
Envoy与Pilot组件的关系
虽然Istio引用了项目go-control-plane来收发XDS请求,但是并非是由Envoy来直连Istiod完成的XDS通信。
Proxy的XDS服务可以通过环境变量PROXY_XDS_VIA_AGENT(默认true)来决定envoy是否通过agent来代理XDS调用而非直连istiod。
Istio中的pilot由下述两个组件组成:
-
discovery: 对接底层基础设施(例如K8s)对各种资源进行监控以获取其更新事件,再通过实现的xDS协议管理服务来下发或更新Envoy配置;
-
agent: 管理Envoy的生命周期(生成配置、启动Envoy、更新Envoy配置等),同时又起到XDS缓存的作用来转发discovery与envoy之间的XDS请求;
后文针对这两个组件分别进行功能阐述。
pilot-discovery
Pilot-discovery被编译为二进制文件运行在Istiod服务中,下图是其运行流程时序图:
由于篇幅有限,不再对源码进行详细解读,其中重要的流程与环节如后文所述。
更新事件处理器
在pilot-discovery服务初始化过程中,添加了两种事件处理器函数:
-
服务处理器(serviceHandler): 处理来自例如kubernetes注册中心的服务变更信息:
-
在initRegistryEventHandlers函数中添加了ServiceEntry类型资源的配置更新处理函数,即当外部资源配置发生更改时的响应处理流程。
-
在initServiceControllers中还添加了启动函数AddMemberCluster中添加了多种K8S原生资源(Namespaces/Service/Nodes/Pods/Endpoints/Endpointslice)的变更事件响应函数,这些资源变更事件最终由处理器ServiceHandler与WorkloadHandler完成推送更新。
-
-
配置处理器(configHandler): 处理除了ServiceEntry/WorkloadEntry/WorkloadGroup之外的所有Istio资源类型的配置更新信息。
pilot-discovery默认启动参数中添加了kubernetes注册器,并且默认以k8s为存储cache来保存Istio CRD配置。
服务处理器与配置处理器对各类资源的监控实际上实现了K8S的List/Watch机制,该机制原理不在本文展开。
客户端连接
在discovery服务初始化过程中,初始化安全发现服务函数中,调用了项目go-control-plane中实现的注册ADS函数,由Istio实现的ADS流函数中,以协程的方式在后台接收客户端的请求连接,在第一次连接成功时pilot-discovery保留客户端的节点信息等,在此之后由配置更新导致的消息推送则直接使用该首次连接信息进行推送。
XDS服务注册
在discovery服务初始化过程中,初始化安全发现服务函数中,调用了项目go-control-plane中实现的注册ADS函数,该函数中实现了两种流描述处理器:
-
全量ADS资源处理器: 在Istio 1.10.5版本中仅SDS服务中设置了非全量更新(PushRequest.Full=false),其余配置更改均为全量推送更新,因此在将更新事件进行推送前在一个for循环中往所有客户端连接中进行推送。
-
增量ADS资源处理器: 用于增量式更新变动的资源,但在Istio 1.10.5版本中暂未实现。
通信流程
Discovery注册了ADS处理器之后,会生成一个连接(connection)在一个协程中接收客户端的连接事件,同时主流程在一个for循环上监听两个channel,分别响应资源变更事件与客户端连接事件,即主动推送配置与响应客户端请求。
主动推送配置
当资源创建或更新事件发生时,处理流程比较复杂:
-
discovery生成一个PushRequest再推送到pushChannel;
-
在一个启动函数中开启的协程中,接收请求,将一段时长内的所有请求进行一些合并等操作,再开启一个push协程来继续下一步操作;
-
push协程中通过遍历所有连接客户端来将请求入队列;
-
在另一个启动函数中开启的协程中通过条件变量从队列中取出请求,生成一个XDS事件并再次将该事件推送到client.pushChannel;
-
主协程中从client.pushChannel取出XDS事件进行最后的客户端推送;
响应客户端请求
当客户端发起对discovery的请求后,进入以下的处理流程:
-
discovery通过GRPC从流中取出请求,将请求推送到reqChannel;
-
主协程从reqChannel取出请求,在函数shouldRespond中判断前后状态决定是否进行响应;
-
根据实际情况进行响应;
pilot-agent
Pilot-agent被编译为二进制文件运行在各个业务服务的边车容器中,下图是其运行流程时序图:
由于篇幅有限,不再对源码进行详细解读,其中重要的流程与环节如后文所述。
网格与代理配置
获取网格配置上遵循一下步骤与流程:
-
生成默认Mesh配置;
-
启动参数中指定的配置文件(默认路径为/etc/istio/config/mesh,istiod中通过Configmap挂载到该路径);
-
环境变量PROXY_CONFIG指定的配置内容;
-
Pod中添加的注解配置;
上述步骤中存在冲突的配置项将以替换的方式进行合并。
默认情况下边车容器中并没有配置文件,因此Agent自行生成了默认的代理配置,而服务发现地址被默认配置为istiod.istio-system.svc:15012,此后Agent以此来与Discovery进行XDS协议通信。
通信方式
在Envoy与Pilot组件的关系描述过,Envoy与pilot之间通过Agent作为媒介进行XDS协议上的通信。
使用HTTP请求curl http://localhost:15000/config_dump在边车容器中导出Envoy的动态配置如下:
Istio与Envoy之间通过ADS进行集成发现服务通信时,配置通信地址为pipe类型的文件./etc/istio/proxy/XDS,该文件在边车容器中的文件如下:
文件./etc/istio/proxy/XDS是个UDS(Unix Domain Socket)。
Linux下进程通讯方式有很多,比较典型的有套接字,平时比较常用的套接字是基于TCP/IP协议的,适用于两台不同主机上两个进程间通信, 通信之前需要指定IP地址. 但是如果同一台主机上两个进程间通信用套接字,还需要指定ip地址,有点过于繁琐. 这个时候就需要用到UNIX Domain Socket, 简称UDS.
UDS的优势:
UDS传输不需要经过网络协议栈,不需要打包拆包等操作,只是数据的拷贝过程
UDS分为SOCK_STREAM(流套接字)和SOCK_DGRAM(数据包套接字),由于是在本机通过内核通信,不会丢包也不会出现发送包的次序和接收包的次序不一致的问题
原文链接:Linux下进程间通讯方式 - UNIX Domain Socket_程序手艺人 - 有趣有能量-CSDN博客_domain socket
因此,Envoy与Agent之间通过UDS文件进行GRPC通信,Agent作为代理将XDS请求转发给Discovery完成通信过程。
通信流程Agent的主流程为启动并管理Envoy的生命周期,该内容不在本文阐述范畴之内。
根据上述运行流程时序图可知,Agent在注册了ADS处理器之后,就启动了对下游服务的监听,当第一次与Envoy建立连接时,便新建了四个协程来处理之后的通信数据:
-
接收下游请求: 接收Envoy的XDS请求,将请求内容通过channel发送给处理上游请求(handleUpstreamRequest);
-
处理上游请求: 将Envoy的请求转发给上游Istiod;
-
接收上游请求/响应: 接收来自Istiod的下发请求或响应,将通信内容通过channel发送给处理上游响应(handleUpstreamResponse);
-
处理上游响应: 将Istiod的下发请求或响应转发给Envoy;
小结
Envoy 代理有两种服务发现的方式: 查询文件和管理服务器动态发现资源。
本文开头的go-control-plan项目样例是一个简单的查询文件的例子,管理服务器动态发现资源的方式(即XDS协议)由于其存在一定的复杂性,本文直接选用了Istio网格作为案例进行了分析,比较详细的阐述了Pilot组件如何与Envoy代理通过XDS通信来实现动态资源的发现与配置下发流程。
目前诸多基于Envoy的项目,都是采取控制面 + xDS + envoy 的模式。比如Apigateway中的gloo,ambassador,Service Mesh 中的istio,app-mesh等。
各个控制面其实基本上对接各种服务注册中心,然后再根据客户配置的转发规则,转换为xDS资源,通过gRPC流下发到Envoy中。



