如何在 Istio 中支持 Dubbo、Thrift、Redis 以及任何七层协议?

赵化冰,腾讯云高级工程师,Istio Member,ServiceMesher管理委员,Istio 项目贡献者, Aerika 项目建立者 ,热衷于开源、网络和云计算。目前主要从事服务网格的开源和研发工做。ios

唐阳,知乎基础架构工程师。Istio 项目贡献者,Argo 项目贡献者,专一于开源,云原生与微服务。目前负责知乎服务网格的研发工做。git

备注:本文根据腾讯云赵化冰和知乎唐阳在 IstioCon 2021 中的演讲 “How to Manage Any Layer-7 Traffic in an Istio Service Mesh?” 整理而成。github

你们好,今天咱们想和你们分享的主题是如何扩展 Istio 以支持任何七层协议?做为云原生领域中一我的气很是高的开源项目, Istio 目前已经基本成为了 Service Mesh 的事实标准。腾讯云上也提供了基于 Istio 进行加强,和 Istio API 彻底兼容的 Service Mesh 管理服务 TCM(Tencent Cloud Mesh),以帮助咱们的用户以较小的迁移成本和维护代价快速利用到 Service Mesh 提供的流量管理和服务治理能力。今天很是高兴可以有这个机会来和你们一块儿分享一下咱们在此过程当中的一些经验。数据库

Service Mesh 提供了一个对应用透明的基础设施层,能够解决咱们在分布式应用/微服务中遇到的常见挑战,例如:如何找到服务提供者?如何保证服务之间的通讯安全?如何得知服务之间的调用关系?如何进行流量管理如灰度发布?等等。Service Mesh 的实现方式是伴随应用部署一个 Sidecar Proxy,该 Sidecar Proxy 会拦截应用的出向和入向流量, 对这些流量进行分析和处理,以达到在不修改应用代码的状况下对服务进行流量管理、安全加密,遥测数据收集的目的。为了实现这些服务治理能力,Sidecar Proxy 不仅须要在 OSI 网络模型的3、四层上对流量进行处理,更重要的是须要在七层上进行处理。在七层上,Istio 缺省只支持了 HTTP 和 gPRC 两种协议。但咱们在微服务中常常还会使用到的其余七层协议,当将这些微服务应用迁移到 Service Mesh 时,咱们但愿使用一致的方式对全部的这些七层协议进行统一管理,以充分利用 Service Mesh 基础设施提供的云原生能力。后端

在今天的分享中,我将会介绍几种将 Istio 流量管理能力扩展到其余七层协议的方法,并对比分析这几种方法各自的优缺点。我会介绍如何利用 Aeraki 开源项目来在 Istio 中管理任何七层协议,包括 Dubbo、Thrift、Redis 等。为了让你们了解 Aeraki 是如何工做的,会展现一个采用 Aeraki 实现 Thrift 服务 Traffic Splitting 的例子。来自知乎的唐阳还会为咱们展现如何使用 Aeraki 的一些有趣的真实案例。缓存

Service Mesh 中常见的七层协议

以下图所示,一个典型的微服务应用中一般会使用到这些七层协议:安全

  • 同步调用:不一样服务之间会采用 RPC (远程方法调用)进行相互调用。常见的 RPC 调用协议包括 gRPC,Thrift,Dubbo,HTTP 也能够看作一种 RPC (只支持 GET/SET/POST 这几种标准方法) 。一些大的公司为了知足本身特定业务场景的需求,每每还会采用一些私用的 RPC 协议。
  • 异步消息:除了 RPC 以外,异步消息也是微服务通讯的一种常见模式,包括 Kafka,RabbitMQ,ActiveMQ 等。
  • 各类数据库和缓存系统:例如 Redis, MySQL,MongoDB 等等。

那么当将这样一个微服务应用加入到 Service Mesh 之后,咱们但愿可以经过 Service Mesh 获得哪些管理能力呢?服务器

理想状况下,咱们但愿 Service Mesh 可以管理微服务中用到的全部七层协议的流量,包括 RPC、Messaging、Cache、DB等。例如:微信

  • 基于请求的负载均衡:能够未来自同一个 TCP 连接的多个独立的请求分发到不一样的后端服务器,以实现更智能,更合理的负载均衡。
  • 基于七层 Header 的流量路由:根据七层 Header 中的属性进行路由,例如根据 Dubbo 请求中的服务名或者 Redis 请求的 Key 进行路由。
  • 对客户端的请求响应注入延迟或者错误,以测试应微服务用的弹性。
  • 提供应用级安全,例如基于 HTTP Header 中的 JWT Token 进行认证,或者对 Redis 服务器进行认证。
  • 请求层面的遥测数据,包括请求成功率、请求耗时、调用跟踪等等。

要实现以上这些流量管理和服务治理能力,Service Mesh 须要分析和处理 TCP 数据包中的七层协议的 Header。即 Service Mesh 必须具备七层协议的管理能力,而不仅是在 TCP 层面上进行处理。网络

然而在 Istio 中,对于除了 HTTP 和 gRPC 以外的协议,咱们只能在 OSI 三到六层对这些协议进行处理。这意味着咱们只能基于三层的 IP 地址,四层的 TCP 端口或者六层的 SNI(Server Name Indication)对这些协议进行路由。只能收集到 TCP 层面的指标,例如 TCP 收发包数量或者打开/关闭的 TCP 连接数量。只能采用 mTLS 进行链路层面的认证和权限控制。换而言之,对于这些协议,咱们依然须要在应用代码中处理流量控制、可观测性、安全认证这些本应该由 Service Mesh 基础设施来统一处理的共性问题。这违背了咱们将微服务迁移到 Service Mesh 的初衷:将微服务通讯和治理的共性问题从应用代码下沉到 Service Mesh 基础设施层。

如何扩展 Istio 的协议管理能力?

若是咱们但愿可以在 Istio 中管理这些七层协议,咱们应该如何实现呢?假设咱们有一个 BookInfo 微服务,但该微服务采用了一种称为 AwesomeRPC 的协议而不是 HTTP 来实现服务间的远程调用。

咱们来看一下如何才可以在 Istio 中实现 AwesomeRPC 协议的流量管理,例如根据请求 header 中的 user name 字段未来自 ProductPage 的请求路由到不一样版本的 Reviews 中,以实现一个灰度发布的场景。

咱们想到的最显而易见的方式就是直接修改 Istio 代码。首先咱们须要在 Istio 的 VirtualService CRD 中支持 AwesomeRPC 协议。加强后的 VirtualService CRD 以下图中最左的规则配置所示。 AwesomeRPC 和 HTTP 路由的语义相似,都是根据 Header 中某些属性的值进行路由。所以咱们只须要将 HTTP 协议类型改成 AwesomeRPC,能够直接采用 VirtualService 中的 HTTPRoute 结构来表示 AwesomeRPC 的路由规则。而后咱们须要在 Pilot 代码中根据 AwesomeRPC 的服务定义和 VirtualService 定义的路由规则生成 Envoy 所需的真实配置,并经过 xDS 下发给数据面的 Envoy。固然,以上的前提是咱们已经经过 Envoy 的 Filter 扩展机制编写了 AwesomeRPC 的 Filter 插件,实现 AwesomeRPC 的编解码,Header 解析,动态路由等数据面所需的功能。

采用这种方式,在 Envoy Filter 已经实现了的状况下,在控制面增长一个新的七层协议的过程是相对比较简单的。可是因为咱们修改了 Istio 的源码,所以须要本身维护一个 Istio 的私有分支,这致使了额外的维护代价,而且很难跟上 Istio 快速的迭代步伐。

若是不但愿维护本身的 Istio 代码分支,一种可行的替代方式是采用 Istio EnvoyFilter CRD:EnvoyFilter 是 Istio 提供的一种灵活强大的配置机制。咱们可使用 EnvoyFilter为 Pilot 生成的缺省 Envoy 配置打一个补丁,添加、修改或者删除缺省 Envoy 配置中的部份内容,以按咱们的要求修改 Envoy 在 Istio Service Mesh 中的缺省行为。

以下图所示,因为 Pilot 并不理解 AwesomeRPC 协议,对于 Pilot 来讲, AwesomeRPC 服务只是一个 TCP 服务。在 Pilot 生成的缺省配置中,AwesomeRPC 服务对应的 Outbound Listener 的 FilterChain 中采用了一个 TCP Proxy 来处理其流量。咱们在 EnvoyFilter 的 Match 部分中选中该 TCP Proxy,并在 Operation 部分将其替换为一个配置了 Traffic Splitting 规则的 AwesomeRPC Filter。Pilot 会根据 EnvoyFilter 修改其生成的缺省 Envoy 配置,而后下发到数据面的 Envoy 上。这样咱们就经过 EnvoyFilter 在 Istio 中实现了对 AwesomeRPC 协议的支持。

下面咱们来看一个采用 Thrift 协议的真实案例。Thrift 是 Apache 基金会下一个轻量级、支持多语言的开源 RPC 框架。Envoy 中已经支持 Thrift,但 Istio 中只对 Thrift 提供了有限的支持,并不能实现 Traffic Splitting 等高级流量管理功能。若是咱们但愿在 Istio 中提供下图中右下角所示 Thrif 服务的 Traffic Splitting 流量控制,咱们能够经过 EnvoyFilter 来实现。

(本示例相关源码能够从 https://github.com/aeraki-framework/thrift-envoyfilter-example 下载)

首先,咱们须要建立一个图中左边所示的 EnvoyFilter 来处理客户端的出向流量,该 EnvoyFilter 的 Match 条件选中了 $(thrift-sample-server-vip)_9090 这个 Outbound Listener 中 的 tcp_proxy,在 Patch 部分将其替换为一个 thrift_proxy。在该 thrift_proxy 中,咱们按照 Traffic Splitting 的要求为其配置了相应的路由:将 30% 的流量路由到 Server v1版本,70% 的流量路由到 Server v2 版本。咱们也须要为 Thrift Server 端建立一个如图右上所示的 EnvoyFilter 来处理服务器端的入向流量。相比客户端的 EnvoyFilter 而言,服务器端的 EnvoyFilter 配置要简单一些,所以咱们不须要在服务器端配置任何路由规则,只须要将 tcp_proxy 替换为 thrift_proxy 便可。这个 thrift_proxy 虽然没有路由规则,但提供了大量七层的服务通讯和治理能力,包括请求层面的负载均衡、产生请求层面的 Metrics 数据等。

从上面的介绍和示例能够看到, EnvoyFilter CRD 比如是 Istio 中的一把瑞士军刀,能够对 Pilot 生成的 Envoy 配置进行很是灵活的定制,以达到对七层协议进行管理的目的。可是 EnvoyFilter 也带来了一些难以处理的问题:

  • EnvoyFilter 将 Envoy 的底层实现细节直接暴露给了运维人员:运维人员必须很是了解 Envoy 的配置细节,而这些配置细节每每和 Envoy Filter 内部的实现机制紧密相关,例如 Filter 的名称和 Filter 内部的配置格式等。这致使建立 EnvoyFilter 成为了一种和代码细节高度耦合的工做,难以直接交付给运维人员。更为合理的方式则应该是采用一种面向用户的高级配置语言来屏蔽这些实现细节,例如 Istio 中的 VirtualService 和 DestinationRule。
  • EnvoyFilter 中的匹配条件依赖于 Pilot 生成的 Envoy 配置中的结构组成和元素命名,例如 Listener 的名称,FilterChain 的构成等。而这些结构和命名在不一样的 Istio 版本之间可能发生变化,致使本来可以正常工做的 EnvoyFilter 在新版本中出现问题。
  • EnvoyFilter 中的匹配条件还依赖于一些和特定 K8s 集群相关的内容,例如 Service Cluster IP,这意味着一个 EnvoyFilter 不能用于多个不一样集群中的相同服务。当 Service 被重建时,因为 Cluster IP 会发生变化,相应的 EnvoyFilter 也必须进行改动,修改 Match 条件中的 Cluster IP。
  • 咱们须要为每一个 Service 建立相应的 EnvoyFilter,当 Mesh 中管理的服务较多时,手动建立成百上千的 EnvoyFilter 的工做是很是繁琐并且及易出错的。
  • 对 Istio 而言,EnvoyFilter 中的 Patch 部分基本上是一个黑盒,所以 Istio 只能对 EnvoyFilter 的正确性进行很是有限的验证。这致使 EnvoyFilter 的调试很是困难,当 Envoy 未能按照你的设想工做时,你很难知道究竟是 EnvoyFilter 的什么地方出现了问题。

因为上述的种种问题,咱们能够看到,虽然可使用 EnvoyFilter 来在 Istio 中实现七层协议的管理,可是在一个生产系统,特别是一个中大型的 Service Mesh 中管理和维护这些 EnvoyFilter 是很是困难的。

Aeraki:在 Istio 中管理任何七层协议

因为难以手动对 EnvoyFilter 进行管理和维护 ,咱们建立了Aeraki (发音:[Air-rah-ki])项目来自动化这个流程。Aeraki 是希腊语中“微风”的意思,咱们但愿 Aeraki 这股微风能帮助 Istio 在云原生的旅程中航行得更远。

Aeraki 的基本工做原理以下图所示:Aeraki 从 Istio 中拉取服务数据,根据 ServiceEntry 和 Aeraki 流量规则生成 Envoy 配置,并采用 EnvoyFilter 将生成的配置推送到 Istio 中。简而言之,你能够把 Aeraki 看作 Istio 中管理的七层协议的 Operator

相比于直接修改 Istio 代码和采用 EnvoyFilter 这两种扩展 Istio 流量管理能力的方式,采用 Aeraki 为咱们带来了如下的好处:

  • 不须要修改 Istio 代码,所以节省了单独维护一个 Istio 的私有代码分支的额外工做量,能够快速跟随 Istio 的版本迭代进行升级。
  • Aeraki 做为一个独立组件部署在 Mesh 的控制面,能够很方便地做为一个插件和 Istio 进行集成,对 Istio 的流量管理能力进行扩展。
  • 协议相关的缺省配置由 Aeraki 自动生成,而且这些配置能够根据 Istio 版本和 K8s 集群相关信息自动进行调整。节约了大量 EnvoyFilter 的手动建立和维护工做。
  • Aeraki 在 Envoy 配置之上进行了抽象,提供了一层面向用户的配置 CRD 来对这些七层协议进行管理。这些高级 CRD 隐藏了 Envoy 的配置细节,屏蔽了不一样 Istio 版本生成的缺省 Envoy 配置的差别,对于运维很是友好。对于 Thrift 和 Dubbo 这样的 RPC 协议,因为其语义和 HTTP 相似,Aeraki 直接采用了 Istio VirtualService 和 DestinationRule;对于非 RPC 协议,Aeraki 则定义了一些新的 CRD 来进行管理,例如 RedisService 和 RedisDestination。咱们后面将进一步介绍如何使用这些配置 CRD 来定制规则,例如实现 Traffic Splitting。

和 Istio 相似,Aeraki 也采用了端口名称来识别协议类型。端口取名须要遵循 “tcp-七层协议名-xxx” 的命名规则。例如,一个 Thrift 服务应取名为 “tcp-thrift-service”。须要注意的是,咱们必须保留端口名中的“tcp-”前缀,由于对于 Istio 而言,这是一个 TCP 协议的服务。Aeraki 则会根据端口名中的七层协议来生成相应的 Envoy 配置,并替换 Istio 缺省生成的 tcp_proxy。

咱们来看看如何采用 Aeraki 来实现上面 Thrift 服务的 Traffic Splitting 用例。首先咱们须要在 Thrift Service 定义的 Port 命名中声明该 Service 的七层协议类型:“tcp-thrift-hello-server”,而后建立一个 VirtualService 将 Thrift 请求按照指定比例路由到不一样的服务版本中。Aeraki 将根据服务定义和 VirtualService 生成所需的 Envoy 配置,并经过 EnvoyFilter 发送给 Istio。

能够看到,相对于手动建立 EnvoyFilter,采用 Aeraki 来管理 Thrift 要简单得多。若是不须要特殊的流量规则,则会更简单,只须要按照命名规范在 Port 名称中声明 Thrift 协议便可,Aeraki 会生成所需的 Envoy 配置,无需任何额外的工做。

想本身试试 Aeraki 的 Thrift、Dubbo、Redis 服务管理能力?很是简单,只需在一个链接到 K8s 集群的命令行终端上运行下面两行代码,就能够安装一个带有 Aeraki 插件的 Istio 集群以及相应的 Demo 程序,欢迎你们尝试!

`git clone https:``//github``.com``/aeraki-framework/aeraki``.git``aeraki``/demo/install-demo``.sh`

也能够访问 Aeraki 的在线 Demo,查看从 Thrift、Dubbo、Redis 等服务收集到的监控指标面板:http://aeraki.zhaohuabing.com:3000/d/pgz7wp-Gz/aeraki-demo?orgId=1&refresh=10s&kiosk

使用 Aeraki 加强 Service Mesh

下面咱们来看一下使用 Aeraki 的七层协议管理能力来加强 Service Mesh 的一些案例。

屏蔽开发/生产环境的差别

咱们在开发、测试和生产环境中一般须要访问不一样的后端资源,例如须要链接到不一样的 Redis 缓存或者不一样的 mySQL 数据库。通常来讲,咱们须要修改随应用程序发布的配置文件中的后端资源地址,以达到在不一样环境中切换后端资源的目的。经过 Aeraki 的帮助,咱们能够用 Service Mesh 来屏蔽不一样后端资源的配置差别,使得应用程序能够用相同的方式访问不一样环境中的后端资源。

以下图所示,咱们在 Dev、Staging 和 Prod 三个环境中都须要访问 Redis 服务,这三个 Redis 服务有不一样的 IP 地址和访问密码,部署方式也可能不一样:在开发环境中,为了节约资源和简化部署,咱们可能使用单个 Redis 实例;在测试和生产环境中,咱们会使用 Redis 集群来保证 Redis 服务的高可用和扩展性,咱们也可能直接使用云服务商提供的 Redis 托管服务。当在这三个环境中进行切换时,咱们须要配置不一样的 IP 地址和访问密码,若是 Redis 部署的方式不一样,咱们甚至可能须要修改客户端代码来切换 Redis 单实例模式和集群模式,这极大影响了咱们开发、测试和上线的效率。

经过 Aeraki 提供的 RedisService 和 RedisDestination CRD,咱们能够屏蔽这些不一样 Redis 服务提供者之间的差别,容许客户端以统一的方式访问后端的 Redis 服务。

在采用 Aeraki 以前,咱们在不一样的环境中须要配置不一样的 IP 地址和 Redis 访问密码。采用 Aeraki 以后,在客户端能够采用相同的代码和配置,经过修改 Aeraki CRD 来切换不一样环境中的 Redis 配置,大大减小在不一样环境之间进行切换的成本。即便 Redis 从单实例改成了 Redis 集群,客户端也能够采用相同的方式进行访问。

采用流量镜像进行对比测试

有一些数据库或者数据库代理采用相同的网络协议。例如 TiDB、Oceanbase、Aurora、Kingshard等都兼容 MySQL 协议;Twemproxy、Codis、Tendis、Pika等都采用了 Redis 协议。因为业务需求,咱们有时须要从一个实现迁移到另外一个实现上。在迁移以前,咱们须要进行对比测试,以对比不一样实现的性能、功能及兼容性。

例以下面的场景:咱们最初只用了一个单实例 Redis 来作缓存,随着线上业务的不断扩展,该 Redis 实例已经出现了访问瓶颈,咱们但愿切换为采用 Twemproxy 来对 Redis 进行水平扩展。经过采用 Aeraki 来将线上的 Redis 流量镜像到 Twemproxy 测试环境,咱们能够采用真实的业务数据对 Twemproxy 进行充分的测试,以评估其对线上业务的影响。

采用全流量故障注入测试系统弹性

Istio 能够实现 HTTP 和 gRPC 的故障注入,但这还不够。在一个分布式系统中,应用服务、数据库、缓存、消息系统等均可能因为网络或者其余缘由出现不可用的状况。采用 Aeraki,咱们能够对系统中的全部这些可能的故障点进行完整的模拟,以测试系统的弹性,保证咱们的系统在一部分出现问题后能够自愈或者经过降级保证系统基本可用,而不至于整个系统崩溃。

小结

Service Mesh 中有大量的七层协议流量,包括 RPC、Database、Cache、Messaging 等类型的七层协议,但 Istio 只提供了 HTTP 和 gRPC 的七层管理能力,对其余七层协议的支持很是有限。Aerkai 开源项目经过非侵入的方式为 Istio 提供了任意七层协议的支持能力,并提供了面向用户的高级配置 CRD,能够很方便地对这些协议的流量进行管理,实现灰度发布等高级流量管理能力。目前 Aeraki 已经支持了 Thrift、Dubbo、Redis、Kafka、Zookeeper,并即将支持更多的协议。Aeraki 的定位是作成一个非侵入式 Istio 功能加强工具集,除了协议扩展以外,还会关注解决在 Istio 使用过程当中遇到的其余常见问题,包括效率优化、配置简化、第三方服务发现接入、功能扩展等。若是您但愿了解更多关于 Aeraki 的内容,欢迎访问 Github 主页 https://github.com/aeraki-framework/aeraki

**招聘信息

腾讯云 Service Mesh 团队正在火热招聘中,Base 成都、北京、深圳或者西安,要求候选者熟悉 Kubernetes/Istio/Envoy。欢迎你们发送简历到 huabingzhao@tencent.com 或者微信联系 zhao_huabing。

参考连接:

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!