Service Mesh 是一个基础设施层,用于处理服务到服务间的网络通信。云原生应用有着复杂的服务拓扑,Service Mesh负责在这些网络拓扑中实现请求的可靠传递。在实践中,Service Mesh通常实现为一组轻量级的网络代理,它们与应用程序部署在一起,但是对应用保持透明。
本文作为 「Service Mesh」系列开篇,将理清 Service Mesh 的前世今生,通过对其概念与原理的理解,开始上手 Service Mesh的工作。与此同时,我们也会讨论 Service Mesh 在业界当前的应用现状,探讨其落地的难点与痛点。
历史演进
随着行业需求的推动,互联网服务从最早的仅有少数几台的大型服务器演变到成百上千的小型服务,服务架构也从最早期的单体式(Monolithic)到分布式(Distributed),再到微服务(Microservices)、容器化(Containerization)、容器编排(Container Orchestration),最后到服务网格(Service Mesh)、无服务器(Serverless)。
总结分布式系统的演进过程,我们可以看到一种通用的发展规律:
- 首先是对每种情况提出临时解决方案
- 然后是更复杂的解决方案,类似于 library 以实现统一复用
- 随着对问题有更多的了解,开始将这些解决方案落实到 platform
接下来我们会回顾从早期TCP/IP协议栈的广泛应用,到微服务时代从容器编排到服务网格的演进过程,并再次体会上述规律。
计算机网络系统的演进
从多台计算机开始通信以来,服务间通信是应用最为广泛的模式。以下图为例,ServiceA 和 ServiceB 可以是我们提供应用的服务端与客户端。在开发者开发这些服务的时候,需要借助底层的网络硬件和协议进行通信。这张图只是一个简化的师徒,省略了在代码操作的数据和通过线路发送接收的电信号之间转换的很多层。
更加具体一点,把底层的网络协议栈加入,我们会看到下图:
从上世纪50年代起,上述的模型就一直在使用。最开始,由于计算机系统规模相对较小,每个节点之间的链路协议都是经过专门设计和维护的。随着计算机规模的迅速扩大,很多个小的网络系统开始连接起来。在这个过程中,不同主机间如何找到彼此,跨网络间如何路由转发,如何实现流量控制等问题,成了摆在网络系统设计人员面前亟需解决的难题。
为了实现各个网络节点的路由转发,屏蔽链路层协议,人们发明了IP网络协议。然而,IP网络协议还不能够解决流量控制的问题。这里的流量控制,值得是防止一台服务器发送过多的数据包,超出下游服务器的处理能力。在最开始,编写网络服务和应用程序的开发者来负责处理上述流量控制的问题。这就意味着在编写应用程序过程中,网络处理的逻辑和应用自身的业务逻辑被耦合在一起,如下图所示。
然而,这种每个开发人员都要去考虑流量处理等传输层的问题太过复杂,程序开发的成本太高。随着技术的快速发展,流量处理和其他网络问题相关的解决方案被整合到网络协议栈,TCP/IP席卷了世界,成为互联网事实上的协议标准。流量控制等网络问题的代码仍在,但是你不再需要自己去开发与维护这段代码,而是直接调用系统提供的网络协议栈。
微服务架构的演进
确定于上世界80年代的TCP/IP网络协议栈和通用的网络模型对于互联网的发展发挥了巨大的作用,极大了促进了互联网应用的繁荣。网络应用的功能逐渐复杂起来,人们把所有的组件都集中在一个应用当中,这即是单体应用 Monolithic
。单体应用基于相同技术栈开发、访问共享的数据库、共同部署运维和扩容。同时,组件之间的通信也趋于频繁和耦合,所有的交互都是以函数调用的形式来实现。
然而,随着互联网的迅猛发展,网络应用中需要添加越来越多的功能,应用的复杂度不断提升,参与软件开发的协作人数也越来越多,单体应用开始爆发出其固有局限性。在这种背景下,微服务的思潮降临,让软件开发重新变得小而美:
- 单⼀职责:拆分后的单个微服务,通常只负责单个高内聚自闭环功能,因此很易于开发、理解和维护。
- 架构灵活:不同微服务应用之间在技术选型层面几乎是独立的,可以⾃由选择最适合的技术栈。
- 部署隔离:相比巨无霸单体应用,单个微服务应用的代码和产物体积大大减少,更容易持续集成和快速部署;同时,通过进程级别的隔离,也不再像单体应用一样只能同生共死,故障隔离效果显著提升。
- 独⽴扩展:单体应用时代,某个模块如果存在资源瓶颈(e.g. CPU/内存),只能跟随整个应用一起扩容,白白浪费很多资源。微服务化后,扩展的粒度细化到了微服务级别,可以更精确地按需独立扩展。
然而,微服务也不是银弹,在微服务落地的过程中,也产生了很多的问题,其中主要的问题就是服务间通信:
如何找到服务的提供⽅?
微服务通讯必须走远程过程调用(HTTP/REST本质上也属于RPC),当其中一个应用需要消费另一个应用的服务时,无法再像单体应用一样通过简单的进程内机制(e.g. Spring的依赖注入)就能获取到服务实例;你甚至都不知道有没有这个服务方。
如何保证远程调⽤的可靠性?
既然是RPC,那必然要走IP网络,而我们都知道网络(相比计算和存储)是软件世界里最不可靠的东西。虽然有TCP这种可靠传输协议,但频繁丢包、交换机故障甚至电缆被挖断也常有发生;即使网络是好的,如果对方机器宕机了,或者进程负载过高不响应呢?
如何降低服务调⽤的延迟?
网络不只是不可靠,还有延迟的问题。虽然相同系统内的微服务应用通常都部署在一起,同机房内调用延迟很小;但对于较复杂的业务链路,很可能一次业务访问就会包括数十次RPC调用,累积起来的延迟就很可观了。
如何保证服务调⽤的安全性?
网络不只是不可靠和有延迟,还是不安全的。互联网时代,你永远不知道屏幕对面坐的是人还是狗;同样,微服务间通讯时,如果直接走裸的通讯协议,你也永远不知道对端是否真的就是自己人,或者传输的机密信息是否有被中间人偷听。
服务通信:耦合业务逻辑
就像历史总是会重演,为了解决上述微服务引入的问题,最早需要工程师独立去完成对应的服务,在业务逻辑中实现下列逻辑:
- 服务发现(Service Discovery):解决“我想调用你,如何找到你”的问题。
- 服务熔断(Circuit Breaker):缓解服务之间依赖的不可靠问题。
- 负载均衡(Load Balancing):通过均匀分配流量,让请求处理更加及时。
- 安全通讯:包括协议加密(TLS)、身份认证(证书/签名)、访问鉴权(RBAC)等
然而,随着分布式程度的增加,这些服务的复杂度也越来越高,一些问题不得不考虑:
- 重复造轮子:需要编写和维护⼤量非功能性代码,如何集中精力专注业务创新?
- 与业务耦合:服务通讯逻辑与业务代码逻辑混在一起,动不动还会遇到点匪夷所思的分布式bug。
服务通信:独立Library
为了解决重复造轮子的问题,集成了服务通信中各种问题的Library开始变得十分流行,包括 Apache Dubbo(手动置顶)、Spring Cloud、Netflix OSS、gRPC 等等。
这些可复用的类库和框架,确确实实带来了质量和效率上的大幅提升,但是也存在着下列问题:
- 并非完全透明:程序员们仍然需要正确理解和使⽤这些库,上手成本和出错概率依然很高。
- 限制技术选择:使用这些技术后,应用很容易就会被对应的语⾔和框架强绑定(vendor-lock)。
- 维护成本高:库版本升级,需要牵连应⽤一起重新构建和部署;麻烦不说,还要祈祷别出故障。
服务通信:Sidecar
像网络协议栈发展的过程一样,将大规模分布式服务所需要的功能剥离出来集成到底层平台是一个众望所归的选择。人们通过应用层的协议(例如HTTP)写出了很多复杂的应用程序和服务,甚至不用考虑TCP是如何控制数据包在网络上传输的。这就是我们微服务所需要的,从事服务开发的工程师们可以专注于业务逻辑的开发,避免浪费时间去编写服务基础设施代码或者管理这些库和框架。
在这个想法下,我们可以得到类似于如下的图:
不幸的是,更改协议栈来增加微服务的功能不是一个可行的方案,许多开发者是通过一组代理来实现此功能。这里的设计思想是服务不需要和下游服务直连,所有的流量都通过该代理透明的来实现对应的功能。这里的透明代理,通过一种叫做 Sidecar
的模式来运行,Sidecar将上述类库和框架要干的事情从应用中彻底剥离了出来,并统一下沉到了基础设施层,这其中的典型代表就是 Linkerd 和 Envoy。
服务通信:Service Mesh
在这种模型中,每个服务都会有一个配套的代理SideCar。考虑到服务之间的通信仅仅通过SideCar代理,我们最终得到如下的部署图:
Buoyant的CEO William Morgan ,发现了各个SideCar代理之间互联组成了一个网状网络,2017初,William为这个网状的平台起了一个“Service Mesh”的定义。
Service Mesh是一个用于服务和服务之间通信的专用基础设施层。它负责服务请求能够在复杂的服务拓扑(组成了云原生应用)中可靠的进行投递。在实践中,Serivce Mesh的典型实现是作为轻量级网络代理阵列,部署在应用程序旁边,不需要业务进程感知到。
William关于Service Mesh的定义中,最有说服力的一点是,他不再将SideCar代理视为一个独立组件,而是承认了它们组成的网络像它们自身一样是有价值的
随着很多公司将它们的微服务部署到更复杂的系统运行环境中,例如Kubernetes和Mesos,人们开始使用这些平台提供的工具来实现合适的Serivce Mesh的想法。它们将独立的SideCar代理从独立的工作环境中转移到一个适当的,有集中的控制面。
看下我们的鸟瞰图,服务之间的流量仍然是通过SideCar代理来进行转发,但是控制平面知道每个SideCar实例。控制平面能够让代理实现例如访问控制,指标收集等需要协作完成的事情。Istio是这个模型的典型实现。
主流实现
Service Mesh 的主流实现包括:
- Linkerd:背后公司是Buoyant,开发语⾔使用Scala,2016年1⽉15日初次发布,2017年1⽉23日加入CNCF。
- Envoy:背后公司是Lyft,开发语言使用C++ 11,2016年9月13日初次发布,2017年9⽉14日加⼊CNCF。
- Istio:背后公司是Google和IBM,开发语言使用Go,2017年5⽉月10日初次发布。
- Conduit:背后公司也是Buoyant,开发语言使用Rust和Go,2017年12月5日初次发布,现在已经加入了
Linkerd
项目。
Linkerd
现在(2020.09.08) Linkerd
已经发展到 2.8 版本,由控制面和数据面组成,详情可以参考 这里
Envoy
Envoy是一个高性能的Service Mesh软件,现在主要被用于数据面作为 Sidecar 代理,详情可以参考 这里
Istio
Istio是第二代 Service Mesh,第一次提出控制面的概念,详情可以参考 这里
NginMesh
Service Mesh 最基础的功能毕竟是 sidecar proxy. 提到 proxy 怎么能够少了 nginx? 我想nginx自己也是这么想的吧 毫不意外,nginx也推出了其 service mesh 的开源实现:nginMesh.
不过,与 William Morgan 的死磕策略不同,nginMesh 从一开始就没有想过要做一套完整的第二代Service Mesh 开源方案,而是直接宣布兼容Istio, 作为Istio的 sidecar proxy. 由于 nginx 在反向代理方面广泛的使用,以及运维技术的相对成熟,nginMesh在sidecar proxy领域应该会有一席之地。
对比Kubernetes原生架构
Kube-proxy vs Sidecar
下图展示的是 Kubernetes 与 Service Mesh 中的的服务访问关系:
- Kubernetes 集群的每个节点都部署了一个
kube-proxy
组件,该组件会与 Kubernetes API Server 通信,获取集群中的Service
信息,然后设置 iptables 规则,直接将对某个Service
的请求发送到对应的 Endpoint(属于同一组Service
的Pod
)上。 - Kube-proxy 实现了流量在 Kubernetes
Service
多个Pod
实例间的负载均衡,但是如何对这些Service
间的流量做细粒度的控制,比如按照百分比划分流量到不同的应用版本(这些应用都属于同一个Service
,但位于不同的 deployment 上),做金丝雀发布(灰度发布)和蓝绿发布?Kubernetes 社区给出了 使用 Deployment 做金丝雀发布的方法,该方法本质上就是通过修改Pod
的 label 来将不同的Pod
划归到 Deployment 的Service
上。
kube-proxy
的设置都是全局生效的,无法对每个服务做细粒度的控制,而 Service Mesh
通过 Sidecar
proxy 的方式将 Kubernetes 中对流量的控制从 Service
一层抽离出来,可以做更多的扩展。
Ingress vs Gateway
kube-proxy
只能路由 Kubernetes 集群内部的流量,而我们知道 Kubernetes 集群的 Pod
位于 CNI 创建的外网络中,集群外部是无法直接与其通信的,因此 Kubernetes 中创建了 Ingress 这个资源对象,它由位于 Kubernetes 边缘节点(这样的节点可以是很多个也可以是一组)的 Ingress controller 驱动,负责管理 南北向流量,Ingress 必须对接各种 Ingress Controller 才能使用,比如 nginx ingress controller、traefik。
- Ingress 只适用于 HTTP 流量,使用方式也很简单,只能对
Service
、port、HTTP 路径等有限字段匹配来路由流量,这导致它无法路由如 MySQL、Redis 和各种私有 RPC 等 TCP 流量。 - 要想直接路由南北向的流量,只能使用
Service
的 LoadBalancer 或 NodePort,前者需要云厂商支持,后者需要进行额外的端口管理。 - 有些 Ingress controller 支持暴露 TCP 和 UDP 服务,但是只能使用
Service
来暴露,Ingress 本身是不支持的,例如 nginx ingress controller,服务暴露的端口是通过创建 ConfigMap 的方式来配置的。
Istio
Gateway 的功能与 Kubernetes Ingress 类似,都是负责集群的南北向流量。Istio
Gateway
描述的负载均衡器用于承载进出网格边缘的连接。该规范中描述了一系列开放端口和这些端口所使用的协议、负载均衡的 SNI 配置等内容。Gateway 是一种 CRD 扩展,它同时复用了 Sidecar
proxy 的能力,详细配置请参考 Istio 官网。
落地问题
服务网格的出现带来的变革:
第一,微服务治理与业务逻辑的解耦。服务网格把 SDK 中的大部分能力从应用中剥离出来,拆解为独立进程,以 Sidecar 的模式进行部署。服务网格通过将服务通信及相关管控功能从业务程序中分离并下沉到基础设施层,使其和业务系统完全解耦,使开发人员更加专注于业务本身。
注意,这里提到了一个词“大部分”,SDK 中往往还需要保留协议编解码的逻辑,甚至在某些场景下还需要一个轻量级的 SDK 来实现细粒度的治理与监控策略。例如,要想实现方法级别的调用链追踪,服务网格则需要业务应用实现 trace ID 的传递,而这部分实现逻辑也可以通过轻量级的 SDK 实现。因此,从代码层面来讲,服务网格并非是零侵入的。
第二,异构系统的统一治理。随着新技术的发展和人员更替,在同一家公司中往往会出现不同语言、不同框架的应用和服务,为了能够统一管控这些服务,以往的做法是为每种语言、每种框架都开发一套完整的 SDK,维护成本非常之高,而且给公司的中间件团队带来了很大的挑战。有了服务网格之后,通过将主体的服务治理能力下沉到基础设施,多语言的支持就轻松很多了。只需要提供一个非常轻量级的 SDK,甚至很多情况下都不需要一个单独的 SDK,就可以方便地实现多语言、多协议的统一流量管控、监控等需求。
此外,服务网格相对于传统微服务框架,还拥有三大技术优势:
- 可观察性。因为服务网格是一个专用的基础设施层,所有的服务间通信都要通过它,所以它在技术堆栈中处于独特的位置,以便在服务调用级别上提供统一的遥测指标。这意味着,所有服务都被监控为“黑盒”。服务网格捕获诸如来源、目的地、协议、URL、状态码、延迟、持续时间等线路数据。这本质上等同于 web 服务器日志可以提供的数据,但是服务网格可以为所有服务捕获这些数据,而不仅仅是单个服务的 web 层。需要指出的是,收集数据仅仅是解决微服务应用程序中可观察性问题的一部分。存储与分析这些数据则需要额外能力的机制的补充,然后作用于警报或实例自动伸缩等。
- 流量控制。通过
Service Mesh
,可以为服务提供智能路由(蓝绿部署、金丝雀发布、A/B test)、超时重试、熔断、故障注入、流量镜像等各种控制能力。而以上这些往往是传统微服务框架不具备,但是对系统来说至关重要的功能。例如,服务网格承载了微服务之间的通信流量,因此可以在网格中通过规则进行故障注入,模拟部分微服务出现故障的情况,对整个应用的健壮性进行测试。由于服务网格的设计目的是有效地将来源请求调用连接到其最优目标服务实例,所以这些流量控制特性是“面向目的地的”。这正是服务网格流量控制能力的一大特点。 - 安全。在某种程度上,单体架构应用受其单地址空间的保护。然而,一旦单体架构应用被分解为多个微服务,网络就会成为一个重要的攻击面。更多的服务意味着更多的网络流量,这对黑客来说意味着更多的机会来攻击信息流。而服务网格恰恰提供了保护网络调用的能力和基础设施。服务网格的安全相关的好处主要体现在以下三个核心领域:服务的认证、服务间通讯的加密、安全相关策略的强制执行。
服务网格带来了巨大变革并且拥有其强大的技术优势,被称为第二代“微服务架构”。然而就像之前说的软件开发没有银弹,传统微服务架构有许多痛点,而服务网格也不例外,也有它的局限性。
- 增加了复杂度。服务网格将
Sidecar
代理和其它组件引入到已经很复杂的分布式环境中,会极大地增加整体链路和操作运维的复杂性。 - 运维人员需要更专业。在容器编排器(如 Kubernetes)上添加
Istio
之类的服务网格,通常需要运维人员成为这两种技术的专家,以便充分使用二者的功能以及定位环境中遇到的问题。 - 延迟。从链路层面来讲,服务网格是一种侵入性的、复杂的技术,可以为系统调用增加显著的延迟。这个延迟是毫秒级别的,但是在特殊业务场景下,这个延迟可能也是难以容忍的。
- 平台的适配。服务网格的侵入性迫使开发人员和运维人员适应高度自治的平台并遵守平台的规则。
展望未来
展望未来,Kubernetes 正在爆炸式发展,它已经成为企业绿地应用的容器编排的首选。如果说 Kubernetes 已经彻底赢得了市场,并且基于 Kubernetes 的应用程序的规模和复杂性持续增加,那么就会有一个临界点,而服务网格则将是有效管理这些应用程序所必需的。随着服务网格技术的持续发展,其实现产品(如 Istio
)的架构与功能的不断优化,服务网格将完全取代传统微服务架构,成为大小企业微服务化和上云改造的首选架构。