构建基于Service Mesh 的云原生微服务框架

目前很多企业还是采用基于 SDK 的传统微服务框架进行服务治理,而随着 Service Mesh 的普及,越来越多的企业开始布局自己的 Service Mesh 框架体系,但多数企业刚开始不会激进地将所有业务迁移至 Serivice Mesh,像 Java 系应用依然保留原框架,而非 Java 系应用采用 Mesh 框架,不同开发语言可以用不同的技术框架,但业务不能被框架割裂,那在两种架构体系下应用服务如何互联互通?微服务如何统一治理?传统微服务又如何平滑迁移至 Service Mesh 呢?

腾讯为了解决以上的技术问题,自研了 TSF Mesh 微服务框架。也许有人会问,开源 Istio 已经是比较完善的 Service Mesh 方案了,为什么要再造一个 Mesh 微服务框架?和原生 Istio 什么区别呢?

QCon 大会讲师张培培来自腾讯云微服务产品团队,在这里分享 TSF Mesh 有哪些不同点?解决了什么样的问题?

云原生的概念在 2013 年被首次提及,在 2015 年又被 CNCF 重新定义,这两年之所以这么火,主要还是云原生的技术能够帮助技术团队构建一个容错性强、易于管理和便于观察的松耦合系统。Service Mesh 作为云原生 SaaS 层的技术代表,与传统基于 SDK 的微服务框架相比,在提供更加完善的服务治理能力的同时,其无侵入松耦合的 Sidecar 接入方式是很多企业开始将微服务架构迁移到 Service Mesh 的一个重要原因。

那如何将传统微服务框架迁移到 Service Mesh 呢?先来看下这样一个迁移场景,客户的业务架构是这样的,就是左图这个架构:

  1. 应用是部署在虚拟机或物理机上的
  2. 业务是基于 Spring Cloud 框架开发的
  3. 注册中心采用的是 Consul 或 Eureka

目前开源 Istio 已经成为 Service Mesh 事实上的标准,因此客户希望尝试迁移到 Istio 上,原生 Istio 是构建在 K8s 上的,因此客户要迁移到 Istio,首先需要容器化改造,再将框架迁移至 Service Mesh;而在 Mesh 化过程中,由于 Spring Cloud 的服务注册体系和 Istio 的服务注册体系不一样,也就是如果应用不全量迁移的话,新老业务就没法互相服务发现,业务间通信就被割裂了。但是对于体量稍大的业务,这几乎是不可能的事情;而且很多客户只想单纯的做框架迁移,并不想容器化改造;所以实际的迁移场景可能是这样的。

我们日常在跟一些客户交流时,他们在 Mesh 化改造中也确实有这样的诉求:

  1. 一些存量老业务运行在虚拟机或者物理机上,暂时没有容器化改造计划,但希望通过 Service Mesh 来做服务治理
  2. 新上的业务或者存量的非关键业务可以做为试点,先容器化、Mesh 化,其它业务依然采用原有的运行方式和微服务框架
  3. 对于未迁移的存量应用和迁移完成的 Mesh 应用依然能保持业务上的互通

因此,最终迁移的架构图可能就是这样的,左边两个绿色框是未迁移的存量应用,右上蓝色框是容器化 Mesh 化的试点应用,右下红色框依然运行在 CVM 上 Mesh 化的应用, 但无论如何改造,应用间依然是保持互通的;

而这些诉求却是原生 Istio 没有办法满足的,虽然社区一直在努力,Istio 2020 的目标也是为了商用,希望应用能够更方便、更简单的接入进来,特别是对虚拟机的支持,不过依然没有一个完整的方案,很多特性还是围绕 K8s 来实现的。

所以这正是我们构建 TSF Mesh 微服务框架的初衷:从业务的实际场景出发,帮助传统微服务框架如 Spring Cloud 平稳过渡至 Service Mesh 框架,并最终形成基于 Service Mesh 的全方位服务治理体系

我们在 2018 年底左右就发布了 TSF Mesh 的第一个版本,并以 TSF 腾讯微服务开发平台的产品形态投入商用,并兼容 Spring Cloud 的开发框架,目前有不少行业客户已经接入商用,特别是一些有历史包袱、不太方便做大规模改造的传统客户。

当时考虑到服务治理能力的完善度、技术栈以及开源社区影响力等因素,我们选择 Istio + Envoy 作为 Service Mesh 底座进行构建,而在实际实现中,我们同样面临三大挑战:

  1. 第一,运行平台的挑战,我们知道 Istio 是强依赖于 K8s 平台的,如应用生命周期管理、服务注册、健康检查、服务寻址、策略配置管理等等,那对于物理机或虚机平台这些能力如何支撑呢?
  2. 第二,微服务框架的挑战,Spring Cloud 和 Service Mesh 都各自完善的服务治理体系和实现机制,如何让两者的服务互相通信?服务注册发现、路由、限流、熔断等治理能力如何拉通并且统一治理呢?
  3. 第三,可观测性的挑战,Spring Cloud 和 Service Mesh 服务治理打通后,如何统一观测两个体系的服务?日志、监控、调用链如何打通?

下面将从 PaaS 平台解耦、注册中心、服务治理、数据运营 4 个切面出发,介绍我们是如何一步步实现 TSF Mesh 微服务框架的;最后,当完成了这样一个框架的构建,针对前面的实际迁移场景,我们又是如何做应用架构的迁移的。

PaaS 平台解耦:将框架与运行平台解耦,让微服务应用可同时运行在虚拟机和 K8s 上

首先,我们来看下如何将框架与运行平台解藕,让微服务应用可同时运行在虚拟机和 K8s 上。为什么要将框架与运行平台解藕呢?因为我们是基于原生 Istio 构建 Service Mesh 框架,而原生 Istio 又是和 K8s 强耦合的,首先就是部署和应用生命周期管理,如果不解藕,很难满足像虚拟机、物理机运行的应用场景。

在业内流行这样一句话:计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。

同样,我们引入 Resource Controller 资源管控模块这样一个中间层来解决容器和虚拟机统一管理的问题;对于应用容器部署,Resource Controller 适配标准的 K8s 的 API Server 接口,如图中左边部分,可以对接任何支持标准 K8s 接口的容器平台,如 TKE、灵雀云等,这里就不过多介绍了。

对于应用虚拟机部署,我们自研了一个 aPaaS 虚拟机应用管理平台,对比左边 K8s 架构,aPaaS 在实现上有很多类似的地方,这里主要引入了以下四个模块来解决程序包管理、应用部署以及弹性伸缩的问题:

  1. Repo Manager 模块,类似容器的镜像管理系统,负责应用程序包基于版本进行管理(如程序包上传、下载),并支持对接 Jenkins 等 CD 系统;
  2. TSF-master-api / TSF-master,类似 K8s 的 API Server 和 Kubernetes Master,TSF-master-api 是 aPaas task 任务接入层,并将应用部署、下线、启停等任务转发给 TSF Master,TSF Master 再将任务异步调度到具体的虚拟机上,并实时同步任务执行状态;
  3. TSF Controller,类似 K8s 的 HPA/ReplicationControlle 水平扩缩容控制器,TSF Controller 则负责虚拟机应用的水平扩缩容,根据虚机部署组中设置的 CPU 利用率、内存利用率等指标周期性探测部署资源的使用情况,如果匹配到设置的目标值,就通过调用 TSF Master 对虚拟机应用进行扩容或者缩容;
  4. tsf-agent,类似 K8s Node 节点上的 Kubelete 组件,负责初始化机器环境以及执行具体的任务,如在执行部署任务时,tsf-agent 根据 task 中的信息先从 Repo Manager 下载相应版本的程序,异步执行并负责应用进程的生命周期管理,通过类似 K8s liveness 的健康检查机制,探测应用的执行状态,如果运行异常则重新拉起应用进程,同时也会同步执行状态给 TSF Master;

这几个模块基本承载了 aPaaS 的核心功能,到这里我们搭建好了一个统一的部署和应用生命周期管理平台,那 Mesh 应用的部署有什么不同呢?

我们知道,要实现透明的 Mesh 服务治理,首先需要为应用额外部署一个 Sidecar(也就是 Sidecar 的注入),通过 Sidecar 来代理应用的进出流量、实现流量管理;

首先,我们回顾下在原生 Istio 下部署应用时,如何将 Sidecar 容器注入到应用 POD 中的,看下上面这个图,Istio 通常采用的是手工注入或自动注入的方式

  1. 手工注入:就是用户手工执行 istioctl kube-inject 来重新构造应用部署;
  2. 自动注入:通过 K8s 的 mutable webhook 回调 istio-sidecar-injector 服务来重新构造应用部署;

其实说白了,注入的本质就是是将运行 Sidecar 所需要的镜像地址、启动参数、所连接的 Istio 控制面信息(如 Pilot、Mixes、Citadel 地址)填充到注入模版,并添加到应用部署中,再通过 K8s 创建应用和 Sidecar 的 POD。

那流量劫持是原生 Istio 是怎么做的呢?这里,Istio 用到了 initContainers 初始化容器,在注入 Sidecar 的同时先注入 istio-init 容器,该容器被赋予了 NET_ADMIN 的 POD 网络空间权限,执行一个 iptables 脚本,该脚本用来劫持 inbound 和 outbound 的流量到 Envoy 的 15001 端口 ;

理解了原生 Istio 的实现原理后,我们的实现也就比较容易了,就是下面这个图画的:

TSF Mesh 在和 K8s 解藕后,Sidecar 统一都在 Resource Controller 这个模块中注入。

对于容器,还是注入到 deployment Crd 中再下发给 kube-apiserver,对于虚拟机,是封装到 task 任务并下发给 TSF master-api 组件,再分发给 VM 上 tsf-agent,tsf-agent 在启动应用时拉起 Sidecar。

对于流量劫持,我们也做了统一,不管是容器还是虚拟机,统一都在 Sidecar 的 pilot-agent 这个组件中执行 iptables 脚本,这里有个优化点哈,就是只劫持服务网格中注册过的服务流量,后面在讲注册中心那章的时候会详细介绍下。

那到这里,其实还有两个部署的边界问题,一个无损发布,另一个就是优雅下线

那什么是无损发布呢?简单说,应用发布成功的标志是应用能够真正提供服务(也就是注册到注册中心可被其它服务调用),或者说,有时候应用进程虽然启动成功了,但由于初始化过程比较慢比如 Java 应用,这时候还不能真正提供服务,如果简单以进程启动成功为发布成功的标志将服务实例注册到注册中心,其它服务在同步到该实例并进行调用时就会失败,这种情况就不是无损发布;K8s 中是如何实现无损发布的呢,我们知道在 K8s 中,是通过配置一个 readiness 探针的 webhook 来实现的,只有探测成功后才会更新 POD 的 endpoint 信息。

那 TSF 中既然不能依赖平台来实现类似 readiness 的功能,我们把这个能力统一放到框架中来实现,对于 Spring Cloud,我们对 SDK 进行了扩展,在应用启动期间会主动探测应用的服务状态,探测成功后才会把这个实例注册到注册中心。

对于 Service Mesh,Sidecar 会主动探测本地应用的服务状态(当然这个需要用户提供一个健康检查的接口),控制面在同步探测成功的状态后才会注册到注册中心,这里 mesh 的服务注册机制会在下一章会详细介绍。

那什么是优雅下线呢?这个应该比较好理解,就是在应用进程处理完当前所有请求后再停止,同时先要把当前服务实例从注册中心摘除,否则还有源源不断的请求过来。

K8s 中是如何实现优雅下线的呢?我们知道在 K8s 中,是通过配置一个 preStop 的 webhook 来实现的,就是在停止应用 POD 前可以执行用户配置的命令或脚本后再停止 POD,比如优雅下线一个 Nginx 服务可以在 preStop 中执行一个 Nginx–squit 命令,当然这里也是依赖 K8s 要先摘除 Pod 的 Endpoint 信息;

在 TSF 中,优雅下线的实现在容器和虚拟机上有所不同,对于容器,我们依然是通过 preStop 配置,只是会默认添加一个反注册到 Consul 的命令并且先执行,再执行用户配置的 preStop 内容;对于虚拟机,同样支持 preStop 的方式,只不过由于应用的生命周期是上面说的 tsf-agent 组件来管理,因此这里的 preStop 也是由 tsf-agent 来执行。

其实,解决了这两个部署边界问题后,我们执行无损的滚动发布也就不难了。

注册中心:解决服务注册、健康检查和服务寻址问题

应用多平台部署的问题解决以后,下面我们来聊下注册中心,就是运行时服务如何互相发现。

原生 Istio 默认采用 K8s 作为注册中心,服务发现依赖控制面组件 pilot-discovery 完成,pilot-discovery 通过适配不同注册中心同步服务注册信息,但 Istio 本身不提供服务注册和健康检查的能力,而是依赖 K8s 部署时写入的 Service 和 Endpiont 信息,并在运行时探测应用 POD 的健康状况,总之,也是依赖部署平台。

既然 K8s 已被解耦,那注册中心也没法依赖他了,我们需要重新挑选一个,然而选择并不多;

首先,Spring Cloud 支持最好的注册中心是 Consul 和 Eureka,Eureka 社区停止维护后,就剩下 Consul 了;其次,Istio 当时已经初步适配了 Consul,虽然不太完美,不过我们可以做些优化。所以,综合考虑,我们选择 Consul 了。

接下来就是对 Consul 的适配,我们对 Istio 做了些改造:

刚才说到 Istio 对 Consul 的支持不够完美,主要是当时用的 Istio 1.0 的版本还是通过 Rest API 轮询的方式去获取 Consul 注册服务信息的变更,因此我们做的第一个改造是把轮训改为 Consul Watch 的方式;

第二个改造就是:支持服务注册和健康检查,我们结合右边的流程图 来具体看下:

  1. 在 Istio 中,控制面组件 pilot-discovery 直接对接 Consul,定义了一套自己的 Service Discovery Interface 模型,比如拉取某个 NameSpace 下服务列表、拉取服务的实例列表,这里我们对 pilot-discovery 的 Service Discovery Interface 模型进行了一些扩展,增加了服务注册和心跳上报两个接口,并在适配 Consul 的 constroller 上实现了这两个接口;
  2. 同时,我们在 pilot-discover 中扩展了一个 HDS(Health Discovery Service)的 gRPC 服务,用来接收数据面服务注册和心跳上报的请求;
  3. 在数据面侧,用户提供一个 spec.yaml 服务描述文件(里面主要包含:服务名、服务监听端口、健康检查 URL 等服务注册信息),格式和 K8s 的 Serivice 配置是兼容的:

d. pilot-agent 根据 spec.yaml 文件构造 envoy-rev0.yaml,这里的 envoy-rev0.yaml 是 Envoy 的启动引导配置文件;

e. Envoy 启动时,与 Pilot-discovery 建立 gRPC 长连接,提取 envoy-rev0.yaml 服务注册信息,并通过 HDS 发送服务注册请求,这里我们对 Envoy 也做了扩展,上面pilot-discover 实现的是 HDS 服务端,那在 Envoy 中实现的就是 HDS 的客户;

f. 对于运行时,Envoy 根据 spec.yaml 文件中配置的健康检查接口周期探测本地应用的健康状态,再通过 HDS 上报给 pilot-discovery,这里在实现中复用了 Envoy 的 Health Check 功能,只是原生实现需要通过主动配置 Envoy 规则来探测外部 upstream 的健康状态,我们这里做了定制,可以在运行时主动探测本地服务的健康状态。

这大概就是我们改造后的服务注册和健康检查机制,无论是容器应用还是虚拟机应用,都采用统一的方式。

服务注册解决后,下面来看下服务间如何通信,首先要解决服务间如何寻址

我们知道,在微服务框架下通常是通过服务名进行服务调用:

  1. 对于 Spring Cloud 应用,依然采用如 RestTemplate/Feign 的调用方式,SDK 自动从注册中心同步服务列表,实现服务名和 IP 地址的转换;
  2. 对于 Service Mesh 应用,由于没有 SDK 的依赖,通常采用 DNS 的方式进行寻址;这里我们也做了些架构上的改造。

由于原生 Istio 是依赖 K8s 的 Kube-DNS 或 Core-DNS 这样的集中式 DNS 服务进行服务名的寻址的,而与 K8s 解耦后我们需要重新选择一个 DNS 方案,这里我们采用了一个分布式 DNS 方案,将 DNS 能力下沉到数据面,而采用这种 DNS 架构方式,主要考虑到以下 4 个原因:

  • 首先,组网无需部署一套 DNS 集群
  • 其次,由于下沉到了每个数据面,因此也不存在单点故障的问题
  • 所以,也无需考虑集群容灾等高可用性问题
  • 而且,因为仅处理本地 DNS 请求,压力小解析快

右图是服务寻址的一个详细流程图,服务名的解析是通过我们自研组件 Mesh-DNS 来实现的,这里 Mesh-DNS 会对接控制面的 pilot 组件,实时同步服务网络中注册的服务列表,如果 DNS 请求中的服务名在服务列表中,就返回一个后续可以被 iptable 重定向到 Envoy 的特定 IP;

如果服务名不在服务列表中,则把 DNS 请求转发给本地 resolve.conf 配置的 NameSever;

这里我们没有通过配置 resolve.conf 文件的方式提供 DNS 服务,而是间接地通过将 DNS 请求劫持到 Mesh-DNS 上,为什么要这么设计呢?

只要有三个原因:

  • resolve.conf 的生效时间,在 Linux 中 DNS 解析是通过 glibc 实现,而 glibc 在 2.26 版本之前有个 Bug,如果进程不重启,新配置的 NameSever 是不会生效的,在容器部署下,Mesh-DNS 和应用分别部署在同一个 Pod 的不同容器中,因此容器的启动是相互独立的,也就是很有可能应用容器启动了,但 Mesh-DNS 所在容器还没有启动好,这样 Mesh-DNS 后面配置的 DNS NameSever 就不会生效,有人说可以在 InitContainer 容器中修改,其实也是有问题,如果容器异常重启后,resolve 配置文件也同样会被还原导致服务不可用。
  • 第二个原因,有些用户可能会安装自己的 DNS 服务如 DNSmasq,已经占用了 53 端口,这样就会引起端口冲突。
  • 第三个原因,如果要配置,我们不会覆盖 resolve 文件 中原有的 NameServer 配置,只是追加进去,那 glibc 在 DNS 解析时就会进行 NameSever 的轮训,如果轮训到其它 NameSever 进行服务名的解析请求,那肯定会报错。

出于这三个方面的考虑,【采用 DNS 劫持】是一个比较合理的方案,具体可以看下,右图中 DNS 劫持的 iptables 规则,看下第一个红框,所有到 53 端口(也就是 DNS 请求)的流量被重定向到本地的 55354 端口,55354 端口运行就是 mesh-dns 服务;

再看下第二个红框,所有到 168.254.48.46 这个目的 IP 的流量被重定向到本地的 15001 端口,15001 端口运行的就是 Envoy 服务,这里就是应用出流量的劫持;

这里 168.254.48.46 这个 IP 就是为了只劫持服务网格内的服务,Mesh-DNS 解析出的特定 IP。

服务治理:打通 Spring Cloud 与 Service Mesh 服务治理能力

到这里,我们已经解决了注册中心统一和 Mesh 服务注册发现的问题,那运行时两个框架的服务治理能力如何互通呢?

那什么是服务治理能力的互通呢?

举个例子,比如一个 Spring Cloud 应用要调用 Mesh 应用,如何发现 Mesh 应用的服务实例列表,如何根据路由规则路由到 Mesh 应用的具体实例;

同样,对于 Mesh 应用在访问 Spring Cloud 应用时,如果 Spring Cloud 应用不能正常提供服务,如何实现服务熔断?

这就需要在服务治理能力上进行拉通;而我们面临的挑战是:两种微服务架构都有各自完善的服务治理体系,无论从架构模式,还是数据模型及实现逻辑,都存在较大差异。那我们是如何实现的呢?

不过我们发现:无论哪种微服务框架,治理能力都是通过服务的形式提供出来,实际用户并不需要感知服务的具体实现;

比如,对于 Spring Cloud 应用通过引入一个 Hystrix 注解就可以实现服务熔断,而对于 Service Mesh 应用,通过控制面配置一个 Destination Rule 规则即可;

因此,基于这个共同点, 我们从部署模式、服务及功能模型上进行了拉通;

首先,也是最重要的,虽然 Spring Cloud 应用和 Mesh 应用都注册到同一个注册中心,但如果注册信息不一致,依然没法实现服务互通,所以这里我们先实现了服务注册元数据模型的统一

再比如,服务 API 的治理,Spring Cloud 应用采用 Swagger 汇聚 API 列表,目前 Swagger 以标准 OpenAPI v3 版本的格式输出,而对于 Mesh 应用,因为没有 SDK 的依赖,我们对 Mesh 进行了一些扩展,能够支持 用户根据 OpenAPI 标准 自行定义的 API 列表;

后续便可基于服务 API 进行统一治理,比如基于 API 的路由、熔断、限流等等,当然也可以对具体的 API 进行在线调试;

而对于服务路由、熔断、限流、鉴权几个治理能力的拉通我们的思路是类似的。

首先,是统一策略配置的元数据模型,那什么叫统一策略配置呢?就是无论是 Spring Cloud 还是 Service Mesh 在控制台入口都采用一致的配置项、配置视图;其次,是对齐服务治理的实现算法,比如路由都基于标准权重算法来实现、熔断都基于标准熔断器来实现、限流都基于标准令牌桶来实现。

当然,我们也对一些治理能力做了增强和扩展,比如熔断,对于 Spring Cloud,开源是 Hystrix 实现,(不过 2018 年 11 月已经停止维护),所以我们采用了官方推荐的 Resilience4J 作为底层实现,同时扩展支持实例、API 和服务三个级别的熔断隔离,以便适应更多的熔断场景;

那对于 Service Mesh 呢,用过 Envoy 的同学可能知道,其实原生 Envoy 的 Upstream 里并没有实现业界标准的熔断器,而且只是实例级别的熔断,是简单通过 Upstream 连接池和 Outlier Detection 异常检测的方式来实现的;

因此这里我们对 Envoy 也进行了增强,首先是实现了业界标准的熔断器,其次是也支持实例、API 和服务三个级别的熔断隔离。

下面,我们以服务路由为例,具体介绍下如何拉通两个框架的路由能力。所谓拉通服务路由,我们的理解是:在配置相同路由规则的情况下,输入相同的请求流量,不论是 Spring Cloud 实例还是 Service Mesh 实例都能达到一致的路由效果,也就是上面抽象出的公式,

在相同的 Inbound 入流量请求下:

  1. 首先,对于路由规则,Spring Cloud 服务和 Service Mesh 服务都采用了一致的配置视图,如上图这个配置,保证了公式中 c 参数的一致;
  2. 其次,统一的注册中心,以及一致的服务注册元数据,保证了两个框架的 consumer 都能拉取到相同的 provider 实例列表,保证了公式中 p 参数的一致;
  3. 最后是算法上的对齐,都基于标准权重算法来实现路由,保证了 Route1 和 Route2 实现上的一致。

因此,输出结果 out1 和 out2 必然是一致的;虽然中间的实现逻辑不一样,如图中 Mesh 的路由规则下发,流经三个组件最终在 Envoy 中生效,但最终的路由效果和 Spring Cloud 是一致的。这里简单介绍下 Mesh 的策略下发过程,由于原生 Istio 依赖 K8s 的 ConfigMap 进行配置的存取,配置的入口就是 kube-apiserver,那与 K8s 解藕后,我们统一采用了自研组件 mesh-apiserver 作为容器和虚拟机 Mesh 的配置入口,控制台通过 mesh-apiserver 下发 Mesh 各种服务治理配置,mesh-apiserver 再以 pilot 的 Crd 配置模型写入 Consul,然后 pilot-discovery 再从 Consul 同步配置更新,最终构造成 Envoy xDS 协议标准的数据包下发给 Envoy;后面的流程跟开源 Istio 是一样的;

对于其它服务治理能力的拉通原理跟上面的路由是类似的,这里就 不过多介绍了!

数据运营:统一观测两个框架体系的服务,打通日志、监控和调用链

到这里,我们解决了不同平台统一部署的问题,解决了不同框架服务通信的问题,以及不同框架服务治理能力互通的问题。

最后一步,也是非常重要的一步,就是如

何来运营我们的业务?如何统一观测服务的运行状态?

这里我们通过自研的 APM 平台,统一整合了日志、监控、调用链的采集、解析、存储和查询方案。

首先,我们来看下右边这个架构图,整个 APM 方案,分为控制流和数据流;先来看下控制流,图上的实线:

  1. 首先是控制台向后端 APM 发送日志配置(如日志创建、变更)、日志检索、调用链检索、服务依赖拓扑等请求;
  2. 如果是日志配置,对于虚机平台,APM 向虚机管控中心服务(图中的 TSF Master 模块)下发日志配置信息,TSF Master 再下发配置至具体实例的 Agent 上,Agent 构造 Filebeat 配置并启动 Filebeat 进程;
  3. 对于容器平台,APM 向资源管理模块(图中的 TSF Resource 模块)下发日志配置信息;
  4. TSF Resource 再根据日志配置构造 Filebeat 配置并以环境变量方式注入 Filebeat 容器,并拉起 Filebeat 容器;
  5. 同时,APM 会根据日志配置,请求 ES,构建对应的预处理器和权限;如果是日志检索、调用链检索的请求,APM 构造一个查询请求并发送到 ES 集群;
  6. 而对于日志监控告警指标的配置请求,是通过图中的 TSF Monitor 模块来处理,构造一个告警指标的查询请求并发送给 ES 集群。

再来看下数据流,图上的虚线:

  1. 首先是虚拟机或者容器中,应用进程产生业务和调用链日志,并写入到本地日志文件;
  2. 然后,Filebeat 进程采集指定路径下的日志文件数据,并进行多行合预处理;再将日志数据以批量方式发送至 ES 集群;
  3. 这里,Pipeline 先做数据预处理,解析日志数据、处理时间戳和索引名再进行存储;
  4. 对于控制台的查询请求如日志检索,ES 处理查询请求并返回匹配的原始文档数据;APM 收到原始文档数据后,进行建模、合并等处理后再返回给前端展示;
  5. 而对于日志监控告警,TSF Monitor 将请求返回的数据推送至 Barad 事件中心和告警平台;
  6. Barad 平台和告警平台再向【前端页面】提供监控图表数据;
  7. 上面就是我们自研 APM 的大体方案,Spring Cloud 应用和 Service Mesh 应用都统一采用了这样的监控方案。

到这里,我们只是讲了 APM 平台的统一。如果要做到指标数据的打通,比如图中 Service Mesh 应用调用 Spring Cloud 应用的场景,如何在调用链层打通呢?

这里我们也做了些技术选型和改造:

  1. 首先,我们需要一个统一 Tracing 数据模型,这里选择了 OpenTracing 的开源标准模型;
  2. 对于 Spring Cloud,我们的采用的是 Sleuth 实现,因为 Sleuth 本身能比较完美的支持 OpenTracing,不过我们也做了些简单扩展,比如支持 Trace 日志本地落盘、以及调用链和日志的联动;
  3. 对于 Service Mesh,Trace 由 Envoy 输出,Envoy 原生是支持 Open Tracing 的,但后端需要接 Zipin 或者 Jeager 这样的 Trace 服务,我们这里也做了些扩展,在原生 envoy.zikin 实现的基础了扩展了 envoy.local,使其支持 Trace 日志本地落盘。

这里 Trace 日志输出都采用的是本地落盘的方式,主要考虑到几个原因:首先,可以和后端的 Trace 服务解藕,不一定需要对接像 Zipin 或者 Jeager 后端服务其次,就是可以异步采集,先落盘再采集,同时保证了 Trace 日志的不丢失还有就是可以方便对接其它 Trace 服务,比如客户想对接自己已有的调用链平台。

应用迁移:Spring Cloud 应用逐步向 Service Mesh 迁移

到这里,我们已经完成了 Mesh 微服务平台的搭建;那在这样的平台上我们如何做架构迁移呢?

先看一个的漫画,上面勾画了通往云原生的三条路:

  1. 最上面那条路,Go Native 迁移,平台架构、微服务架构直接重构只云原生形态,迁移思路非常直接,但道路曲折,改造量大,且风险极高,很容易掉沟里;
  2. 中间那条路,Evolve 迁移,先平台迁移容器化改造,再做微服务架构迁移,方案相对比较稳妥,迁移道路较平坦,但迁移周期可能会比较长;
  3. 最下面那条路,Lift & Shift 迁移,考虑到物理机的运行场景,先物理机上云,再做容器化改造,最后 Mesh 化改造,方案非常稳妥,但迁移周期可能非常长。

这三条路我们应该如何选择呢?

而在我们搭建好上面的 Mesh 微服务平台后,其实我们可以更平稳、更快速的通达 Service Mesh 云原生:

  1. 我们可以按照传统较稳妥的 Evolve 或者 Lift & Shift 方式迁移,图中最上面的迁移路线,先容器化再 Mesh 化,这里就不多说了;
  2. 也可以采用激进点的 Go Native 方式,图中最下面的迁移路线,直接 Mesh 化,因为我们的微服务框架已经完全和 K8s 平台解藕,可以完美运行在虚拟机或物理机上;
  3. 在 Go Native 的迁移方案上我们还可以再激进点,就是图中中间的迁移路线,一部分业务先做全形态迁移,一部分业务只做 Mesh 迁移,但新老应用要保持业务上的互通,正如开篇介绍的,其实这种更符合实际业务迁移场景;

之所以能够实现这样的迁移,是因为我们前面构建的微服务平台是支持多框架的,框架间的治理能力、监控运营也都一 一打通了。

聊到这儿,是不是像 Go Native 这种比较激进的迁移方式也可以如此简单安全呢!

当然,除了满足客户架构迁移的场景,对于不少公司目前多语言多技术栈的业务场景也是可以满足的,比如:

Java 应用依然跑在 Spring Cloud 上,其它语言由于没有太多统一的服务治理框架,其服务治理 完全可以通过 Service Mesh 来接管,

这样,Java 应用和非 Java 应用不仅可以统一进行服务治理,业务上的互通也完全不受影响。

当然,除了上面介绍的几个迁移场景,我们还可以应对更多的迁移场景,比如单体架构的迁移、传统 SOA 架构的迁移等等。

总结

这次分享主要以腾讯云 TSF Mesh 为例,介绍了如何一步步构建一个跨平台多框架的基于 Service Mesh 微服务平台,

来帮客户解决实际迁移过程的痛点问题,希望能帮助大家在做架构演进或迁移时带来一些思考和启发。

往期

推荐

扫描下方二维码关注本公众号,

了解更多微服务、消息队列的相关信息!

解锁超多鹅厂周边!

戳原文,了解更多腾讯微服务平台的信息

点亮在看,你最好看