Istio简介
Istio是一个开源的服务网格,可以控制微服务之间数据的共享方式。 Istio 可以在多种环境中运行:企业本地、云托管、Kubernetes容器,或虚拟机上运行的服务等,支持多集群及混合部署。通过可插拔的策略层和配置 API可以与Prometheus、kiali等工具集成。
下面就istio的架构和特性简单介绍,通过研究istio服务xDS通信协议探究下使用。
架构
Istio架构
Istio分为两部分,数据平面和控制平面,控制平面在istio部署完成之时就存在,负责数据平面的服务发现、配置分发、证书配置等,数据平面在用户创建pod时以sidecar的方式拉起(控制平面pilot agent也一同拉起),流量通过网关路由的转发在proxy之间流转。
控制平面
version 1.5以前:服务发现Pilot、配置Galley、证书生成Citadel、可扩展性Mixer。
version 1.5及以后:Pilot、Galley、Citadel合并为Istiod,Mixer模块由于性能原因取消,新增WebAssembly,在数据平面侧支持。
控制平面主要负责管理和配置数据平面。
数据平面
数据平面由C++开发的envoy组件组成,envoy通过sidecar的方式和业务容器一起提供服务,代理拦截该服务的所有入站和出站流量,实现流量管理等特性。
envoy配置包括四个方面:listeners、routes、clusters、endpoints,而数据平面和控制平面通信的协议为xDS。
特性
Istio主要有三大特性:流量管理、安全性和可观察性。正是为了实现这些特性设计了这样的架构,通过使用CRD(Custom Resource Define)定制路由规则、安全规则等,用户使用istio在配置自己的deployment、SVC同时,配置正确的CRD资源就能够正确使用istio,Istiod实现对CRD资源的管控。通过sidecar的方式,容器可以获取到istio配置,sidecar具体实现是使用的envoy,envoy想要和管理面通信则需要使用envoy定义的xDS协议。
xDS类型
xDS提供了标准的控制面规范,通过此规范传递信息到数据面,控制平面和数据平面Envoy所使用的通信协议,目前xDS版本主要是v3版本,为一组不同数据源的服务发现协议总称。该协议是由envoy组件定义规范,istiod实现响应envoy的请求。主要包括LDS、RDS、CDS、EDS、SDS、HDS、MS等多种类型,其中LDS、RDS、CDS、EDS为流量管理常用资源类型,ADS为聚合发现服务,一次请求能够获取LDS、RDS等所有信息。
CDS(Cluster Discovery Service)
cluster发现服务,envoy调用CDS API可以动态获取集群信息,根据DiscoveryResponse调整数据平面集群的增删改。
端点:POST /v3/discovery:clusters
Envoy侧配置CDS动态资源方式:
cds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
EDS(Endpoint Discovery Service)
Endpoint为上游主机标识,可以和k8s的endpoint对照理解。Envoy调用EDS API动态获取集群成员信息,通过具体的服务发现配置策略修改。
端点:POST /envoy.service.endpoint.v3.EndpointDiscoveryService/StreamEndpoints
Envoy侧配置EDS动态资源方式:
eds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
LDS(Listener Discovery Service)
listener发现服务,envoy调用LDS API可以动态获取listener信息,同样可以增删改。
端点:POST /envoy.service.listener.v3.ListenerDiscoveryService/StreamListeners
Envoy侧配置LDS动态资源方式:
lds_config:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
RDS(Route Discovery Service)
Route发现服务,用于envoy调用动态获取完整的路由配置,路由配置包括HTTP header的修改等。
端点:POST /envoy.service.route.v3.RouteDiscoveryService/StreamRoutes
Envoy侧配置RDS动态资源方式:
route_config_name: some_route_name
config_source:
resource_api_version: V3
api_config_source:
api_type: GRPC
transport_api_version: V3
grpc_services:
envoy_grpc:
cluster_name: some_xds_cluster
xDS一些特性
订阅方式
放置在指定configsource路径下可配置动态配置,envoy使用inotify监听文件更改,DiscoveryResponse支持Binary protobufs、JSON、YAML、text等
xDS变体
支持的传输协议变体包括:
- Basic xDS:每种资源类型使用独立的gRPC流;
- ncremental xDS:每种资源类型增量的使用独立的gRPC流;
- ADS:所有资源的聚合gRPC流;
- Incremental ADS:增量使用所有资源的聚合gRPC流。
对应的变体提供相应的xDS资源类型。
pilot响应过程
Pilot 实现
Pilot是控制平面和envoy主要通信的组件,包括Discover Service和Agent两部分,其中agent运行在容器pod sidecar内,但是也是属于控制平面的一部分,这两部分共同构成pilot。
Discover Service获取服务信息、流量规则等,envoy通过xDS获取到配置信息修改。
Agent负责envoy的配置管理、生命周期管理,同样也是通过xDS和agent交互。
Discover Service初始化
Discover Service初始化流程图
这是最新1.12.1版本Discover Service关于gRPC的初始化流程图。与旧版本不一样的点:
- 使用了envoy V3版本api;
- 通过调用discovery.RegisterAggregatedDiscoveryServiceServer新增了ADS相关的初始化工作。
func NewServer(args *PilotArgs, initFuncs ...func(*Server)) (*Server, error) {
…
s.XDSServer = xds.NewDiscoveryServer(e, args.Plugins, args.PodName, args.Namespace, args.RegistryOptions.KubeOptions.ClusterAliases)
…
s.initDiscoveryService(args)
…
}
NewServer方法首先配置DiscoveryServer实例,DiscoveryServer实例是Pilot对Envoy的xds API的gRPC实现,然后调用initDiscoveryService初始化initDiscoveryService,initDiscoveryService调用initGrpcServer执行gRPC的初始化工作。
func (s *Server) initGrpcServer(options *istiokeepalive.Options) {
interceptors := []grpc.UnaryServerInterceptor{
// setup server prometheus monitoring (as final interceptor in chain)
prometheus.UnaryServerInterceptor,
}
grpcOptions := istiogrpc.ServerOptions(options, interceptors...)
s.grpcServer = grpc.NewServer(grpcOptions...)
s.XDSServer.Register(s.grpcServer)
reflection.Register(s.grpcServer)
}
initGrpcServer则执行gRPC具体的初始化工作,包括初始化CDS、EDS等,同样包括ADS。执行完初始化的动作之后就执行启动的方法。
gRPC连接
由前文可知istio的xDS主要是通过调用gRPC的方式与envoy交互。Envoy发送DiscoveryRequest 请求,pilot获取请求返回DiscoveryResponse ,envoy读取信息并动态加载配置。
DiscoveryRequest 与DiscoveryResponse
DiscoveryRequest是envoy节点请求istiod时版本化资源定义,结构如下:
type DiscoveryRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 请求消息中提供的 version_info 使用最近成功处理的响应接收的version_info
VersionInfo string `protobuf:"bytes,1,opt,name=version_info,json=versionInfo,proto3" json:"version_info,omitempty"`
// 发起请求的节点信息,如位置信息等元数据
Node *v3.Node `protobuf:"bytes,2,opt,name=node,proto3" json:"node,omitempty"`
// 请求的资源名称列表,为空表示订阅所有的资源
ResourceNames []string `protobuf:"bytes,3,rep,name=resource_names,json=resourceNames,proto3" json:"resource_names,omitempty"`
// 指的是资源的类型,对应的是xDS的端点值
TypeUrl string `protobuf:"bytes,4,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
ResponseNonce string `protobuf:"bytes,5,opt,name=response_nonce,json=responseNonce,proto3" json:"response_nonce,omitempty"`
ErrorDetail *status.Status `protobuf:"bytes,6,opt,name=error_detail,json=errorDetail,proto3" json:"error_detail,omitempty"`
}
DiscoveryResponse则是对envoy发起的请求响应,结构如下:
type DiscoveryResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
//对应DiscoveryRequest的VersionInfo,相应的版本号
VersionInfo string `protobuf:"bytes,1,opt,name=version_info,json=versionInfo,proto3" json:"version_info,omitempty"`
// pilot返回envoy具体的资源,已序列化
Resources []*any.Any `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"`
Canary bool `protobuf:"varint,3,opt,name=canary,proto3" json:"canary,omitempty"`
//对应DiscoveryRequest的TypeUrl
TypeUrl string `protobuf:"bytes,4,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
//定义了随后DiscoveryRequest中明确 ACK 特定 DiscoveryResponse 的方法
Nonce string `protobuf:"bytes,5,opt,name=nonce,proto3" json:"nonce,omitempty"`
ControlPlane *v3.ControlPlane `protobuf:"bytes,6,opt,name=control_plane,json=controlPlane,proto3" json:"control_plane,omitempty"`
}
通过约定的请求和响应结构定义了如何在控制平面和envoy之间使用xDS协议,是使用gRPC流或者轮询方式的进一步封装。
最终一致性
相对于事务类型数据库强一致性,istio是实现的最终一致性。最终一致性指的是istio最终的状态会能够达到预期,但是更新过程中并不能保证。这样设计可以提高性能,简化系统。但同时也会带来流量黑洞的问题,如果系统对可用性要求很高,则设计更新策略的时候需要遵循make before break原则。
make before break原则
istio的make before break模型简单的来说就是主要是对更新顺序的设定:CDS,EDS,LDS,RDS,VHDS,如果没有则顺延更新后面的配置,更新完成之后删除旧的CDS、EDS端点。
为了让用户能够比较简单的实现高可用的最终一致性,envoy提供了聚合发现服务(ADS API)的能力。这个API是单个gRPC流的多路复用,提供了一个严格的排序方式实现有序更新,提高可用性,他的更新序列如下:
ADS更新序列
用户可以根据自己的使用场景来选择配置的方式。
总结
目前Service Mesh最为活跃的Istio项目,是采取控制面 + xDS + envoy 的模式,控制面对接各种服务注册中心,然后再根据用户配置的转发规则,转换为xDS资源,通过gRPC流下发到Envoy中,完成容器的管理工作。