Istio Pilot与Envoy的交互机制解读 - model 包分析

前言

基于Istio的服务网格,Istio项目本身实现了网格的控制平面,主要是Envoy提出的服务发现协议xDS。Envoy本身则作为网格的数据平面,它通过xDS和Istio通信,获得各种资源的配置并更新代理逻辑。

Istio还支持:

Envoy统计数据的收集,从Statd格式转换为Prometheus格式。目前看来代理端已经直接暴露了Prometheus的Exporter了
限速服务、策略服务
和第三方Tracer的对接
等功能。这些都需要Istio和Envoy的协同才能实现。最基础、关键的协同是Istio组件Pilot和Envoy之间基于xDS协议进行的各种Envoy配置信息的推送。

Istio的文档并没有对Istio Pilot和Envoy如何交互的细节进行描述,本文结合Istio、Envoy的源码来探讨这些细节。

model包分析

Pilot的model包为很多Pilot抽象创建了模型(结构),并定义了它们支持的操作。注意这里建模的是Pilot的抽象,因此名词Service是指Istio的抽象服务,而非K8S的Service或者Envoy的Cluster。

config.go

对Istio的配置信息、配置存储进行建模。

Config

代表一个Istio配置单元:

type Config struct {
    ConfigMeta
    // 配置内容以Proto消息的形式存储
    Spec proto.Message
}

ConfigMeta

配置的元数据:

type ConfigMeta struct {
    //匹配内容消息类型的短类型名称,例如route-rule
    Type string 
    // API组和版本
    Group string 
    Version string 
    // 命名空间范围内唯一性名称
    Name string 
    // 命名空间
    Namespace string 
    // FQDN后缀
    Domain string 
    // 标签集
    Labels map[string]string 
    // 注解集
    Annotations map[string]string 
    // 资源版本,跟踪对配置注册表的变更
    ResourceVersion string 
    CreationTimestamp meta_v1.Time 
}

ConfigStore

定义一组平台无关的,但是底层平台(例如K8S)必须支持的API,通过这些API可以存取Istio配置信息

每个配置信息的键,由type + name + namespace的组合构成,确保每个配置具有唯一的键

写操作是异步执行的,也就是说Update后立即Get可能无法获得最新结果。有资源版本判断资源是否更新

此接口返回的引用,仅支持只读操作,对其修改存在线程安全问题

type ConfigStore interface {
    // 返回配置描述符,其实就是[]ProtoSchema类型,ProtoSchema描述了资源的Group/Version/Type等信息
    ConfigDescriptor() ConfigDescriptor
    Get(typ, name, namespace string) (config *Config, exists bool)
    List(typ, namespace string) ([]Config, error)
    Create(config Config) (revision string, err error)
    Update(config Config) (newRevision string, err error)
    Delete(typ, name, namespace string) error
}

ConfigStoreCache

表示ConfigStore的本地完整复制的缓存,此缓存主动和远程存储保持同步,并且在获取更新时提供提供通知机制。

为了获得通知,事件处理器必须在Run之前注册,缓存需要在Run之后有一个初始的同步延迟。

type ConfigStoreCache interface {
        // CRUD接口
    ConfigStore
    // 添加某种配置类型的事件处理器
    RegisterEventHandler(typ string, handler func(Config, Event))
    Run(stop <-chan struct{})
    // 初始缓存同步完毕后返回true
    HasSynced() bool
}

IstioConfigStore

此接口扩展ConfigStore,增加一些针对Istio资源的操控接口:

type IstioConfigStore interface {
    ConfigStore
    // 列出ServiceEntry
    ServiceEntries() []Config
    // 列出绑定到指定工作负载标签的Gateway
    Gateways(workloadLabels LabelsCollection) []Config
    // 列出绑定到指定工作负载标签EnvoyFilter
    EnvoyFilter(workloadLabels LabelsCollection) *Config
    // 列出关联到指定目标服务实例的Mixerclient HTTP API Specs
    HTTPAPISpecByDestination(instance *ServiceInstance) []Config
    // 列出关联到指定目标服务实例的Mixerclient quota specifications
    QuotaSpecByDestination(instance *ServiceInstance) []Config
    // 列出关联到指定服务+端口的身份验证策略
    // 如果存在多个不同范围(全局、命名空间、服务)的策略,最精确的那个被返回。如果同一范围有多个策略,返回第一个
    AuthenticationPolicyByDestination(service *Service, port *Port) *Config
    // 列出指定命名空间的ServiceRoles
    ServiceRoles(namespace string) []Config
    // 列出指定命名空间的ServiceRoleBindings
    ServiceRoleBindings(namespace string) []Config
    // 列出名字为DefaultRbacConfigName的RbacConfig
    RbacConfig() *Config
}

context.go

Environment

此结构为Pilot提供聚合的环境性的API:

type Environment struct {
    // 内嵌接口:用于列出服务、实例
    ServiceDiscovery
 
    // 已经废弃,使用 PushContext.ServiceAccounts
    ServiceAccounts
 
    // 内嵌接口:用于列出路由规则
    IstioConfigStore
 
    // 网格配置信息
    Mesh *meshconfig.MeshConfig
 
    // 用于和Mixer通信
    MixerSAN []string
 
    // 全局的推送上下文,已经废弃
    // 除非出于测试、处理新连接的目的,不要使用此字段
    PushContext *PushContext
}

Proxy

此结构建模代理(Envoy代理)的属性,xDS使用此结构对代理进行识别:

type Proxy struct {
    // 此代理所在的集群
    ClusterID string
    // 节点类型(也就是说运行代理的那个Pod的代理角色
    Type NodeType
    // 用于识别代理以及它的同地协作的服务实例的IP地址
    IPAddress string
    // 平台先关的Sidecar代理ID
    ID string
    // 短主机名的DNS后缀
    Domain string
    // 节点的元数据
    Metadata map[string]string
}

NodeType

用于区分不同代理在网格中的职责。

type NodeType string
const (
    // 应用程序容器的边车代理,普通被网格管理的Pod使用这种代理角色
    Sidecar NodeType = "sidecar"
    // 独立运行的,集群入口代理,istio-ingress中运行的是这种代理
    Ingress NodeType = "ingress"
    // 独立运行的,作为L7/L4路由器的代理,istio-ingressgateway、istio-egressgateway中运行的是这种代理
    Router NodeType = "router"
)

push_context.go

EndpointShard

端点分片,存储单个服务的单个注册表中的单个分片的名称及其端点列表:

type EndpointShard struct {
    Shard   string
    Entries []*IstioEndpoint
}

EndpointShardsByService

存储单个服务的所有分片信息。使用K8S作为注册表时,Shards通常只有一个元素,其键是"Kubernetes",其值是Shard名为"Kubernetes"的EndpointShard

type EndpointShardsByService struct {
    // 这种结构下,每个注册表只能有一个分片
    // 映射的键是注册表名称
    Shards map[string]*EndpointShard
    ServiceAccounts map[string]bool
}

IstioEndpoint

此结构用于代替NetworkEndpoint和ServiceInstance,做了以下优化:

ServicePortName字段代替ServicePort字段。原因是进行了端点回调(endpoint callbacks are made)时端口号、协议可能不可用
合并两个结构,原因是一对一关系
不再持有Service的指针。原因是接收到端点时,服务对象可能不可用
提供缓存的EnvoyEndpoint对象,避免为每次请求/每个客户端重新分配

type IstioEndpoint struct {
    // 工作负载的标签
    Labels map[string]string
    Family AddressFamily
    Address string
    EndpointPort uint32
    // 跟踪端口的名称,避免最终一致性相关的问题。某些情况下Endpoint先于Service可见,这时进行端口查找会失败
    // 端口名到号的映射将在集群计算时进行
    ServicePortName string
    // 用于遥测
    UID string
    // 缓存的LbEndpoint(来自Envoy Go客户端包),通过数据转换得到,避免重复计算
    EnvoyEndpoint *endpoint.LbEndpoint
    ServiceAccount string
}

PushContext

参考下文。

service.go

Service

此结构对Istio服务进行建模,每个服务具有全限定的名称(FQDN),一个或多个监听的端口,一个可选的和服务关联的负载均衡器/虚拟IP地址(FQDN解析到此地址)。

例如,在K8S中,服务kubernetes关联到FQDN kubernetes.default.svc.cluster.local,具有虚拟IP地址10.96.0.1,监听443端口。

// 主机名,可能是通配符
type Hostname string
 
type Service struct {
    // 主机名
    Hostname Hostname 
 
    // 服务的负载均衡器IPv4地址
    Address string 
 
    // 多集群支持,服务在每个集群中的负载均衡器IPv4地址
    ClusterVIPs map[string]string 
 
    // 监听的端口列表
    Ports PortList 
 
    // 运行服务的账号
    ServiceAccounts []string 
 
    // 指示服务是否位于网格外部,这种服务通过ServiceEntry定义
    MeshExternal bool
 
    // 在路由之前,如何解析服务的实例
    Resolution Resolution
 
    // 服务创建时间
    CreationTime time.Time 
 
    // 额外的属性,Mixer/RBAC 策略会用到
    Attributes ServiceAttributes
}

Resolution

用于指示在路由请求之前,如何解析出服务的实例:

type Resolution int
 
const (
    // 代理根据自己本地的负载均衡池决定使用哪个端点
    ClientSideLB Resolution = iota
    // 代理进行DNS解析,并把请求发给解析结果
    DNSLB
    // 代理直接根据请求者指定的目的地址
    Passthrough
)

ServiceInstance

服务的特定版本的一个实例,绑定到一个NetworkEndpoint:

type ServiceInstance struct {
    // 关联的端点
    Endpoint         NetworkEndpoint 
    // 所属的服务
    Service          *Service        
    // 标签集
    Labels           Labels          
    AvailabilityZone string          
    ServiceAccount   string          
}&nbsp;

NetworkEndpoint

建模关联到服务的实例的网络地址:

type NetworkEndpoint struct {
    // 地址族
    Family AddressFamily
    Address string
    Port int
    ServicePort *Port
    UID string
}

Port

对服务监听的网络端口进行建模:

type Port struct {
    // 易读的端口名,如果服务包含多个端口,则此字段必须
    Name string 
 
    // 服务的端口号,非必须关联到服务背后的实例的端口
    Port int 
 
    // 使用的协议
    Protocol Protocol 
}
 
// 端口集
type PortList []*Port

Protocol

通信协议:

type Protocol string
// 目前支持的协议枚举
const (
    ProtocolGRPC Protocol = "GRPC"
    ProtocolHTTP Protocol = "HTTP"
    ProtocolHTTP2 Protocol = "HTTP2"
    ProtocolHTTPS Protocol = "HTTPS"
    ProtocolTCP Protocol = "TCP"
    ProtocolTLS Protocol = "TLS"
    ProtocolUDP Protocol = "UDP"
    ProtocolMongo Protocol = "Mongo"
    ProtocolRedis Protocol = "Redis"
    ProtocolUnsupported Protocol = "UnsupportedProtocol"
)

TrafficDirection

流量的方向:

type TrafficDirection string
const (
    TrafficDirectionInbound TrafficDirection = "inbound"
    TrafficDirectionOutbound TrafficDirection = "outbound"
)

ServiceDiscovery

此接口用于发现服务的实例:

type ServiceDiscovery interface {
    // 列出所有服务
    Services() ([]*Service, error)
 
    // 废弃,根据主机名获得服务
    GetService(hostname Hostname) (*Service, error)
 
    // 取回服务的、匹配指定标签集的实例
    Instances(hostname Hostname, ports []string, labels LabelsCollection) ([]*ServiceInstance, error)
    InstancesByPort(hostname Hostname, servicePort int, labels LabelsCollection) ([]*ServiceInstance, error)
 
    // 返回和指定代理同地协作(co-located)的实例,所谓co-located是指运行在相同的命名空间和安全上下文
    //
    // 对于以Sidecar方式运行的代理,返回非空的切片;对于独立运行的代理,返回空切片
    GetProxyServiceInstances(*Proxy) ([]*ServiceInstance, error)
 
    // 返回一个IPv4地址关联的管理端口
    ManagementPorts(addr string) PortList
 
    // 返回一个IPv4地址关联的健康检查探针
    WorkloadHealthCheckInfo(addr string) ProbeList
}