1.1 Service Mesh
1.1.1 什么是Service Mesh
Service Mesh是最近才兴起的一个名词,最早在2016年9月29日由开发Linkerd的Buoyant公司首次提出[1]。2016年10月,Alex Leong开始在Buoyant公司的官方博客中连载一系列关于“A Service Mesh for Kubernetes”的文章。随着2017年Linkerd加入CNCF,Service Mesh开始大规模出现在各个技术论坛。Service Mesh在国内被翻译为“服务啮合层”或“服务网格”。那到底什么是Service Mesh呢?目前比较公认的是Buoyant的CEO William Morgan在博客中给出的定义[2],具体描述如下。
Service Mesh是用于处理服务到服务通信的专用基础架构层。Cloud Native有着复杂的服务拓扑,它负责可靠的传递请求。实际上,Service Mesh通常作为一组轻量级网络代理实现,这些代理与应用程序代码部署在一起,应用程序无感知。
随着Cloud Native的崛起,Service Mesh逐步发展为一个独立的基础设施层。在Cloud Native架构下,单个应用程序可能由数百个服务组成;每个服务可能有数千个实例;并且这些实例中的每一个实例都可能处于不断变化的状态,因为它们是由诸如Kubernetes之类的资源调度系统动态调度的。这个世界中的服务通信不仅非常复杂,而且是运行时行为的普遍和基本部分,管理它对于确保端到端的性能和可靠性至关重要。
Azure的云设计模式库中,也描述了类似的模式,名为Sidecar[3],这个概念很形象。就是我们以前在战争片中看到的那种挎斗车,如图8-2所示。
图82 现实中的Sidecar[4]
在模式库中,Sidecar被这样描述:
将应用程序的组件部署到单独的进程或容器中以提供隔离和封装。这种模式还可以使应用程序由异构组件和技术组成。
这种模式被命名为Sidecar,因为它类似于连接到摩托车的边车。在该模式中,辅助车被附加到父应用程序并为应用程序提供支持功能。边车也与父应用程序共享相同的生命周期,与父进程一起被创建和退出。
简单来说,Service Mesh帮助应用程序在海量服务、复杂的架构和网络中建立稳定的通信机制,业务所有的流量都转发到Service Mesh的代理服务中,不仅如此,Service Mesh还承担了微服务框架所有的功能,包括服务注册发现、负载均衡、熔断限流、认证鉴权、缓存加速等能力。不同的是,Service Mesh强调的是通过独立的进程代理的方式,除此之外,还承担了上报日志、监控的责任。Service Mesh架构,如图8-3所示。
图83 Service Mesh架构
1.1.2 为什么需要Service Mesh
Service Mesh的出现是由微服务架构推动的,原本一个应用,被拆分成几百个甚至几万个应用,服务治理面临这巨大的挑战。这个时候,微服务框架出现,例如,Finagle、Dubbo、Spring Cloud、Netflix OSS等。这些框架都是基于客户端负载均衡、直连的方式,此方案的优势是性能高、应用性好,如Dubbo,如果你使用Java语言,可以直接引入JAR包,通过简单的配置,就可以实现微服务之间的通信。
归根结底,在微服务架构中,我们要解决的问题是让开发人员感觉不到微服务之间的通信。当服务数量越来越多,升级微服务框架变得越来越复杂的时候,你不可能要求微服务框架一直不变,而且是没有bug的。在技术更新速度如此之快的年代,就更不可能了。因此,微服务框架的部分功能开始逐步向服务端移动,希望客户端可以尽量“薄”,但是客户端不可能无限制的“薄”,剩余部分仍然比较“厚”。
因为使用客户端更像一种交付的模式,不容易变更,控制力较差,而Service Mesh则从业务进程集成客户端的方式,演进为独立进程的方式,也就是说,原本的客户端变成了一个独立进程。对这个独立进程升级、运维要比绑在一起强得多。
微服务架构更强调去中心化、独立自治、跨语言,但是通常微服务框架限制了这一点,不可能为每种语言都实现一个框架,要么都用一种语言,要么实现多种语言的框架。而Service Mesh通过独立进程的方式进行了隔离,可以低成本实现跨语言。
随着Docker及Kubernetes的崛起,微服务的部署模式开始发生转变,越来越趋向于轻量级,越来越强调隔离自治。每个服务独立占用一个容器,将服务、依赖包、操作系统、监控运维所需的代理打包成一个镜像。这种模式促成了Service Mesh的发展,让Service Mesh实现起来更容易。否则开发人员需要额外维护Service Mesh进程,就非常麻烦了。
1.1.3 Service Mesh的现状
目前Service Mesh的框架如雨后春笋般快速涌现,以下几个框架是目前为止被提到次数最多的。
Linkerd
2016年1月15日,Twitter前基础设施工程师William Morgan和Oliver Gould组建了一个名为Buoyant的公司,同时在GitHub上发布了Linkerd 0.0.7版本。它由Scala实现,业界第一个Service Mesh项目诞生。2016年下半年,Linkerd陆续发布了0.8和0.9版本,宣布支持HTTP/2和gRPC。2017年1月23日,Linkerd加入CNCF。2017年4月25日,Linkerd发布了1.0版本。
Linkerd在生产环境得到了大规模使用,但是随着Istio的诞生,Linkerd开始走下坡路。由于Istio背后有强大的Google和IBM的支持,社区非常活跃,虽然到目前为止还没有大规模使用,但是业界已将Istio列为第二代Service Mesh。2017年7月11日,Linkerd迫于压力发布版本1.1.1,宣布和Istio项目集成,但是Linkerd在Istio中替代Envoy的难度非常大,前景并不是特别乐观。
Envoy
2016年9月13日,Lyft的Matt Klein宣布Envoy在GitHub开源,发布1.0.0版本。Envoy由C++实现,性能和资源消耗上非常出色。和Linkerd相比,Envoy发展得更平稳,被Istio收编之后,Envoy专注于数据平面。2017年9月14日,Envoy加入CNCF,成为CNCF继Linkerd之后的第二个Service Mesh项目。目前,Envoy已用于生产系统中,据Lyft介绍:“Envoy在Lyft上可以管理一百多个服务,跨域上万台虚拟机,每秒可处理近两百万次请求。”
nginMesh
2017年9月,在波特兰举行的nginx.conf大会上,Nginx宣布了nginMesh在GitHub上发布了0.1.6版本。nginMesh的定位是作为Istio的服务代理,也就是专注于数据平面,替代Envoy,和Linkerd与Istio集成的思路一致。nginMesh的发展一直比较缓慢,目前它还没有应用到生产环境中。
Conduit
由于Buoyant在Linkerd上受到Istio压制,2017年12月5日,Buoyant在KubeConf上发布了Conduit 0.1.0版本,作为Istio的竞争产品。Conduit的架构几乎和Istio一样,也分成了数据平面和控制平面,为了性能和资源占用方面的优势,它直接采用Rust语言开发。Conduit也被称为第二代Service Mesh。
Istio
2017年5月24日,Istio 0.1版本发布,目前已经到了0.7版本。Istio背后是强大的Google和IBM,所以Istio自诞生之日起就备受瞩目。Istio的初期版本只支持Kubernetes平台,从0.3版开始支持非Kubernetes平台,可以独立运行。
虽然它的架构思想被很多人认同,功能也很多,但是Istio的问题仍然较多,可用性较差,几乎没有生产级的案例。尽管如此,Istio被认为是Service Mesh的未来,很多公司都在跟随这个框架。
实际上,如果在一个小公司,Service Mesh的优势体现的并不是十分明显。例如小公司很少会考虑采用多语言,因为多一种语言就意味着要分散额外的精力进行维护,所以到目前为止,大多数公司在后端只使用一两种语言。另外,微服务框架相对来说比较稳定,就如同Spring Framework一样,不会轻易进行不兼容的升级,只要保持好节奏,问题不会太多。因此,像Netflix、阿里这样的公司,基本上还是沿用类似Spring Cloud的模式。
此外,Service Mesh降低开发人员的门槛也并没有那么明显,因为完全不考虑是不可能的,Service Mesh在没有理解业务的情况下,是不能私自做决定的。例如A服务调用B服务的超时时间,设置多少合理呢?还是需要开发人员根据业务进行配置的。
另外,Service Mesh对业务开发人员友好,但是基础设施层的研发会更复杂,需要依赖更多的服务、组件,否则将带来极大的架构、运维复杂度。对于很多公司来说,门槛稍高,且需要投入成本。
因此,对于绝大多数公司来说,Service Mesh并不具备压倒性的优势,在没有成熟起来以前,大多都是观望状态。
1.1.4 Istio架构分析
Istio架构如图8-4所示。Istio从逻辑上可以分为数据平面(Data Plane)和控制平面(Control Plane)。如果用服务化框架类比Istio,那么控制平面可以认为是注册中心及管理配置面板,数据平面可以认为是服务化框架依赖的组件独立而成的一个进程,数据平面代理业务服务的注册发现、负载均衡、容错等能力。
图84 Istio架构[5]
数据平面也就是代理,使用Envoy实现,独立进程,和业务服务部署在一起,主要是管理微服务之间的网络通信及执行控制平面下发的指令。Envoy支持的通信协议包括:HTTP/1.1、HTTP/2、gRPC、TCP等,它支持TLS。
控制平面负责管理和配置这些代理,并动态执行策略。主要包括三个部分,分别是Pilot、Mixer、Istio-Auth,都是有Go语言开发。
Pilot主要的责任就是管理Envoy实例的生命周期。Pilot通过Envoy API接入Envoy实例,主要是服务发现以及流量控制。运维人员可以通过Rules API自定义流量管理规则,转化为配置信息下发到Envoy,Envoy实例按照自定义的规则执行流控。从理论上说,Pilot抽象了模型,通过适配器可以适配Kubernetes、Mesos、CloudFoundry等平台,但是目前只支持Kubernetes。Pilot架构如图8-5所示。
图85 Pilot架构[6]
利用Pilot可以轻松实现流量控制,如限流、灰度发布、熔断等能力。图8-6中的配置代表了Pilot分发到v1版本75%的流量,分发到v2版本25%的流量。
图86 流量分发
此外,利用Pilot还可以轻松的注入故障。图8-7中的示例就表示选择“reviews”服务的“v1”版本的10%的请求中引入5秒的延迟。
图87 示例
Mixer负责通过策略进行访问控制,并从Envoy代理收集数据,代理提取请求级属性,发送到Mixer进行决策。要理解Mixer,首先需要理解Istio中属性的意义。属性是具有名称和类型的元数据片段,用来描述流量和这些流量所在的上下文环境。请求的路径、大小、时间、源IP地址及目标服务如下所示。
request.path: blog/article
request.size: 200
request.time: 13:34:56.789 04/17/2018
source.ip: 10.10.0.1
target.service: article
Mixer提供如下三个核心功能。
校验。在响应之前,先验证调用者的身份、是否在服务的白名单、是否通过ACL检查等。
配额。例如访问频率控制。当服务消费者对有限的资源进行争抢时,配额就成了一种有效的资源管理工具,它为服务之间的资源抢占提供相对的公平。
报告。使服务通过Envoy上报日志和监控。
Istio Auth的目标是在不修改业务服务代码的前提下,提升业务服务之间通信的安全性。Istio Auth利用secret volume mount,从Istio CA向Kubernetes容器传递keys/certs。由于Envoy代理一切流量,因此Envoy客户端和Envoy服务端之间使用TLS非常简单。Istio Auth为我们提供的能力如下。
每个服务都可以被设定身份,代表其角色,以实现跨集群和云的互通性。
可以加密服务间通信和终端用户到服务的通信。
提供密钥管理系统来自动执行密钥和证书的生成、分发、轮换和撤销。
在很多场景下,微服务通常不对外网开放访问权限,只有边缘服务才会通过API Gateway进行开放,而加密往往意味着性能的降低,因此通常会选择不加密,除非部署的环境比较特殊。