Istio技术与实践02:源码解析之Istio on Kubernetes 统一服务发现
前言
文章Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制结合Pilot的代码实现介绍了Istio的抽象服务模型和基于该模型的数据结构定义,了解到Istio上只是定义的服务发现的接口,并未实现服务发现的功能,而是通过Adapter机制以一种可扩展的方式来集成各种不同的服务发现。本文重点讲解Adapter机制在Kubernetes平台上的使用。即Istio on Kubernetes如何实现服务发现。
Istio的官方设计上特别强调其架构上的可扩展性,即通过框架定义与实现解耦的方式来集成各种不同的实现。如Pilot上的adapter机制集成不同的服务注册表,Mixer通过提供一个统一的面板给数据面Sidecar,后端可以通过模板定义的方式对接不同的Backend来进行各种访问管理。但就现阶段实现,从代码或者文档的细节去细看其功能,还是和Kubernetes结合最紧密。
Kubernetes和Istio的结合
从场景和架构上看Istio和Kubernetes都是非常契合的一种搭配。
首先从场景上看Kuberntes为应用负载的部署、运维、扩缩容等提供了强大的支持。通过Service机制提供了负载间访问机制,通过域名结合Kubeproxy提供的转发机制可以方便的访问到对端的服务实例。因此如上图可以认为Kubernetes提供了一定的服务发现和负载均衡能力,但是较深入细致的流量治理能力,因为Kubnernetes所处的基础位置并未提供,而Istio正是补齐了这部分能力,两者的结合提供了一个端到端的容器服务运行和治理的解决方案。
从架构看Istio和Kubernetes更是深度的结合。 得益于Kuberntes Pod的设计,数据面的Sidecar作为一种高性能轻量的代理自动注入到Pod中和业务容器部署在一起,接管业务容器的inbound和outbound的流量,从而实现对业务容器中服务访问的治理。在控制面上Istio基于其Adapter机制集成Kubernetes的域名,从而避免了两套名字服务的尴尬场景。
在本文中将结合Pilot的代码实现来重点描述图中上半部分的实现,下半部分的内容Pilot提供的通用的API给Envoy使用可参照上一篇文章的DiscoverServer部分的描述。
基于Kubernetes的服务发现
理解了Pilot的ServiceDiscovery的Adapter的主流程后,了解这部分内容比较容易。Pilot-discovery在initServiceControllers时,根据服务注册配置的方式,如果是Kubernetes,则会走到这个分支来构造K8sServiceController。
case serviceregistry.KubernetesRegistry: s.createK8sServiceControllers(serviceControllers, args); err != nil { return err }
创建controller其实就是创建了一个Kubenernetes的controller,可以看到List/Watch了Service、Endpoints、Node、Pod几个资源对象。
// NewController creates a new Kubernetes controller func NewController(client kubernetes.Interface, options ControllerOptions) *Controller { out := &Controller{ domainSuffix: options.DomainSuffix, client: client, queue: NewQueue(1 * time.Second), } out.services = out.createInformer(&v1.Service{}, "Service", options.ResyncPeriod, func(opts meta_v1.ListOptions) (runtime.Object, error) { return client.CoreV1().Services(options.WatchedNamespace).List(opts) }, func(opts meta_v1.ListOptions) (watch.Interface, error) { return client.CoreV1().Services(options.WatchedNamespace).Watch(opts) }) out.endpoints = out.createInformer(&v1.Endpoints{}, "Endpoints", options.ResyncPeriod, func(opts meta_v1.ListOptions) (runtime.Object, error) { return client.CoreV1().Endpoints(options.WatchedNamespace).List(opts) }, func(opts meta_v1.ListOptions) (watch.Interface, error) { return client.CoreV1().Endpoints(options.WatchedNamespace).Watch(opts) }) out.nodes = out.createInformer(&v1.Node{}, "Node", options.ResyncPeriod, func(opts meta_v1.ListOptions) (runtime.Object, error) { return client.CoreV1().Nodes().List(opts) }, func(opts meta_v1.ListOptions) (watch.Interface, error) { return client.CoreV1().Nodes().Watch(opts) }) out.pods = newPodCache(out.createInformer(&v1.Pod{}, "Pod", options.ResyncPeriod, func(opts meta_v1.ListOptions) (runtime.Object, error) { return client.CoreV1().Pods(options.WatchedNamespace).List(opts) }, func(opts meta_v1.ListOptions) (watch.Interface, error) { return client.CoreV1().Pods(options.WatchedNamespace).Watch(opts) })) return out }
在 createInformer 中其实就是创建了SharedIndexInformer。这种方式在Kubernetes的各种Controller中广泛使用。Informer调用 APIserver的 List 和 Watch 两种类型的 API。在初始化的时,先调用 List API 获得全部资源对象,缓存在内存中; 然后,调用 Watch API 去 Watch这种这种资源对象,维护缓存。
Service informer := cache.NewSharedIndexInformer( &cache.ListWatch{ListFunc: lf, WatchFunc: wf}, o, resyncPeriod, cache.Indexers{})
下面看下Kubernetes场景下对ServiceDiscovery接口的实现。我们看下Kubernetes下提供的服务发现的接口,包括获取服务列表和服务实例列表。
func (c *Controller) GetService(hostname model.Hostname) (*model.Service, error) { name, namespace, err := parseHostname(hostname) item, exists := c.serviceByKey(name, namespace) svc := convertService(*item, c.domainSuffix) return svc, nil }
最终是从infromer的缓存中获取Service资源对象。
func (c *Controller) serviceByKey(name, namespace string) (*v1.Service, bool) { item, exists, err := c.services.informer.GetStore().GetByKey(KeyFunc(name, namespace)) return item.(*v1.Service), true
}
获取服务实例列表也是类似,也是从Informer的缓存中获取对应资源,只是涉及的对象和处理过程比Service要复杂一些。
func (c *Controller) InstancesByPort(hostname model.Hostname, reqSvcPort int, labelsList model.LabelsCollection) ([]*model.ServiceInstance, error) { // Get actual service by name name, namespace, err := parseHostname(hostname) item, exists := c.serviceByKey(name, namespace) svc := convertService(*item, c.domainSuffix) svcPortEntry, exists := svc.Ports.GetByPort(reqSvcPort) for _, item := range c.endpoints.informer.GetStore().List() { ep := *item.(*v1.Endpoints) … } ... } } return nil, nil }
可以看到就是做了如下的转换,将Kubernetes的对一个服务发现的数据结构转换成Istio的抽象模型对应的数据结构。
其实在conversion.go中提供了多个convert的方法将Kubernetes的数据对象转换成Istio的标准格式。除了上面的对Service、Instance的convert外,还包含对port,label、protocol的convert。如下面protocol的convert就值得一看。
func ConvertProtocol(name string, proto v1.Protocol) model.Protocol { out := model.ProtocolTCP
switch proto { case v1.ProtocolUDP: out = model.ProtocolUDP
case v1.ProtocolTCP: prefix := name i := strings.Index(name, "-") if i >= 0 { prefix = name[:i] } protocol := model.ParseProtocol(prefix) if protocol != model.ProtocolUDP && protocol != model.ProtocolUnsupported { out = protocol } } return out }
看过Istio文档的都知道在使用Istio和Kuberntes结合的场景下创建Pod时要求满足4个约束。其中重要的一个是Port必须要有名,且Port的名字名字的格式有严格要求:Service 的端口必须命名,且端口的名字必须满足格式 <protocol>[-<suffix>],例如name: http2-foo 。在K8s场景下这部分我们一般可以不对Pod命名的,看这段解析的代码可以看服务的Protocol是从name中解析出来的。如果Service的protocol是UDP的,则协议UDP;如果是TCP的,则会从名字中继续解析协议。如果名称是不可识别的前缀或者端口上的流量就会作为普通的 TCP 流量来处理。
另外同时在Informer 中添加对add、delete、和update事件的回调,分别对应 informer 监听到创建、更新和删除这三种事件类型。可以看到这里是将待执行的回调操作包装成一个task,再压到Queue中,然后在Queue的run()方法中拿出去挨个执行,这部分不细看了。
到这里Kuberntes特有的服务发现能力就介绍完了。即kube\controller也实现了ServiceDiscovery中规定的服务发现的接口中定义的全部发方法。除了初始化了一个kube controller来从Kubeapiserver中获取和维护服务发现数据外,在pilot server初始化的时候,还有一个重要的initDiscoveryService初始化DiscoveryServer,这个discoveryserver使用contrller,其实是ServiceDiscovery上的服务发现供。发布成通用协议的接口,V1是rest,V2是gRPC,进而提供服务发现的能力给Envoy调用,这部分是Pilot服务发现的通用机制,在上篇文章的adapter机制中有详细描述,这里不再赘述。
总结
以上介绍了istio基于Kubernetes的名字服务实现服务发现的机制和流程。整个调用关系如下,可以看到和其他的Adapter实现其实类似。
1. KubeController使用List/Watch获取和维护服务列表和其他需求的资源对象,提供转换后的标准的服务发现接口和数据结构;
2. Discoveryserver基于Controller上维护的服务发现数据,发布成gRPC协议的服务供Envoy使用。
前面只是提到了服务发现的数据维护,可以看到在Kubernetes场景下,Istio只是是使用了kubeAPIServer中service这种资源对象。在执行层面,说到Service就不得不说Kuberproxy,因为Service只是一个逻辑的描述,真正执行转发动作的是Kubeproxy,他运行在集群的每个节点上,把对Service的访问转发到后端pod上。在引入Istio后,将不再使用Kubeproxy做转发了,而是将这些映射关系转换成为pilot的转发模型,下发到envoy进行转发,执行更复杂的控制。这些在后面分析Discoveryserver和Sidecar的交互时再详细分析。
在和Kubnernetes结合的场景下强调下几个概念:
1. Istio的治理Service就是Kubernetes的Service。不管是服务描述的manifest还是存在于服务注册表中服务的定义都一样。
2. Istio治理的是服务间的通信。这里的服务并不一定是所谓的微服务,并不在乎是否做了微服务化。只要有服务间的访问,需要治理就可以用。一个大的单体服务打成一个镜像在Kuberntes里部署起来被其他负载访问和分拆成微服务后被访问,在治理看来没有任何差别。
本文只是描述了在服务发现上两者的结合,随着分析的深入,会发现Istio和Kubernetes的更多契合。K8s编排容器服务已经成为一种事实上的标准;微服务与容器在轻量、快速部署运维等特征的匹配,微服务运行在容器中也正成为一种标准实践;随着istio的成熟和ServiceMesh技术的流行,使用Istio进行微服务治理的实践也正越来越多;而istio和k8s的这种天然融合使得上面形成了一个完美的闭环。对于云原生应用,采用kubernetes构建微服务部署和集群管理能力,采用Istio构建服务治理能力,也将成为微服务真正落地的一个最可能的途径。有幸参与其中让我们一起去见证和经历这个趋势吧。
注:文中代码基于commit:505af9a54033c52137becca1149744b15aebd4ba
Istio技术与实践02:源码解析之Istio on Kubernetes 统一服务发现的更多相关文章
- MatrixOne从入门到实践02——源码编译
MatrixOne从入门到实践--源码编译 在部署MatrixOne前,我们可能会比较纠结使用哪个版本合适,MatrixOne在github上有各个版本的Releases,包含源码包和适用于Lin ...
- 线程池技术之:ThreadPoolExecutor 源码解析
java中的所说的线程池,一般都是围绕着 ThreadPoolExecutor 来展开的.其他的实现基本都是基于它,或者模仿它的.所以只要理解 ThreadPoolExecutor, 就相当于完全理解 ...
- Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制
服务模型 首先,Istio作为一个(微)服务治理的平台,和其他的微服务模型一样也提供了Service,ServiceInstance这样抽象服务模型.如Service的定义中所表达的,一个服务有一个全 ...
- Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析
王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...
- [Spark內核] 第42课:Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践
本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 ...
- 了解腾讯开源的多渠道打包技术 VasDolly源码解析
一.概要 大家应该都清楚,大家上线app,需要上线各种平台,比如:小米,华为,百度等等等等,我们多数称之为渠道,如果发的渠道多,可能有上百个渠道. 针对每个渠道,我们希望可以获取各个渠道的一些独立的统 ...
- 美团动态线程池实践思路开源项目(DynamicTp),线程池源码解析及通知告警篇
大家好,这篇文章我们来聊下动态线程池开源项目(DynamicTp)的通知告警模块.目前项目提供以下通知告警功能,每一个通知项都可以独立配置是否开启.告警阈值.告警间隔时间.平台等,具体代码请看core ...
- Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践
本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
随机推荐
- windows环境下启动mongodb服务
方法一1.打开命令窗口,切换到mongodb安装目录下的“bin”目录中. 输入命令:cd E:\software\MongoDB\Server\3.4\bin 2.启动服务.输入命令:”mongod ...
- 计算机中buffer和cache的理解
Linux中Buffer和Cache的区别 Cache 和 Buffer的区别 作者:知乎用户链接:https://www.zhihu.com/question/26190832/answer/323 ...
- 子线程里调用performSelector需要注意什么
以下代码执行顺序是什么 ? - (void)action { NSLog(@"1"); dispatch_queue_t queue = dispatch_get_global_q ...
- Qt for Android (二) Qt打开android的相册
以下有一个可以用的Demo https://files.cnblogs.com/files/wzxNote/Qt-Android-Gallery-master.zip 网盘地址: https://pa ...
- tomcat 启动报错org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].xxx
今天在写完一个非常简单的servlet页面跳转的web项目后,启动tomcat报错org.apache.catalina.LifecycleException: Failed to start com ...
- spring配置hibernate在使用oracle驱动时报错Cannot load JDBC driver class 'oracle.jdbc.driver.OracleDriver '
在看到这个错误的时候就感觉有点不对劲了,在错误的结尾和引号之间还有空间,如果敏锐的点应该察觉到可能是空格问题.由于本人的粗心导致这个问题一直困扰了我接近一个上午. 在排查这个问题的时候首先想到的就是关 ...
- 50行代码写的一个插件,破解一个H5小游戏
小游戏链接:测测你的眼睛对色差的辨识度http://www.webhek.com/post/color-test.html?from=timeline 废话不多说,先放代码: window.onloa ...
- appStore上传苹果应用程序软件发布
首先确定帐号是否能发布, https://developer.apple.com/account,如果你打开Provisioning Portal,然后点击DisTribution(1)图中加号是灰色 ...
- vue 项目 使用sass以及注意事项
vue 项目 使用sass以及注意事项 1,安装依赖: npm install node-sass --save-dev npm install sass-loader --save-dev 注: 通 ...
- 深入理解Android插件化技术
深入理解Android插件化技术 转 https://zhuanlan.zhihu.com/p/33017826 插件化技术可以说是Android高级工程师所必须具备的技能之一,从2012年插件化概 ...