首先,给大家简单介绍一下Istio,Istio是一个Service Mesh的开源框架,来自Google,大部分使用Go语言来开发,是Service Mesh的集大成者。
Istio数据层面主要使用envoy,Istio开发了一些 filter 扩展envoy的功能,这些功能主要集中在mixer上。Istio是新鲜出炉的技术,2017年5月0.1 release版本横空出世,不过版本更新迭代很快,最新的版本是今年3月发布的,1.1.3版。
Istio简介
Istio在逻辑架构上由数据平面和控制平面组成。数据平面是经典的实现方式,由一组以 sidecar 方式部署的智能代理(Envoy)组成。这些代理可以调节和控制微服务及 Mixer 之间所有的网络通信。
在每一个容器中注入了一个sidecar,Istio的sidecar称之为Istio proxy,相当于envoy 加上Istio开发的envoy filter。Istio proxy可以拦截整个容器的入口和出口流量。根据sidecar从数据层面接受到的规则和策略,进行一系列对于网络流量的配置和管理,比如路由管理,加密,遥测数据收集。控制平面负责管理和配置代理来路由流量,此外,控制平面还配置 Mixer 以实施策略和收集遥测数据。
Istio有几大块功能,首先是流量控制,这个基本上是通过Istio里的pilot组件来实现的。从图上我们可以看到,rules api就是pilot提供的抽象API,用户通过api来配置相应的规则。Paltform Adapter主要是兼容不同的平台。用户针对不同的平台实现服务发现等功能。基于用户的规则和从平台收集的数据,通过envoy api把具体的规则下发到各个容器的Istio proxy。 这里实现了大部分我们需要的流量管理功能,例如负载均衡策略,路由控制。我们可以把本来发给服务A的请求,都导向另一个服务。 故障注入,为测试进行模拟。以及熔断,url重写,重试,超时等等。
Istio的第二块,主要是安全机制。我们可以对网格内的微服务通讯进行tls加密。在发起请求的Istio proxy对请求使用tls证书加密,在接受请求的Istio proxy对这个请求解密,这对于应用的开发者是无感的。Istio最优秀的地方就是对于业务开发者的基本无感。这些网络调用相关的功能和策略被抽离出来,不用再放在应用的代码里。
Istio还提供了类似k8s rbac的权限实现方式。在servicerole定义使用service api的权限,通过service role binding绑定在k8s service account上,然后当应用通过service accout启动后, 该应用就有service role里定义的访问其他服务的权限。
下面一块是策略和遥测,这个是Istio中争议非常大的一个功能。好的方面是设计非常干净。Istio proxy只需要把它收集到的attribute发给mixer,mixer的Adapter基于attribute来做相应的操作。一类attribute用来做策略检查,例如Qouta,另一类用来做遥测数据收集,如promothues。 这其中体验不好之处就在策略检查上面。因为每个请求都需要做这个检查,这个检查目前看来会影响整体的性能。1.1开始,Istio提供了简单的方式可以关闭全局这个检查。如果你对性能要求比较高,目前最好还是先关闭。
Istio 1.1的发布对性能进行了大幅优化。官方提供的数据是:1000 k8s service, 2000个sidecar,每秒70000个请求在这个mesh网格中。这时候每个proxy使用0.6个CPU,50M内存来支持每秒1000个请求。Istio的遥测,就是mixer需要0.6个cpu来支持每秒1000次的请求。这个地方并没有说是不是包括策略检查。我感觉是不包含的。pilot需要1个cpu和1.5G的内存来做服务下发。最后,Istio proxy对服务间调用的性能影响是tp90 8ms。
微服务体系带来的问题
原来的微服务体系中都面临哪些难题呢?
首当其冲是定位和调试困难。当遇到bug或者性能问题,原来的方式基本都是逐级排查,从客户遇到问题的地方开始。因为一个深层次的微服务会引起一系列的上层微服务出现问题。如果发现两个服务直接之间的整体调用性能不好,这个时候哪怕你找到某一次性能差的日志或数据,基于这个数据和日志找出来的原因不一定是root cause。这种排查问题的方式就像烽火台,你只看到了最近的烽火台,并不知道起点在哪。
第二,测试时数据会有遗漏,缺少完整的测试数据。
最好的测试数据是线上的真实数据。但是线上的请求采集下来还需要独立开发相应的程序,整体实现很麻烦。另外,如果测试微服务的错误处理,对于QA的黑盒测试来说,这还需要一个可配置的错误生成器。这点对于测试也是一个独立的工作。
第三,缺少上线流程。
我们原来使用独立的微服务作为开关,来判断是否加载新功能。在新的功能代码外层加上调用该微服务的代码,根据返回值来判断是否执行新功能代码,上线完成后再把开关代码删掉,的确有点麻烦。上线前需要修改源码增加控制,上线完成后还需要在源码中删除这些逻辑。没有简单、无侵入的金丝雀和灰度发布的实现方式。
第四,微服务间的网络调用策略配置不灵活。
不同的客户环境需要使用不同的网络策略,例如重试,超时等等设置,如果对应的设置没有通过配置文件暴露出来,就只能对代码进行修改。而且这些代码在业务代码里,统一的维护和升级都需要独立的流程。
灵雀云ASM如何解决上述问题?
灵雀云从去年就开始有针对性地在ASM产品中解决这些问题。下面是我们ASM的总体架构图:
ASM controller是我们开发的K8s controller,主要处理我们自己定义的crd,以及做一些自动化的K8s资源处理;Dablo是ASM的前端界面;controller和diablo都会直接和K8s api server来通讯;Istio gateway是Istio用来服务南北流量的接口,主要用来暴露集群里的微服务;jaeger分为collector和query;collector直接从Istio proxy收集上报的调用链路数据,目前数据格式还是用的zipkin,jqeger query是用来显示调用链路的;为了做namespace的数据隔离,我们对jaeger的组件都做了相应的改造;存储方式是ES;遥测数据用prometheus来存储,数据从mixer收集而来;diablo通过直接调用prometheus api来获取。
关于定位和调试,灵雀云主要通过两方面来解决:一方面使用promothues里的遥测数据来绘制实时的拓扑图。展示调用关系,以及调用数据,包括流量,性能,错误率等。另一方面,通过jaeger的调用链来解决。我们可以查看完整的调用链路,具体到某次调用的时候,请求的内容,以及返回的状态码。
下面是我们预览版产品中的一些截图。这个截图来自于灵雀云PaaS平台的数据。我们可以看到整个平台所有组件之间的调用关系,右边是某两个组件之间的调用数据,包含调用次数,rps,以及响应时间这样的性能指标。这些可以帮助我们快速做定性的判断。
后面两张图是我们嵌入的jaeger query组件。当前面做了定性判断后,这里可以查询具体的问题是什么,通过过滤条件来缩小范围,然后具体看某一个请求的详细信息。
为了使用Istio这些功能,需要做些什么配置呢?
1.所有的微服务中注入sidecar;
2.pod里的container声明了自己监听的端口,保证能够拦截入口流量;
3.pod的label需要app和version两个key;
4. K8s service里声明的port都必须包含name字段,根据使用的协议name的格式有一定的规则。例如使用是http协议,name可以为http或者以“http-”开头;
5.服务调用的代码需要做稍微的改造,需要获取上一个请求header里的一些字段,包括request id,trace_id, span_id。把他们设置在header中传递给下一个调用。这个在Istio官方的文档里可以找到。
除了最后需要对代码做少许的修改,前面都只是需要修改服务部署的yaml。
通过Istio提供的流量镜像功能,我们可以很容易的使用生产环境来测试新的代码。只需要把测试代码通过一个独立的应用直接发布到生产环境,然后通过配置把流量拷贝一份调用这个测试代码的应用就好了。
Istio的错误注入功能很容易模拟返回错误的状态码,增加请求返回的延迟。
在安全上线方面,在生产环境同时发布新、老版本,通过拓扑图和调用链的数据,来观测新版本是否可以正常工作。我们通过流量的权重来实现灰度发布,通过一些规则设置来实现金丝雀发布。加上前面的生产环境测试,对于安全上线提供了很大的保证。
最后,灵活的网络策略。通过istio的Virtual Service和Destination Rule这两种资源,实现灵活的配置微服务间的网络访问策略。终于不用把这些策略的配置写到我们的代码里来,Istio的virtual service和destiinatio rule就完全实现了。