蚂蚁金服Service Mesh新型网络代理的思考与实践

本文根据晓东在 GIAC 上海站的演讲内容整理,完整的分享 PPT 获取方式见文章底部。

作者 朵晓东 发表于 2018年12月4日

朵晓东,花名奕杉,蚂蚁金服高级技术专家。专注企业云计算技术及产品,蚂蚁金融云 PaaS 创始团队核心成员,Antstack 网络产品负责人。开源爱好者,Apache Kylin 创始团队核心成员;SOFAMesh 创始团队核心成员,SOFAMosn 项目负责人。

本文根据晓东在 GIAC 上海站的演讲内容整理,完整的分享 PPT 获取方式见文章底部。

image.png | left | 720x481.1881188118812

大家好,我是蚂蚁金服系统部的高级技术专家奕杉,今天分享的内容是:《蚂蚁金服在 ServiceMesh 推进落地过程中对新型网络代理的思考和实践》

内容结构:

主要的分享顺序:

  • 背景概述
  • 架构设计及功能特性
  • 技术案例
  • 总结展望

1、背景、概览:

image | left

ServiceMesh 作为云原生之上的服务网格技术在今年引起了业界的广泛关注,首先我们来看一下目前 ServiceMesh 数据平面的一些方案。

最为大家熟知的是老牌七层代理 Nginx 和 ISTIO 原生的数据平面 Envoy。Nginx 早已在国内外广泛使用,近两年积极探索 K8S、ServiceMesh 微服务场景,并推出了与 ISTIO 集成的微服务解决方案,试图扩展其场景边界,拿下新的领域,从单纯的7层流量代理到云原生时代的智能数据平面转型。但目前看 “NgMesh”研发不够活跃,已知的使用方也不多。Envoy 作为 Google 和 Lyft联合开发的 ISTIO 原生数据平面产品,近两年借助 ServiceMesh 微服务场景快速打开了市场,并在一些互联网公司推广使用,同时引入了一批开发者进行 API 网关等功能网关的开发,发展势头非常好。

其次 LINKERD 是基于 Rust 的一种高性能数据平面,但其发展空间受到了 Envoy 挤压,业界使用的公司也比较有限。

蚂蚁金服基于自身诉求自研了基于 Golang 的数据平面 SOFAMosn(后简称MOSN),并在蚂蚁、UC 等公司落地使用。

同时对业界开源,提供了一种新的数据平面产品选择。

此外国内的华为、新浪等公司都基于自身场景提出了数据平面方案并先后进行了开源,数据平面竞争已经从独霸业界的基于 Nginx 二开方案逐步转变为目前的多样化产品同场竞技的局面。

image | left

为什么众多大厂纷纷投入研发数据平面呢?

我个人认为新生技术栈、云原生、微服务快速发展等契机对数据平面提出了场景多样化、功能服务化、云原生亲和等多重挑战。

以往从未像现在这样对数据平面提出过如此多的要求:

  • 数据平面需要执行部署运维中的流量切换;
  • 需要提供云亲和的细粒度流量调度功能;
  • 需要提供微服务亲和的服务发现、路由组网特性;
  • 需要以云原生的方式感知资源;
  • 需要支撑服务粒度、高度自定义的压测、故障测试、线上灰度流量管理;
  • 需要提供链路级、服务级的安全隔离保护,需要支持多种语言、多种协议的转换分发能力;
  • 需要能享受系统层面、硬件层面的红利;
  • 需要为复杂的运维架构(如蚂蚁的 LDC 等)提供可扩展的流量调拨能力等等;
  • 当然根据每个公司的业务场景可能还有其他的因素。 最后,如何要将这些能力都汇聚在统一的数据平面产品上,弥合南北向、东西向数据平面由于技术栈、团队等差异带来的鸿沟,变成了另一个更为复杂的问题。这里所提到的问题中任何一点扩展开来都可以是一个丰富独立的 Topic,受限于篇幅本次分享只能介绍我们在解决这些问题中的一小部分思考和实践。

2、SOFAMesh 架构 & 重点特性

image | left

首先,蚂蚁已经将基于 ISTIO 的 ServiceMesh 方案 “SOFAMesh” 开源,在控制面我们选择克隆 ISTIO 官方版本并研发符合蚂蚁需求的控制面,在数据面我们选择使用 Golang 研发数据平面 MOSN,目前已经支持了微服务场景所需的大量常用功能。

这里我根据 ISTIO 的 Task 文档总结了目前 SOFAMesh 支持的一些能力,如:透明拦截适配,细粒度的流控,故障注入,双向链路加密等。对于一些暂时存疑的功能,如 Mixer Check 等,暂时没有支持。目前 SOFAMesh 已在 UC 生产环境落地使用,满足了 Sidecar、Ingress、Egress 多种场景的使用需求。在这里附上 SOFAMesh,SOFAMosn 的 Github 地址,也欢迎大家使用交流。 SOFAMesh:https://github.com/alipay/sofa-mesh SOFAMosn:https://github.com/alipay/sofa-mosn

image | left

再来看看蚂蚁内部,由于目前蚂蚁生产环境尚未大量铺开K8S,并且已经存在一套完善的管控技术体系,加上目前ISTIO 的性能和稳定性还不满足大规模微服务场景等原因,我们暂时没有选择直接升级到 ISTIO,而是通过优先落地Sidecar 的方式来赢得 ServiceMesh 解决方案带来的红利。在蚂蚁内部,MOSN 接管了SOFABoot 应用,代理了服务发现、路由/负载均衡、通信等工作,构成了微服务网格,通过自有的中间件及管控平面进行微服务的管理、治理。同时,我们积极的推进 MOSN 与 SOFA中间件,网络接入层,安全防护及监控体系的整合,以提供更统一更强大的数据平面。

image | left

接下来我将介绍 MOSN 支持多协议的方案。

为了在内部快速落地试错,我们首先支持了内部使用最广泛的 SOFARPC 协议,并对其进行了深度优化。随后我们根据 UC Mesh 化推进遇到的普遍问题提出了 XProtocol 方案,以在不解包的场景下提供路由能力。最后我们深度改造了三方 HTTP/1.1 实现及官方 HTTP/2.0 实现。到目前为止,MOSN 已提供了多种协议的支持。同时 MOSN 提供了两种自定义协议的能力支持使用者通过扩展的方式自定义协议实现,满足需要解包、不需要解包的协议扩展需求。

image | left

除协议之外,性能是大家比较关心的另一个问题。为了提供满足生产要求的7层转发性能,我们在 IO、协议、内存、协程、网络处理等方面进行了优化,从目前通过 SOFARPC 通信应用的上线情况来看可以满足生产使用要求,在案例分析中我将展示一些性能数据,后续我们也将继续推进性能优化,以达到更好的性能。

image | left

在安全能力上,SOFAMesh 支持 mTLS,并在蚂蚁内部集成蚂蚁内部的 KMS 完成了 mTLS 落地,同时 RBAC 功能在研发中,此外WAF、流量镜像能功能也在规划中。

image | left

在蚂蚁内部基于 MOSN 的网关产品正在研发中,将会在稳定验证后开源。网关场景相对于 Sidecar 场景有一些特性需求,比如说一般会 Hold 住大量长链接,比如说会根据请求内容动态选择后端应用,由于网关可能代理了不同的后端应用,就会需要动态选择后端协议。此外还有一些网关类的通用能力需求,如签名,授权,限流等。

image | left

为了能基于开源版建设蚂蚁内部的 Sidecar 及网关产品,我们充分考虑了开源版 MOSN 的扩展性,在路由、后端管理、TLS、网络、流处理等各方面提供了扩展性支持。对于其他使用 MOSN 的场景,也可以通过类似的方式来满足自身业务定制需求。

image | left

image | left

image | left

image | left

image | left

为了更清晰的展示 MOSN 功能特性,这里将 MOSN 0.4.0 的功能特性通过表格的方式展示出来。可以说0.4.0版本已经初步具备了生产所需的大部分功能点,支持云原生场景下的多协议、路由&LB、后端管理、TLS、遥感监测、XDS对接等功能,并充分优化了性能,目前已经在蚂蚁、UC生产环境进行了验证。同时在蚂蚁内部我们通过扩展的方式支持了灰度路由、 LDC 路由、弹性路由,支持了配置中心等定制需求。后续我们会继续完善功能点,如果有发现未支持的功能可以在 Github 给我们提 Issue,或者直接加入我们 commit code。由于MOSN在扩展性上提供了比较好的能力支持,在特定的场景都可以通过扩展的方式来满足需求。

3、技术案例解析

在介绍了架构、功能特性以后,我将介绍一些落地过程中的技术案例。

image | left

首先我们来看在蚂蚁在非 K8S 场景下将 MOSN 作为 Sidecar 接入应用的姿势。 在我们推进落地初期,需要接入MOSN 的 SOA 应用还没有通过原生 K8S 的方式运行起来,服务发现是基于典型的服务发现中间件来做,也没有直接使用 ISTIO 来落地。在这种情况下,我们通过扩展开源版 MOSN 支持服务发现,将 MOSN 作为服务代理,由其完成服务 Pub/Sub,并代理服务完成 RPC 通信。这里有几个要点,首先由于内核限制我们没有第一时间使用Iptables 拦截请求,而是通过升级 SOFA 的方式来支持应用切换访问地址,这里需要应用方升级 SOFA 依赖,但不需要改业务代码。

其次,我们通过扩展开源版服务发现实现的方式支持从蚂蚁配置中心获取后端服务列表。在路由生成上,MOSN 基于完善的服务间依赖关系生成服务路由,由于 SOFA 应用在编码阶段已明确定义了服务依赖并在服务启动时由 MOSN 代理完成 Pub/Sub,MOSN 感知所需的服务依赖关系,并动态生成了出向/入向路由。如果你的使用场景服务之间没有明确的依赖关系,则需要扩展路由机制支持基于完全请求内容的动态路由机制。其次,SOFA 服务寻址基于明确的 id:version信息,所以这样的服务发现、路由方案同时也适用于类似的 SOA 服务,同时也可以支持标准微服务寻址。这样的扩展方式可以实现基于服务发现中间件的非 K8S 的 Sidecar 注入,来享受 Mesh 思路落地的便利。

image | left

除了通过以上方式支持 SOA 化服务外,__SOFAMesh 标准方案提供了一种基于 DNS 的寻址方案,__以在不修改应用的情况下支持标准微服务寻址。 首先通过 DNS 将服务名转化为 IP,同时在 MOSN 路由的 Virtual Host 中配置服务IP,以及与后端的匹配关系。Client 请求被 Iptables 拦截并转发给 MOSN,MOSN 在处理请求时通过 Tcp Option 的 Original Destination 项拿到目标 IP,并作为 Host 与 Virtual Host 匹配寻址到 Cluster 并做后端 LB及转发。这样在不进行微服务化改造的情况下就可以进行服务名完成寻址通信了。这里更多细节可以参考敖小剑老师写的XProtocol完整方案

image | left

接下来我们一起分析另一个在生产环境非常有用的特性,无损平滑升级。 大部分7层流量代理,包括 Nginx、Envoy等,在升级过程中通常会让老进程静默等待一段时间后再退出、或等待时机由新进程控制退出,通过这种方式来保证老进程不再处理到请求。这种方式对于 HTTP/1.x 短链接是比较有用的,但对于 HTTP/1.1长链接、RPC 长链接最终不得不通过暴力断链的方式让 Client 重连、重试,对业务是有一定影响的。凡会造成业务抖动一般都会造成业务方紧张,这会导致新功能升级推动困难。针对这个问题,MOSN 提出了自己的解决方案,在升级过程中无损迁移存量链接,目前支持 HTTP/1.1、无状态 RPC、TLS,后续将基于 Goaway 帧支持 HTTP/2.0。下面我们来看看MOSN 是怎么实现这样能力的。

image | left

这里我把典型场景抽象成 Client 请求处理和 Server 回复处理两部分,我们先来看看 Client 请求处理。在升级阶段,同时存在新老两个 MOSN 进程,此时可能存在 Client 正在访问老进程的情况,此时老进程会通过 Domian Socket 将 TCP1 的 FD 及链接状态数据传递给新进程,由新进程创建 TCP3 链接并将数据发送到后端 Server 并接收 Server 响应,新进程在收到响应后不再转发给老进程,直接转发给 Client 完成本次请求。此后老进程退出 Read流程, 不再接受该 TCP1 连接上的数据,同时新进程开始 Read 流程,接受该 TCP1 连接上的数据,完成 Reload 过程。

image | left

再来看看第二种情况,在第一步链接迁移完成后仍然可能出现 Server 通过TCP2 将残留响应发送到老进程的情况,此时老进程会通过 Domian Socket 将请求数据传递给新进程,由新进程回复到 Client。这样就避免了两个 MOSN进程同时写到 Client 造成乱序的问题。延伸一下,未来基于这样的思考可以与容器 fork 结合提供容器、Pod 层面的无损迁移方案。

image | left

在迁移过程中我们发现,对于无状态的 HTTP/1.1 长链接,RPC 长链接迁移较为简单,但对于有多回合握手的 TLS迁移则比较麻烦,这里主要涉及到 TLS 的状态数据迁移,如加密秘钥,Seq 序列,读缓存数据,Cipher 类型等状态数据都需要做特殊的处理以保证迁移过程不会破坏握手过程。此外,MOSN 还支持对请求链接做明、密文检测,来保证上游可以灰度的开启链路加密。

image | left

最后我将介绍一下我们在性能优化方面的一些实践。数据来源于 2018年8月份的 0.2.1 开源版。 先介绍一下在 Sidecar 模式下的性能数据,测试场景是一个典型的服务间通信场景,服务A 通过 MOSN 访问服务B。这里选用的机型是蚂蚁内部的测试机器。我们测试的场景包括了 SOFARPC、HTTP/1.1、HTTP/2.0三种协议,测试的工具分别是蚂蚁内部的压测平台、ab、h2load,其中 HTTP/2.0 压测 5 条链接的场景,并且是 H2C,无 TLS 加密。数据场景是 1K 的请求、响应。

image | left

我们来看一下测试结果,可以看到 SOFARPC 性能远好于其他两个协议。需要说明的是,HTTP/1.1 在 0.2.1 版本中直接使用了开源的 FastHTTP,MOSN 没有接管 IO 处理,协议解析等工作,未做任何优化;HTTP/2.0 直接使用了官方实现,MOSN 没有接管 IO 处理,协议解析等工作,未做任何优化。

image | left

在 SOFARPC 的优化上我们从 IO,协议到上层处理做了不少优化,这里简单介绍一些优化经验。首先分享一个踩过的坑,在基于 Golang Connection API 编写读数据代码时,一个常用的方式是通过 SetReadDeadline 来设置读超时,我们发现在读超时很短的情况下,在2.6.2 内核会比 4.13.0 内核性能下降 30%,而通过绑核可以解决此问题。此外,有很多有用的手段可以用来优化性能,比如说读合并减少协议处理次数,writev 减少系统调用写的次数可以有效提升整体吞吐量。我们通过对比 Golang 和 OS 的 perf 数据发现 Golang 系统调用耗时比 OS 系统调用耗时要多(原因还需要进一步明确),减少 Golang 系统调度总是有效的优化手段。在内存优化方案,首先可以尽量减少内存入堆,对于 100K 以下的内存入栈比入堆更快,并且不会影响 GC,对于不得不入堆的内存,可以通过有效的内存回收复用减少内存创建,减少 GC 压力。其次,在可控的范围内池化协议可以减少 Golang runtime 调度,并减少为了 Golang 为了保证连续栈而调用 morestack 造成的开销。对于单核的场景,需要关注协程数量及协程使用率,避免协程饥饿的情况。对于 perf 发现的热点,需要有针对性的进行优化。

image | left

接下来看看网关的场景,Client 通过 MOSN 访问 Server,测试条件与单核类似,不过没有限制 MOSN 的 P,也没有绑核。

image | left

从实验结果上看,SOFARPC,HTTP/1.1 的结果基本在预期内,但 HTTP/2.0 性能远低于预期。

image | left

我们看到 Golang 官方的 HTTP/2.0 实现在多核场景下性能不佳,在 0.4.0 中对官方 HTTP/2.0 进行了性能优化,我们将在 0.4.0 正式发布后更新性能数据。在多核场景下我们仍然选择了单进程模型,根据连接数、负载等变化可以压到 4-8 核。我们也尝试了多进程绑核+reuse port 的方案,多核吞吐量高于单进程 15% 以上,但从容器的适配性,进程模型简单等角度考虑我们仍然选择了单进程模型。在多核场景下需要特别关注全局锁的性能和 IO 的优化,这也是官方 Golang HTTP/2.0 实现性能不佳的重要原因。此外,需要在压测时关注 G 是否频繁切换,P 是否有饥饿等问题,有针对性的进行多核性能优化。

image | left

我们再来看看长链接的模式,在这种模式下会有大量链接,但不会出现同时有大量流量的情况。MOSN 针对这种场景提供了基于 NetPoll 的使用模式,我们重点压测了在 10K 链接场景下 SOFARPC 的性能。

image | left

从压测结果上可以看看到,基于 Raw Epoll 的 NetPoll 模式在资源消耗上明显少于原生 Golang IO 的模式。

image | left

从结果上看,原生 IO模式还无法高性能的满足 C10K 场景的要求,针对高性能网关场景还是需要通过更有针对性的方案来支持。

image | left

最后我将介绍 TLS 性能数据,这里通过 Nginx+OpenSSL,Caddy,Caddy+BoringSSL 三种实现来测试 SSL 处理的性能数据。

image | left

从测试结果可以看到,对于 RSA 加密,使用了 Golang 原生支持的 Caddy 性能明显弱与 OpenSSL 及Caddy+BoringSSL,但对于 ECDSA 来说 Caddy 弱于 OpenSSL,但明显略好于通过 cgo 调用 BoringSSL 的方式。

image | left

通过进一步分析发现,Golang 对 RSA 的实现是基于 Golang 的,但对 ECDSA 等现代加密算法有汇编优化,比如说对 p256 的一些重点实现方法是移植了 OpenSSL 实现。同时 Golang 对 AES-GCM,SHA,MD 等算法都有汇编优化。如果你需要使用的算法正好在 Golang 的优化范围内,那么完全可以直接使用 Golang 原生实现,可以省去对接 OpenSSL、BoringSSL 的麻烦。

image | left

最终总结一些性能优化的结果,到目前为止,在 SOFARPC 协议上对 0.1.0 版本 QPS 提升了 50%,内存使用减少了 40%;HTTP/2.0 经过一轮优化,QPS 提升了一倍,后续会继续推进优化;HTTP/1.1 也有 30% 以上的性能提升。

此外,Golang 性能优化与 C/C++ 还是有比较大的区别,在C/C++ 优化过程中,重点观察系统 perf 进行优化,但Golang 的话需要既需要了解、观察 Golang runtime 的 perf、调度数据,也需要观察 OS层面 的 perf 数据,并且需要进行结合分析。一些 C/C++ 常用的方式在 Golang 也无法直接使用,比如说在C中做无锁替换经常通过整块内存替换指针的方式来保证原子性,但在 Golang 里指针替换并不是原子的,如果分析汇编会发现实际上执行了多条汇编,在实现层面就需要一些特殊的处理。

由于 Golang 从编译到运行时,从运行单元到系统调用都是一个非常独立并且自包含的体系,他并没有基于 C 的开发套件建立,可以说是非常自成一体了,所以在系统层面的集成上仍然有不少问题需要克服,比如说 cgo 性能,但是由于他的优点也是非常明显的,可以在更多系统软件场景去探索挖掘,完善实现,建立出基于 Golang 世界的系统软件体系。

image | left

此外,在解决具体问题的过程中我们积累了不少有值得分享的案例,比如说在大流量场景下动态更新存量链接的配置风险策略,Metrics 平滑迁移,支持多层路由判断的可扩展链式路由等,受限于篇幅无法一一展开,后续我们将通过blog 或 meetup 的方式与大家分享。

4、总结 & 展望

image | left

最后我们再看看微服务场景下 SOFAMosn 落地的方案,首先 SOFAMosn 作为 Sidecar 与 SOFABoot APP 融合代理服务、通信、配置等功能,与蚂蚁的 Control Plane 通信完成服务配置更新。

image | left

最后我们再从整体视角看看 MOSN、MOSNG 在蚂蚁架构中的位置。MOSN 作为蚂蚁全新的数据平面,会贯穿网络接入、微服务、安全、Serverless 等场景的落地中。我们将推进东西向,南北向技术架构的融合,形成统一的负载网络。在安全方向上,我们将会在微服务级别的安全保障上做更细致的工作,从 2、3、4、7 层做更多的安全隔离工作,例如做到微服务粒度的流量拦截、牵引。在 Serverless 场景,MOSN 将作为 Serving 的前置提供服务。最后 MOSN 将积极地与用户态加速技术,7层流量拦截服务等基础能力集成,更好的服务于统一负载网络。 从蚂蚁技术栈演进的视角看,在下一代微服务架构、下一代接入网络、零可信微隔离的技术发展的萌芽之下,MOSN的出现是必然也是偶然,最终将成为新生技术体系落地过程的重点环节,我们将继续探索,逐步形成适合蚂蚁业务场景的数据平面。

image | left

我今天要介绍的内容就是这些,欢迎大家关注“金融级分布式架构”,“ServiceMesher” 公众号,我们将会有更多技术干货发布在公众号中。同时这里有 SOFAMesh、SOFAMosn 的 Github 地址,欢迎大家 star,或试用,更欢迎大家为我们提出宝贵意见。谢谢大家。 地址: SOFAMesh: https://github.com/alipay/sofa-mesh SOFAMosn: https://github.com/alipay/sofa-mosn

PPT 下载

地址: http://www.sofastack.tech/posts/2018-12-04-01

image.png | left | 720x481.1881188118812