Service Mesh 及其主流开源实现解析

什么是 Service mesh

Service Mesh 直译过来是 服务网格,目的是解决系统架构微服务化后的服务间通信和治理问题。服务网格由 sidecar 节点组成。在介绍 service mesh 之前,我们先来看一下什么是 sidecar.

Sidecar 在软件系统架构中特指边车模式。这个模式的灵感来源于我们生活中的边三轮:即在两轮摩托车的旁边添加一个边车的方式扩展现有的服务和功能。在绝地求生吃鸡游戏中,摩托车是无敌的,应该也与这个模式有关吧? 这个模式的精髓在于实现了数据面(业务逻辑)控制面的解耦:原来两轮摩托车的驾驶者集中注意力跑赛道,边车上的领航员专注周围信息和地图,专注导航。

具体到微服务架构中,即给每一个微服务实例(也可以是每个宿主机host)同步部署一个 sidecar proxy:

该 sidecar proxy 负责接管对应服务的入流量和出流量。并将微服务架构中以前有公共库、framework实现的熔断、限流、降级、服务发现、调用链分布式跟踪以及立体监控等功能从服务中抽离到该 proxy 中:

当该 sidecar 在微服务中大量部署时,这些 sidecar 节点自然就形成了一个网格:

这就是我们说的 service mesh 了。对 service mesh 有了一个感性认识后,我们看一下 Linkerd 和 Conduit 的作者 William Morgan 在What’s a service mesh? And why do I need one? 中是如何诠释什么是 Service Mesh:

A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.

Service Mesh 这个服务网络专注于处理服务和服务间的通讯。其主要负责构造一个稳定可靠的服务通讯的基础设施,并让整个架构更为的先进和 Cloud Native。在工程中,Service Mesh 基本来说是一组轻量级的与应用逻辑服务部署在一起的服务代理,并且对于应用服务是透明的。

Service Mesh的特点

  • 是一个基础设施
  • 轻量级网络代理,应用程序间通讯的中间层
  • 应用程序无感知,对应用程序透明无侵入
  • 解耦应用程序的重试/超时、监控、追踪和服务发现等控制层面的东西

Service Mesh 有哪些开源实现

Service Mesh 的概念从2016年提出至今,已经发展到了第二代。

第一代 service mesh 以 LinkerdEnvoy 为代表。

Linkerd 使用Scala编写,是业界第一个开源的service mesh方案。作者 William Morgan 是 service mesh 的布道师和践行者。Envoy 基于C++ 11编写,无论是理论上还是实际上,后者性能都比 Linkderd 更好。这两个开源实现都是以 sidecar 为核心,绝大部分关注点都是如何做好proxy,并完成一些通用控制面的功能。 但是,当你在容器中大量部署 sidecar 以后,如何管理和控制这些 sidecar 本身就是一个不小的挑战。于是,第二代 Service Mesh 应运而生。

第二代service mesh主要改进集中在更加强大的控制面功能(与之对应的 sidecar proxy 被称之为数据面),典型代表有 IstioConduit

Istio 解析

Istio 是 Google 和 IBM 两位巨人联合 Lyft 的合作开源项目。是当前最主流的service mesh方案,也是事实上的第二代 service mesh 标准。

Google 和 IBM 之所以要带上小弟 Lyft 一起玩耍是因为他们不想从头开始做数据面的组件,于是在 Istio 中,直接把 Lyft 家的 Envoy 拿来做 sidecar. 除了sidecar, Istio中的控制面组件都是使用Go编写。Istio架构如下图所示:

对于一个仅提供服务与服务之间连接功能的基础设施来说,Istio的架构算不上简单。但是架构中的各个组件的理念的确非常先进和超前。

  • Envoy: 扮演sidecar的功能,协调服务网格中所有服务的出入站流量,并提供服务发现、负载均衡、限流熔断等能力,还可以收集大量与流量相关的性能指标。
  • Pilot: 负责部署在service mesh中的Envoy实例的生命周期管理。本质上是负责流量管理和控制,是将流量和基础设施扩展解耦,这是Istio的核心。感性上,可以把Pilot看做是管理sidecar的sidecar, 但是这个特殊的sidacar并不承载任何业务流量。Pilot让运维人员通过Pilot指定它们希望流量遵循什么规则,而不是哪些特定的pod/VM应该接收流量。有了 Pilot 这个组件,我们可以非常容易的实现 A/B 测试和金丝雀Canary测试:

  • Mixer: Mixer在应用程序代码和基础架构后端之间提供通用中介层。它的设计将策略决策移出应用层,用运维人员能够控制的配置取而代之。应用程序代码不再将应用程序代码与特定后端集成在一起,而是与Mixer进行相当简单的集成,然后Mixer负责与后端系统连接。也就是说,Mixer可以认为是其他后端基础设施(如数据库、监控、日志、配额等)的sidecar proxy:

  • Istio-Auth: 提供强大的服务间认证和终端用户认证,使用交互TLS,内置身份和证书管理。可以升级服务网格中的未加密流量,并为运维人员提供基于服务身份而不是网络控制来执行策略的能力。Istio的未来版本将增加细粒度的访问控制和审计,以使用各种访问控制机制(包括基于属性和角色的访问控制以及授权钩子)来控制和监视访问您的服务,API或资源的人员。

Istio 的很多设计理念的确非常吸引人,又有 Google 和 IBM 两个巨人加持,理论上这条赛道上的其他选手都可以直接退赛回家了。但是 Istio 发布的前几个版本都在可用性和易用性上都差强人意。此外,service mesh 布道师、 Linkerd 作者 William Morgan 也心有不甘。因此, William Morgan一方面在2017年7月11日,Linkerd 发布版本 1.1.1,宣布和 Istio 项目集成,一方面夜以继日的开发Conduit.

Conduit 解析

Conduit 各方面的设计理念与 Istio 非常类似。但是作者抛弃了 Linkerd, 使用Rust重新编写了sidecar, 叫做 Conduit Data Plane, 控制面则由Go编写的 Conduit Control Plane接管:

从Conduit的架构看,作者号称Conduit吸取了很多 Linkerd 的 Scala 的教训,比 Linkerd 更快,还轻,更简单,控制面功能更强可信度还是挺高的。与Istio比较,个人其实更喜欢Conduit的架构,一方面是它足够简单,另一方面对于要解决的问题足够聚焦。

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领域应该会有一席之地。

反思

对于大规模部署微服务(微服务数>1000)、内部服务异构程度高(交互协议/开发语言类型>5)的场景,使用service mesh是合适的。但是,可能大部分开发者面临的微服务和内部架构异构复杂度是没有这么高的。在这种情况下,使用service mesh就是一个case by case的问题了。

理论上,service mesh 实现了业务逻辑和控制的解耦。但是这并不是免费的。由于网络中多了一跳,增加了性能和延迟的开销。另一方面,由于每个服务都需要sidecar, 这会给本来就复杂的分布式系统更加复杂,尤其是在实施初期,运维对service mesh本身把控能力不足的情况下,往往会使整个系统更加难以管理。

本质上,service mesh 就是一个成规模的sidecar proxy集群。那么如果我们想渐进的改善我们的微服务架构的话,其实有针对性的部署配置gateway就可以了。该gateway的粒度可粗可细,粗可到整个api总入口,细可到每个服务实例。并且 Gateway 只负责进入的请求,不像 Sidecar 还需要负责对外的请求。因为 Gateway 可以把一组服务给聚合起来,所以服务对外的请求可以交给对方服务的 Gateway。于是,我们只需要用一个只负责进入请求的 Gateway 来简化需要同时负责进出请求的 Sidecar 的复杂度。

小结:service mesh不是银弹。对于大规模部署、异构复杂的微服务架构是不错的方案。对于中小规模的微服务架构,不妨尝试一下更简单可控的gateway, 在确定gateway已经无法解决当前问题后,再尝试渐进的完全service mesh化。

扩展阅读

Dropbox Bandaid 微服务反向代理/Sevice Mesh 代理解析

随着微服务架构以及的广泛普及,很多公司都会使用或者自行开发自己的API Gateway, 甚至在内部服务也会应用Service Mesh.

不久前,看到了一篇Dropbox公司介绍其内部服务代理Bandaid的文章: Meet Bandaid, the Dropbox service proxy. 不得不说设计细节一贯独角兽风格,非常有收获,对于改进我们自己设计的proxy 也有一定参考意义。这里做一个简单的读后笔记。英语好的同学可以直接阅读原文。(插一句,Dropbox不久前在美股上市了,在中美贸易战中为数不多逆势上涨的股票之一,玩美股的同学可以关注一下。再插一句:股市有风险,投资须谨慎。)

Bandaid 诞生的背景

Bandaid 是有公司内部的反向代理服务演变而来,使用Golang实现。反向代理有很多成熟的解决方案,之所以选择自行开发主要有以下几个原因:

  • 更好的与与内部的基础设施集成
  • 可以复用公司内部基础库(更好的与内部代码集成)
  • 减少对外部的依赖,团队可以灵活的按需开发
  • 更适合公司内某些特殊使用场景

上面的大部分因素根我们进行微服务组件开发时候的考量基本一致。这也是我们当初没有使用Go kit 这种工具套装进行架构微服务改造的顾虑,当然,那个时候还没有这些工具链。

Bandaid 的特性

  • 支持多种负载均衡策略 (round-robin, least N random choices, absolute least connection, pinning peer)
  • 支持 https to http
  • 上下游支持 http2
  • 路由改写
  • 缓存请求与响应
  • host级别的逻辑隔离
  • 配置热加载
  • 服务发现
  • 路由信息统计
  • 支持gRPC代理
  • 支持HTTP/gRPC健康检查
  • 流量支持按权重分配和金丝雀测试

丰富的负载均衡策略,以及对HTTP/2和gRPC的支持实例亮点。要知道,nginx 从 1.13.10才开始支持gRPC.

另一方面,从支持的特性看,如果要求不是特别多,直接用来作为Service Mesh也是相当不错的。而且由于是使用Go开发,对于本来就在使用Go作为技术栈的团队来说,无论是使用还是二次开发,门槛和学习成本都是很低的。

Bandaid 设计解析

整体架构

请求队列设计

接收的请求按照LIFO 后入先出的方式进行处理。这个设计有点反直觉,但却是合理的:

  • 绝大部分情况下,队列应该是空或者接近空的状态。因此LIFO的策略并不会恶化队列最大等待时间。
  • 根据业务类型,可以配置队列的优先级和长度。可以非常方便的实现服务限流、服务降级和熔断器。
  • Bandaid 采用总是接收TCP连接,并将连接交由用户态管理的策略。结合LIFO有一个很大的好处:
    • 与内核态管理连接比较,如果客户端发送请求后意外关闭了TCP连接,Bandaid 是无法马上获取到该错误的,需要等到读取完该请求,然后处理请求后开始写response时才会触发错误,发现这个连接其实已经关闭。因此,处理这类请求是在无谓的消耗服务器资源。而采用LIFO和用户态管理连接的话,Bandaid 可以根据配置的超时策略,一定程度上drop掉这类请求,减少处理这种「dead request」的数量。

Worker 设计

Worker采取固定大小的工作池设计,一方面可以精确的控制并发数量,另一方面,也避免频繁创建worker的开销。不过文中也承认,池的大小在设置的时候需要结合业务考虑清楚,否则可能不能充分利用服务器资源,错误的触发服务降级。

worker在处理队列中的请求时,支持按照优先和权重处理。因此,可以非常容易的实现金丝雀发布和逻辑上的upstream隔离。

负载均衡策略

  • RR, 均匀撒胡椒面。优点是足够简单,缺点是没有考虑不同后端服务实例的数量和接口处理时长差异,会导致大量Bandaid 服务资源被极少数的慢服务和接口消耗掉。
  • least N random choices: 先随机选择N个候选upstream host, 然后选择连接数最少的host (认为是当前负载最低的)作为最终目标host.

这种方式在在大部分时候可以工作得很好,但对于那种快速失败的小服务会失效。因为这种服务有很大概率被选中,但是并不意味着其当前负载较低。缓解该策略的方式是absolute least connection.

  • absolute least connection: 从全局host中选择连接数最少的host作为目标host.

  • pinning peer: 将worker与host绑定。这种方式可以避免慢服务过量消耗Bandaid资源的问题,但是请求调度不够灵活。

总结

从 Bandaid 的设计看,无论是作为 reverse proxy 还是 service mesh 都有不错的潜力。不过 Dropbox 团队当前还没有公开Bandaid性能测试数据,代码也还没有开源。因此,猴急的同学可能还需要等一段时间。

微服务架构下的立体监控系统设计和实现

背景

GOPS全球运维大会(北京站)听到了不少干货。特别受益的是来自腾讯SNG事业部聂鑫分享的
《从0到1到N,腾讯监控体系全透视》

在他的主题分享中,他将腾讯这些年的监控系统的发展历程概括为点监控-->面监控-->深度监控

看到他这页幻灯片的时候,有一种醍醐灌顶的感觉。因为在听他分享的时候,我们的系统才刚刚完成架构微服务化没多久,我们上线了调用链:分布式追踪系统来解决在微服务分布式系统中排查跟踪特定问题,但我们的监控系统还没有针对架构微服务化后进行相应的进化。比如,大部分监控系统停留在点监控的层面,少数进行关联多个服务的面监控也做得比较初级,需要人工分析和干预。

点监控比较好理解,就是对系统布置监控点,根据阈值触发告警。

面监控则是对告警信息进行时间和空间关联,有效消除毛刺告警,使告警更加准确。因为告警本身有时效性,时效性源于告警延时,连续性可能是干扰,因此只进行时间关联是不够的。链路相关性(空间相关性)和时间相关性一起决定准确性。

深度监控其实有点追深度学习的热点,从分享看,实际就是对面监控的链路相关性进一步完善,以及根据收集到的系统进行使用机器学习进行简单的分类。

参加会议回来以后,我们明确了自己监控系统的进化方向,根据自身系统的特点进行了一些取舍,确定了立体监控的方案。

立体监控方案目标

所谓「立体监控」即指在我们当前系统点监控为主的情况下,尽可能复用当前监控的探针,进行时间和空间(服务链路之间)维度上的扩展,实现对整个系统时空上的监控。

立体监控需要消除点监控带来的监控毛刺,如服务存在依赖情况下,级联告警通过立体监控分析融合后,应该只对最后一级进行告警。

立体监控可以快速的定位系统故障,定位粒度根据不同监控类型可以做到微服务级别、接口级别、数据库实例级别、缓存实例级别等。

立体监控方案设计和实现

对于微服务,我们通过data bus将需要进行监控的信息发送到kafka进行收集。这种方式在调用链分布式追踪系统也有使用。不得不说,data bus是架构微服务化后非常重要和实用的基础组件。

为了尽可能降低各个微服务集成监控组件的侵入性,我们通过修改基础库的方式进行集成。比如,微服务使用了MySQL数据库,那么我们就修改微服务使用的数据库驱动来对数据库进行监控,一旦发生错误或warning信息,将消息写入data bus;某个微服务需要调用腾讯的某个接口,我们就对修改我们的http客户端基础库,将错误和超时消息写入data bus实现对该接口的监控。

因此,我们的微服务监控集成几乎不需要研发人员的介入,只需要运维人员更新服务依赖库,然后重新发布上线即可。事实上,我们在微服务监控集成上就没有安排独立的发布上线时间,都是在研发上线feature或hotfix时搭车上线的,将近两百个微服务在2周内完成集成。需要注意的是,基础库的修改一定是由团队中相对资深的开发人员来做,并且测试一定要做到位,否则会引起大规模的问题。

对于k8s集群、数据库、redis等基础设施的监控沿用以前的点监控数据,只是将事件统一上报到了Event Collector事件收集服务。Event Collector除了收集事件数据,也对一些不符合通用规则的数据进行过滤。

Event Analyzer事件分析服务根据Event Collector收集到的事件,进行时间和空间上的分析监控结果,并发送告警通知。

在事件分析上,时间维度非常简单,选取一个时间窗口内的事件信息即可(我们当前根据经验设定的是1min)。空间维度方面,则相对麻烦一下。我们没有采用腾讯使用的全链路分析算法。主要是因为该算法需要预生成链路拓扑图。而微服务架构中,各个微服务的增加、减少和变更是非常频繁的,预生成拓扑图有点反模式,也会产生一定的成本。我推测腾讯之所以觉得预生成拓扑图不是问题,跟它当前架构没有完全微服务化以及内部严格的管理流程有关。

因为不想预生成链路拓扑图,分享中给出的链路面积计算公式也就无法使用。另一方面,因为腾讯给出的全链路分析算法其实没有完备的理论证明,在我们数据量没有腾讯庞大的情况下,我们是否能用该算法取得同样效果是缺乏信心的。

但是,思想是可以借鉴的。通过讨论,我们一致认为所谓的链路分析其实就是关联性分析,而关联性分析那就毫不犹豫的使用Google Page Rank算法:

如上图所示,我们将服务(1, 2, 3, 4)之间的告警事件作为一个超级链接指向,然后计算PR值,那么,PR值最大的是我们认为出现问题可能性最大的服务。理论证明这里略过,这个锅我们扔给Google背即可。

需要说明的是,任何一个复杂的系统在出现故障时,往往不是一个组件或服务出现问题,很可能是多个服务同时出现问题。那么在计算PR时,可能就是面对多个独立的PR有向图。这个时候独立图之间的关系处理就可以根据历史数据进行机器学习,以进一步给出故障原因,然后根据预案快速处理故障,恢复服务。

Event Analyzer也提供了一个页面,可以查看历史的告警信息链路:

也可以对链路流量(边越粗流量越大)进行监控和分析:

总结

立体监控上线后,运维方面以前只能从点入手排查问题转变为直接根据Event Analyzer的聚合告警信息联系对应服务的开发者解决问题。同时,发现了很多以前被经验标记为系统抖动没有重视的潜在问题。至此,我们的微服务架构在调用链和立体监控的双重加持下,又完成了一次进化。