作者: 钟华,腾讯云容器产品中心高级工程师,热衷于容器、微服务、service mesh、istio、devops 等领域技术
今天我们来解析istio控制面组件Pilot, Pilot为整个mesh提供了标准的服务模型, 该标准服务模型独立于各种底层平台, Pilot以插件方式对接不同的服务发现平台, 解析用户输入的流控配置, 转换为统一的服务发现和流量控制模型, 并以xDS方式下发到数据面。
Pilot 译为领航员
, 在mesh中负责路由领航, 是istio控制面的核心组件。
在组件拓扑中, Pod istio-pilot
包括istio-proxy
(sidecar)和discovery
2个容器, pilot核心能力由容器 discovery
中执行的命令pilot-discovery discovery
提供。
在源代码中, package github.com/istio/istio/tree/master/pilot/cmd 有三个命令的入口:
下图展示了当前istio(1.1.X) 中Pilot 的流程设计:
A conceptual diagram for Pilot’s current design(图片来自Isio Community Doc)
从图中可以看出Pilot的处理流程可以抽象为3层:
Pilot 关注的Config
有2大类(图中进行了颜色区别):
Istio Config: 用户侧提供的流控管理配置, 特别的, 在K8s平台中表现为CRD, 如VirtualService、DestinationRule等。
Service Discovery Config: 服务发现配置, 包括Services、Endpoints、Nodes等。
下文中分别以Istio Config
和Service Discovery Config
来表示以上2类数据。
Config Ingestion Layer 以插件化的方式对接各种服务发现平台, 这些对接逻辑以in-process方式内嵌在pilot进程中. 包括Kubernetes, Consul, file-based config plugin, MCP 方式等。
Core Data Model Layer 会缓存上一层(Config Ingestion Layer)获取的配置信息, 根据Istio Config
和Service Discovery Config
数据的不同特点, 该层分别使用不同的控制器对其进行处理和存储. 并将来自不同平台的配置信息抽象为统一的服务发现模型, 如Service, ServiceInstance, Registry 等。
Proxy Serving Layer 负责将上层(Core Data Model Layer)的抽象模型, 转换为具体的xDS协议数据, 并下发到订阅这些数据的数据面。
本文将尝试对pilot-discovery discovery
的处理流程进行分析, 重点关注pilot对k8s平台的适配实现. 后续将对Config转换为Pilot Model和xDS进行分析。
命令pilot-discovery discovery
将创建并启动discoveryServer:
// Create the server for the discovery service.
discoveryServer, err := bootstrap.NewServer(serverArgs)
......
// Start the server
if err := discoveryServer.Start(stop); err != nil {
return fmt.Errorf("failed to start discovery service: %v", err)
}
其中函数bootstrap.NewServer
按照以下顺序对discoveryServer进行初始化, 步骤清晰明了:
// 略掉错误处理代码
func NewServer(args PilotArgs) (*Server, error) {
......
//对于k8s 平台场景, 初始化kubeClient, 后续使用
s.initKubeClient(&args);
// 网格初始化
s.initMesh(&args);
s.initMeshNetworks(&args)
// 初始化处理Istio Config的控制器
s.initConfigController(&args)
// 初始化处理Service Discovery Config的控制器
s.initServiceControllers(&args)
// 初始化xDS服务端
s.initDiscoveryService(&args)
s.initMonitor(&args)
s.initClusterRegistries(&args)
......
}
Config 一词在源码中使用泛滥, 除了上面提及的:Istio Config
和Service Discovery Config
外, Istio 还有2个全局配置集:
mesh
:该配置集为数据面envoy实例提供全局的配置(由pilot下发), 包括mixer地址, 是否开启链路跟踪, 以及其他重要配置开关和默认值等。
参考配置说明: https://istio.io/docs/reference/config/istio.mesh.v1alpha1/#MeshConfig
meshNetworks
:该配置集提供了多集群mesh中网络配置, 主要包括如何在三层网络中路由到各网络的endpoints, 以及各网络独立的服务发现配置。
参考配置说明: https://istio.io/docs/reference/config/istio.mesh.v1alpha1/#MeshNetworks
我们称以上2个配置集为「网格配置」, 在源码中, 网格配置初始化入口:
s.initMesh(&args);
s.initMeshNetworks(&args)
istio 控制面使用一个名为「istio」的config map, 作为网格的全局配置:
% kubectl -n istio-system get configmap istio -o yaml
apiVersion: v1
data:
mesh:
......
meshNetworks:
......
kind: ConfigMap
metadata:
name: istio
namespace: istio-system
该configmap的2个data域「mesh」和「meshNetworks」, 分别对应上面的2个网格配置集: . 用户可以通过修改该ConfigMap, 进行网格特定行为调整。
在pilot容器定义中, 默认会将该ConfigMap挂载到/etc/istio/config
目录, 2个配置集文件将分别位于/etc/istio/config/mesh
和/etc/istio/config/meshNetworks
image: gcr.io/istio-release/pilot
......
volumeMounts:
- mountPath: /etc/istio/config
name: config-volume
......
volumes:
- configMap:
name: istio
name: config-volume
该configMap作为投射卷(projected volume) , kubernetes 会自动维护ConfigMap到文件系统的更新, 因此pilot只需要通过文件系统watch 这2个配置文件变化, 即可实现运行时配置动态修改, 无需重启pilot。
pilot在watch到「网格配置」变化后, 会触发xDS的重新计算, 并将新的xSD下发到数据面, 从而使得配置修改得以生效。
控制器模式在k8s里使用非常广泛, 典型的k8s控制器利用informer/reflector 对资源进行List/Watch, 获得资源更新事件, 事件对象入队列, 缓存object到indexer, 然后在控制循环中进行自定义处理。
Pilot对Service Discovery Config
和Istio Config
两大类数据的处理, 也是使用控制器模式, 不过Pilot中Config 控制器有特殊之处, 因为适配多种平台, Config 有多种来源可能, 除了k8s informer, 还可能是MCP, 文件系统, 或者consul client等等. 一个典型的Config 控制器, 可以用下图来描述:
上图左边是描述Config来源, 右边描述Config 控制器的结构, 可以划分为三个部分:
Run
方法。Config 控制器会按需构造特定的config 更新事件来源, 如k8s informer、MCP等, 同时通过实现Controller interface, 允许为不同的config type, 添加不同的处理器链, 存储到 type-handlers中. 在接收到config 更新事件后, Pilot 会将event、object和该type对应的handlers包装成Task, push到queue中. 最终在Run
方法中启动对queue中Task的消费。
具体的, Pilot Service Discovery Config
和Istio Config
都按照上述控制器模式实现, 下面分别介绍。
Istio Config 控制器用于处理istio 流控CRD, 如VirtualService、DestinationRule 等, 和Istio Config 控制器相关的interface主要有:
ConfigStore
对象利用client-go库从Kubernetes获取route rule、virtual service等CRD形式存在控制面信息,转换为model包下的Config对象,对外提供Get
、List
、Create
、Update、Delete
等CRUD服务。
这是一种「Store Interface」
interface IstioConfigStore
通过 embed 方式扩展了接口ConfigStore
。其主要目的是为访问route rule、virtual service等数据提供更加方便的接口。相对于ConfigStore
提供的Get
、List
、Create
、Update、Delete
方法,IstioConfigStore直接提供更为方便的RouteRules、VirtualServices等方法。
这是一种「Store Interface」
interface ConfigStoreCache
通过 embed 方式扩展了接口ConfigStore
, ConfigStoreCache
的主要扩展有: 注册Config变更事件处理函数RegisterEventHandler
、开始处理流程的Run
方法等。
这是一种「Controller Interface」, 同时也是「Store Interface」
如上所述, inferface ConfigStoreCache
包括了上一节中要求的2类接口, 目前实现了interface ConfigStoreCache
的Istio Config 控制器主要有以下三种:
具体实现位于 pilot\pkg\config\kube\crd.controller
ConfigSources
获取, pilot 作为MCP client, ConfigSources
从全局配置mesh config中获取。具体实现位于 pilot\pkg\config\coredatamodel.Controller
具体实现位于pilot\pkg\config\memory.controller
我们以在第一种方式k8s List/Watch的Config 控制器为例分析:
// pilot\pkg\config\kube\crd.controller
type controller struct {
client *Client
queue kube.Queue
kinds map[string]cacheHandler
}
该controller 同时实现了interface IstioConfigStore
和ConfigStoreCache
, queue 和kinds 属性是用于存储Task的队列和type-handlers的map。
该controller对象在初始化过程中, 会为指定的 istio CRD 创建一个k8s informer, 这些CRD主要是:
IstioConfigTypes = ConfigDescriptor{
VirtualService,
Gateway,
ServiceEntry,
DestinationRule,
EnvoyFilter,
Sidecar,
HTTPAPISpec,
HTTPAPISpecBinding,
QuotaSpec,
QuotaSpecBinding,
AuthenticationPolicy,
AuthenticationMeshPolicy,
ServiceRole,
ServiceRoleBinding,
AuthorizationPolicy,
RbacConfig,
ClusterRbacConfig,
}
并为每个informer 创建EventHandler, 在EventHandler中会将新的config event 包装为Task, 并push 到queue中。
Run 方法进行queue中Task消费, Task中包括了事件类型, 对象, 以及处理函数链:
type Task struct {
handler Handler
obj interface{}
event model.Event
}
至此, 我们看到了event的 生产和消费, 但不涉及event/Task 是如何消费的。
通过调用RegisterEventHandler
可以添加event/Task的处理器。
但是还没有看到RegisterEventHandler
的调用, 也就是每种类型的config, 有哪些处理函数, 这个在下文中补充。
Service Discovery Config 控制器用于处理各平台服务发现数据, 如Services、Endpoints、Nodes等, 和Service Discovery Config 控制器相关的interface主要有:
对服务发现资源(service/instance等)提供访问方法, 如Services()
InstancesByPort()
等。
这是一种「Store Interface」
注册Config变更事件处理函数, 包括AppendServiceHandler()
AppendInstanceHandler()
, 另外还有控制器启动的Run()
方法。
这是一种「Controller Interface」
如上所述, 只要实现了以上2个interface, 就可以作为Service Discovery Config 控制器, Pilot 中实现了以上interface的有:
具体实现位于 pilot\pkg\serviceregistry\kube.Controller
具体实现位于 pilot\pkg\serviceregistry\external.ServiceEntryStore
具体实现位于 pilot\pkg\serviceregistry\consul.Controller
以上控制器带上ClusterID后, 被包装为Registry:
// Registry specifies the collection of service registry related interfaces
type Registry struct {
// Name is the type of the registry - Kubernetes, Consul, etc.
Name serviceregistry.ServiceRegistry
// ClusterID is used when multiple registries of the same type are used,
// for example in the case of K8S multicluster.
ClusterID string
model.Controller
model.ServiceDiscovery
}
因为Pilot允许同时对接多个服务发现平台, 因此在实际使用中会将多个Registry聚合在一起使用:
// pilot\pkg\serviceregistry\aggregate.Controller
type Controller struct {
registries []Registry
storeLock sync.RWMutex
}
该聚合控制器也实现了以上2个interface。
下面我们重点看看k8s Service Discovery Config 控制器的实现:
pilot\pkg\serviceregistry\kube.Controller
同时实现了上述2个interface, 利用client-go库从Kubernetes获取pod
、service
、node
、endpoint
,并将这些CRD转换为istio中Service、ServiceInstance等统一抽象模型。
type Controller struct { // k8s service/node/ep的controller
......
queue Queue
services cacheHandler
endpoints cacheHandler
nodes cacheHandler
pods *PodCache
servicesMap map[model.Hostname]*model.Service
......
}
// NewController creates a new Kubernetes controller
// Created by bootstrap and multicluster (see secretcontroler).
func NewController(client kubernetes.Interface, options ControllerOptions) *Controller {
......
svcInformer := sharedInformers.Core().V1().Services().Informer()
out.services = out.createCacheHandler(svcInformer, "Services")
epInformer := sharedInformers.Core().V1().Endpoints().Informer()
out.endpoints = out.createEDSCacheHandler(epInformer, "Endpoints")
nodeInformer := sharedInformers.Core().V1().Nodes().Informer()
out.nodes = out.createCacheHandler(nodeInformer, "Nodes")
podInformer := sharedInformers.Core().V1().Pods().Informer()
out.pods = newPodCache(out.createCacheHandler(podInformer, "Pod"), out)
return out
}
总结主要信息:
类似Istio Config 控制器, k8s Service Discovery Config 控制器订阅的资源变更事件也会包装成Task, push 到queue中等待消费, 不在赘述。
通过上述2类控制器, Pilot 已经可以获得Istio Config
和 Service Discovery Config
的更新, 接下来需要将这些不同平台的数据转换成统一的服务和路由模型, 然后通过xDS下发给数据面代理。
目前pilot默认创建一个gRPC Server 提供xDS 订阅服务, 在Pilot源码里叫做DiscoveryServer, 简单说下DiscoveryServer的主要逻辑:
该gRPC Server 需要实现2个接口:
// AggregatedDiscoveryServiceServer is the server API for AggregatedDiscoveryService service.
type AggregatedDiscoveryServiceServer interface {
// This is a gRPC-only API.
StreamAggregatedResources(AggregatedDiscoveryService_StreamAggregatedResourcesServer) error
DeltaAggregatedResources(AggregatedDiscoveryService_DeltaAggregatedResourcesServer) error
}
接口StreamAggregatedResources
主要逻辑:
DiscoveryServer接受下游的订阅请求, 根据请求的xDS类型, 返回指定的资源, 如CDS/EDS/LDS/RDS。
DiscoveryServer将连接对象缓存到map中, key为下游node ID 加上连接计数器. 当检测到配置发生变化, 将会触发这些连接上的xDS重新push到下游. 这些配置变化可能是Istio Config
、Service Discovery Config
或者网格全局配置集。
DeltaAggregatedResources
是增量xDS订阅接口, 目前在istio中还未实现。
查阅社区讨论和源码分析, Pilot目前的不足主要有这些方面:
in-process
方式对接各平台的配置获取和处理, Pilot和这些平台之间并没有明确的接口依赖约定, istio 和其他平台出现接口和数据格式的兼容性, 会是一个潜在风险. (要重视墨菲定律)。下图是社区对Pilot解耦的方案提议:
Mesh Configuration APIs proposal(图片来自Isio Community Doc)
简要说明:
Galley
)来处理isito CRD的处理, 通过MCP进行下发, Galley作为MCP 服务端, Pilot/Mixer等作为MCP 客户端. 在istio 1.1 中, Galley的以上功能已经发布, 并作为默认的配置处理方式, 只是1.1 中还保留了旧的实现代码, Pilot/Mixer 可以选择独立List/Watch Istio CRD, 未来随着Galley功能的增强和稳定, 旧的实现应该会被移除。以上对Pilot的能力和结构进行了分析, 下一篇文章将分析Pilot是如何将Config 转为为xDS。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章