ServiceMesh已经不是一个新概念,但大家对ServiceMesh的探索依然火热。本文将依次讲解ServiceMesh的定义(什么是ServiceMesh)、成因(为什么需要ServiceMesh)和现状(ServiceMesh的主流实现),希望能通过通俗易懂的方式帮助大家更好地理解ServiceMesh-了解介绍。前言随着云原生时代的到来,微服务架构和容器化部署模式越来越流行,慢慢从最初的时髦词汇演变为现代IT企业的技术标准配置。曾经被视为理所当然的巨型单体应用,被拥抱微服务的架构师小心翼翼地拆分成小而独立的微服务,再被拥抱容器化的工程师打包成独立的微服务。带有依赖的Docker镜像最终通过某种神秘的DevOps管道——也就是众所周知——风暴的诞生·谷歌之子·Shacklebreaker·云时代操作系统·Kubernetes——部署运行。听起来一切都很顺利?显然不是,这个世界上永远没有免费的午餐。美好的事物都有其阴暗面,微服务也不例外:过去只需要部署和管理单个应用,现在拆分成多个,运维管理成本成倍增加。本来各个模块之间的交互可以通过应用直接调用(进程间通信),现在拆分成不同的进程甚至节点,只能使用复杂的RPC通信。难不成我们辛辛苦苦做了微服务,却只能在老板面前坚持“没问题,一切都好”,而默默忍受研发和运维的私怨?很明显不是。对于以“懒”着称的程序员来说,方法总是比困难多。例如,对于上面的第一个问题,云原生提倡的DevOps和容器化是一剂近乎完美的解药:通过自动化的CI/CD流水线,让多应用的集成构建和部署变得更快;通过Docker镜像和K8sOrchestration,多应用的资源调度、运维管理也变得不那么痛苦。至于第二个问题,是时候看看本文的主角——服务网格(ServiceMesh)如何力挽狂澜,近乎完美地解决微服务之间的通信问题了。什么是服务网格?ServiceMesh从概念到实现诞生?不,是从实施到概念。时间拨回到2016年9月29日,那是一个即将迎来全世界(也就是我们)欢庆假期的日子。在Buoyant内部的一次微服务分享会上,“ServiceMesh”这个近几年占据各种云原生头条的流行词就是这样诞生的。不得不说,命名真的是一门艺术,Micro-Services->ServiceMesh,如何承前启后,顺其自然,光是看名字就可以形象的理解这个东西是干什么的:把每一个服务(service)的微服务节点之间通过网状连接。就这样,原本拆散的微服务,被ServiceMesh这个大网络紧紧地连接在一起;即使它们仍然彼此分离(进程之间隔离),但它们也恢复了过去挤在一起的单个应用程序。内心拥抱的亲密感(交流更容易)。最难得的是,这么好的概念居然不是PPT出来的,他们还真有货(这让广大PPT创业者都觉得尴尬):2016年1月15日,ServiceMeshLinkerd的首次实现【1]已完成初始发布,次年1月23日加入CNCF,同年4月25日发布1.0版本。对于Buoyant来说,这可能只是无意中的一小步,但却是在云原生领域迈向成熟的一大步。几年后的今天,ServiceMesh的概念早已深入人心,各种生产级实现和规模化实践也遍地开花,但请不要忘记背后的功臣这篇,ServiceMesh革命的先驱,BuoyantCompany的CEO——WilliamMorgan,以及他对ServiceMesh的定义和思考:什么是服务网格?为什么我需要一个?[2]ServiceMesh的定义别再废话了,没时间听你讲那么多,请你一句话给我解释一下ServiceMesh是什么。好的服务网格是用于处理服务间通信的专用基础设施层。它负责通过构成现代云原生应用程序的复杂服务拓扑可靠地交付请求。在实践中,服务网格通常被实现为一组轻量级网络代理,它们与应用程序代码一起部署,应用程序不需要知道。这是上面帅气干练的CEO对ServiceMesh的权威定义。我强调了一些关键词:“专用基础设施层”:ServiceMesh不是用来解决业务领域问题的,而是一层专门的基础设施(中间件)。“service-to-servicecommunication”:ServiceMesh的定位非常简单明了,就是用来处理服务之间的通信。《请求的可靠传递》:为什么服务间通信需要特殊处理?因为网络是不可靠的,ServiceMesh的愿景是让服务间的请求传递变得可靠。“云原生应用”:ServiceMesh从一开始就是为现代云原生应用而生,瞄准未来的技术发展趋势。《网络代理》:ServiceMesh应该如何实现?典型的方法是使用一组轻量级网络代理在应用程序不知情的情况下秘密执行此操作。“与应用代码一起部署”:这些网络代理必须与应用一起部署,一对一的近距离贴心服务(比房产中介更专注);否则,如果应用程序和代理之间的远程通信仍然不可靠,这件事还没有结束。ServiceMesh要想致富,先修路;但是大路不是马,而是更现代的汽车。左图是Linkerd的部署示意图,在每个微服务所在的主机(host)或容器组(pod)中部署一个Linkerd代理软件,代理微服务应用实例之间的RPC调用。对于应用程序来说,这一切都是无意识的:它仍然像往常一样发起自己的RPC调用,但它不再需要关心对端服务器的地址,因为服务发现由代理节点覆盖。右边是一张更高维抽象的大图,可以更好的理解ServiceMesh的逻辑形态——想象这是一个生产级的大规模微服务集群,里面部署了上百个服务实例和对应的ServiceMesh代理节点;所有微服务之间的通信都将流经这些密集的代理节点,它们共同构成了一个连续的现代交通网格。为什么需要服务网格?微服务的兴起大爆炸:大爆炸后的混乱统治。我们大多数人都经历过单一应用程序为王的时代。所谓“单体”,就是将所有的组件都塞进了同一个应用程序中,那么这些组件自然而然地紧密相连:基于同一个技术栈开发,接入共享数据库,共同部署运维和扩展。同时,这些组件之间的通信也趋于频繁和耦合——它只是一个函数调用,何乐而不为呢。这样做本身并没有错。毕竟当时的软件系统还是比较简单的。一个人写一个20000行代码的应用,轻松搞定所有业务场景是可能的。天下大事,分久必合,分久必分。现代软件系统的复杂性不断增加,协作的人越来越多,单体应用的固有局限性开始暴露出来。就像大爆炸前的奇点一样,单体应用开始加速膨胀,终于在几年前达到了临界点,然后“砰”的一声爆炸了。就这样,微服务时代的王者来了,让软件开发再次“小而美”:单一职责:拆分后的单个微服务通常只负责单个高内聚的自闭环功能,所以它是易于开发、理解和维护。架构灵活:不同的微服务应用在技术选型层面几乎是独立的,可以自由选择最适合的技术栈。部署隔离:与巨型单体应用相比,单个微服务应用的代码和产品体积大大减少,持续集成和快速部署变得更加容易;同时,通过进程级别的隔离,不再只是一个单体应用。同生同死,故障隔离效果明显提升。独立扩展:单体应用时代,如果某个模块出现资源瓶颈(如CPU/内存),只能随着整个应用一起扩展,浪费了大量资源。微服务化后,将扩展的粒度细化到微服务层面,可以更精准地按需独立扩展。但显然,微服务也不是灵丹妙药。大爆炸虽然打破了单体应用的独裁统治,但大爆炸之后的微服务新宇宙显然不会马上尘埃落定,而是需要经历漫长的混沌期。适应了单体时代的开发者被迫去拥抱微服务带来的一系列变化。最大的变化是服务间通信:如何找到服务的提供者?微服务通信必须使用远程过程调用(HTTP/REST本质上也是RPC),当一个应用需要消费另一个应用的服务时,它不能像单个应用那样,通过简单的in-流程机制(如Spring的依赖注入);你甚至不知道是否有这样的服务器。如何保证远程调用的可靠性?既然是RPC,就必须要经过IP网络,而我们都知道网络(相对于计算和存储)是软件世界中最不可靠的东西。虽然有TCP等可靠的传输协议,但经常会出现丢包、交换机故障甚至断线等情况;网络再好,万一对方机器挂了,或者进程负载太高没反应怎么办?如何减少服务调用的延迟?网络不仅不可靠,而且存在延迟问题。同一个系统中的微服务应用虽然通常部署在一起,但是同一个机房??的调用延迟很小;但是对于更复杂的业务环节,很可能一个业务访问会包含几十个RPC调用,累积起来的延迟是非常可观的。如何保证服务调用的安全性?网络不仅不可靠和延迟,而且不安全。在网络时代,你永远不知道坐在屏幕对面的是人还是狗;同样,微服务之间通信时,如果直接使用裸通信协议,你永远不知道对方是否真的是自己的,也不知道传输的保密性。信息是否被中间人窃听。服务通讯:石器时代的毛主席说:自己动手,丰衣足食。为了解决微服务引入的上述问题,最早一批吃螃蟹的工程师开始了自己的造轮之旅:ServiceDiscovery:解决“我要给你打电话,怎么找到你”的问题。断路器:缓解服务之间依赖关系的不可靠性。负载均衡:通过平均分配流量,可以更及时地处理请求。安全通信:包括协议加密(TLS)、身份认证(证书/签名)、访问认证(RBAC)等。用自己的代码解决问题,确实是程序员可以做的事情,也没有错。可是,时间都去哪儿了?重新发明轮子:需要编写和维护大量非功能代码。如何专注于业务创新?与业务耦合:服务通信逻辑和业务代码逻辑混杂在一起,动不动就会出问题。令人难以置信的分布式错误。服务通讯:现代社会主义精神:共享和重用。觉悟比较高的那群工程师坐不住了:你们这是违反了共享重用的原则,对不起GNU老祖宗!于是,各种高质量、标准化、有望统一的精品轮子应运而生,包括ApacheDubbo(手动顶)、SpringCloud、NetflixOSS、gRPC等。这些可重用的类库和框架确实带来了质量和效率上的巨大提升,但是这还不够好吗?不够:不完全透明:程序员仍然需要正确理解和使用这些库,入门成本和出错概率仍然很高。限制技术选择:使用这些技术后,应用程序很容易被相应的语言和框架强绑定(供应商锁定)。维护成本高:更新库版本需要同时重建和部署应用;不说麻烦,还要祈求不要失败。ServiceNewsletter:新一代ServiceMesh:我只是一个搬运工。ServiceMesh的诞生彻底解决了以上所有问题。听起来很神奇,它是怎么做到的?简单来说,ServiceMesh使用Sidecar模式[3],将上述类库和框架从应用中完全分离出来,下沉到一个统一的基础设施层。这是什么想法?这是一种在古代操作系统中早已存在的抽象和分层的思维(应用程序不需要关心网络协议栈),也是现代云计算平台逐步自下而上的承载。软件即服务思维(IaaS->CaaS->PaaS->SaaS)。上述ServiceMesh的演化图参考了文章ServiceMeshPattern[4]。ServiceMesh的主流实现注:以下内容来自资料收集,仅供参考。进一步研究,请参考最新的权威资料。主流实现概览ServiceMesh的主流实现包括:Linkerd:背后的公司是Buoyant,开发语言使用Scala。2016年1月15日首次发布,2017年1月23日加入CNCF,2018年5月1日发布1.4.0版本。Envoy:背后的公司是Lyft,开发语言使用C++11。2016年9月13日首次发布,2017年9月14日加入CNCF,2018年3月21日发布1.6.0版本。Istio:背后公司为Google和IBM,开发语言使用Go。2017年5月10日首次发布,2018年3月31日发布0.7.1版本。Conduit:背后的公司也是Buoyant。开发语言使用Rust和Go。2017年12月5日首次发布,2018年4月27日发布0.4.1版本。Linkerd简介Linkerd的核心组件是服务代理,只要弄清楚它的请求处理流程,其核心逻辑就可以掌握:动态路由:根据上游服务请求参数确定下游目标服务;除了常规的服务路由策略,Linkerd还可以利用这一层动态路由能力来支持灰度发布、A/B测试、环境隔离等非常有价值的场景。服务发现:确定目标服务后,下一步就是获取对应实例的地址列表(如查询服务注册中心)。负载均衡:如果列表中有多个地址,Linkerd会通过负载均衡算法(例如LeastLoaded、PeakEWMA)选择一个合适的低延迟实例。执行请求:向上一步选择的实例发送请求,并记录延迟和响应结果。重试处理:如果请求没有响应,则选择另一个实例重试(前提:Lin??kerd知道请求是幂等的)。断路器处理:如果发送给某个实例的请求经常失败,则该实例会主动从地址列表中移除。超时处理:如果请求超时(在给定的deadline时间点之前还没有返回),会主动返回失败响应。可观察性:Linkerd将持续收集和报告上述各种行为数据,包括Metrics和Tracing。Envoy简介Envoy是一款高性能的ServiceMesh软件,主要包括以下几个特点:高性能:基于本地代码(C++11);相比之下,Linkerd是基于Scala写的,速度肯定慢很多。可扩展:L4和L7层代理功能均基于可插拔的FilterChain机制(类比netfilter、servletfilter)。协议升级:支持双向透明的HTTP/1到HTTP/2代理能力。其他能力:服务发现(根据最终一致性)、负载均衡(支持区域感知)、稳定性(重试、超时、断路器、限速、异常检测)、可观察性(统计/日志/追踪)、易调试等。Istio简介Istio是一个完整的控制/数据平面分离的ServiceMesh套件,包括以下组件:Envoy:构成数据平面(其他组件共同构成控制平面);可以被其他代理替换(例如Linkerd、nginMesh)。Pilot:负责TrafficManagement,提供平台无关的服务模型定义、API和实现。Mixer:负责Policies&Controls,核心功能包括:预检、配额管理、遥测报告。Istio-Auth:支持多粒度的RBAC权限控制;支持双向SSL认证,包括身份识别、通信安全、密钥管理等。Istio组件-PilotPilot组件是Istio服务网格中的“导航器”,负责管理数据平面流量规则和服务发现。一个典型的应用场景是灰度发布(或金丝雀发布,蓝绿部署):开发者通过Pilot提供的规则API,将流量路由规则发送给数据面的Envoy代理,从而实现精准的多版本流量分发(例如,将1%的流量分配给新版本服务)。Istio组件——MixerMixer组件是Istio服务网格中的“调谐器”,负责执行各种流量策略(如访问控制、限速),同时还负责观察和分析流量(如日志记录、监控、和追踪)。这些能力都是通过上面提到的EnvoyFilterChain扩展机制实现的:Mixer会分别在“Pre-routing”扩展点和“Post-routing”扩展点挂载自己的Filter。完成。Istio组件——AuthAuth组件是Istio服务网格中的“安全官”,负责处理服务节点间通信的认证(Authentication)和授权(Authorization)问题。在认证方面,Auth支持服务间的双向SSL认证,让双方通过通信来识别对方的身份;在鉴权方面,Auth支持流行的RBAC鉴权模型,可以实现便捷、细粒度的“用户-角色-权限”多级访问控制。Conduit简介Conduit是Buoyant出品的下一代ServiceMesh。作为Istio的挑战者,Conduit的整体架构与Istio相似,明确区分了控制平面和数据平面,但它还具有以下关键特性:轻量级和快速:Conduit的数据平面基于原生Rust语言编写,即非常轻量化、速度和安全性(Rust相比C/C++最大的提升就是安全性)。单个agent的实际内存消耗(RSS)小于10mb,延迟p99分位数小于1ms,基本相当于为应用提供免费(无额外开销)的ServiceMesh功能。安全保障:Conduit在建设之初就考虑了云原生环境的安全性,包括Rust语言内存安全、默认TLS加密等。端到端可见性:Conduit可以自动测量和汇总服务成功率、延迟和请求容量数据,让开发者无需更改应用程序代码即可获得服务的完整行为视图。Kubernetes增强功能:Conduit为K8s集群增加了可靠性、可见性和安全性,同时让开发人员可以完全控制其应用程序的运行时行为。结束语本文从云原生时代面临的微服务通信问题入手,依次介绍了ServiceMesh的起源、发展和现状,希望能帮助读者建立初步的了解和认知。当然,真知灼见,不如退而求其次织一个ServiceMesh(自己织一个ServiceMesh),而不是面对原先鱼(觊觎ServiceMesh的技术红利)。手头的工作没有实际的业务场景?没关系,我有:欢迎各位技术同路人加入阿里云云原生应用研发平台EMAS团队。我们专注于广泛的云原生技术(BackendasaService、Serverless、DevOps、low-codePlatform等),致力于为企业和开发者提供一站式的应用研发管理服务,直接内部推荐给邮箱:pengqun.pq#alibaba-inc.com,来信必回。相关链接[1]https://github.com/linkerd/linkerd[2]https://buoyant.io/2017/04/25/whats-a-service-mesh-and-why-do-i-need-one/[3]https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar[4]https://philcalcado.com/2017/08/03/pattern_service_mesh.html
