腾讯蓝鲸 API 网关如何借助 APISIX 实现产品升级与业务完善
分享嘉宾朱雷,腾讯 IEG 运维 PaaS 平台技术负责人。
蓝鲸(全名“蓝鲸智云”)是一套孵化于腾讯 IEG(互动娱乐事业群)内部,服务于多业务与各内部平台的研运一体化 PaaS。 其作用是在 CI、CD 和 CO 三个阶段,对公司业务提供全生命周期的服务。

蓝鲸 API 网关
既然是为内部业务服务,那么腾讯游戏的业务都有哪些特点呢?
大家在日常生活中肯定也接触过很多腾讯出品的游戏。在腾讯内部,可能有上千款的游戏业务,除部分自研游戏外,大部分都属于代理类。代理业务的特点在于,它们是由不同公司所开发,因此各产品使用的语言、依赖的存储或者整个架构风格可能都是千差万别的。
面对这种包含大量异构架构的复杂业务场景,蓝鲸作为一个服务于内部的平台产品,在建设时就需要做到以下几点:
采用原子化设计,将平台能力抽象、打散,进行微服务化改造,形成一套 PaaS 架构;
使用低代码技术高效开发 SaaS,来使用 PaaS 平台的原子能力;
通过各类 SaaS 来灵活应对不同的服务场景。
诞生背景
考虑到现实中的业务环境与内部业务需求,最终蓝鲸平台的整体架构如下图所示。

中间的蓝色部分是 PaaS 层,其中最大的一块是前面提到的各类原子能力。包括用户管理、统一权限中心、统一配置平台等。
上层的橙色部分是 SaaS 层,主要由很多不同角色针对特定需求场景开发的 SaaS 构成。这些 SaaS 在开发过程中,或多或少地都需要通过 API 使用 PaaS 层最核心的各平台原子能力。在这种情况下,就势必需要一个统一的 API 网关。
以上就是最初想要打造蓝鲸 API 网关的原因。将前面的架构图做一些抽象,就会得到一个如下简单的网关画像。

蓝鲸是一个比较复杂的平台,它对于统一网关的需求也会比较复杂。除了最基础的作为代理去调用原子平台的 API 之外,还需要提供一些额外的网关能力。比如服务发现、统一用户认证和鉴权、限流限速,等等。
另一方面,随着云原生技术的发展,如今内部很多 SaaS 和原子平台也开始部署在 K8s 集群中。这类场景又向网关提出了新的要求,比如需要通过统一流量网关或 API 网关来将外部的调用请求流量统一管控。同时,内部还存在一些业务系统,本身使用了蓝鲸平台的一些基础架构能力,如容器管理或监控等,它们也需要一个统一的服务网关来管理所有调用流量。
面对外部技术的趋势与内部业务的发展要求,蓝鲸的 API 网关需要支持的场景开始变得越来越多样化。
往期迭代
蓝鲸 API 网关到目前为止经历了三个阶段的迭代。
蓝鲸 API 网关的 1.0 版本,主要是让原子平台的调用方(含各 SaaS 和流程引擎)不再直接对接各个原子平台,而是可以直接调用 API 网关,通过网关完成协议转换、权限校验等相关功能。

那时的架构也比较简单,如下图所示整体分为两大块:服务端和管理端。原子平台需要把自身服务注册到 API 网关上,首先得访问管理端,配置好平台的 API 资源地址和各自对应的权限等等。
当原子平台通过管理端提供了网关所需的配置细节后,数据会被写入到 MySQL 中,供服务端读取。之后,当 SaaS 请求某原子平台的 API 时,首先经过前端的 NGINX 负载均衡器,然后透到网关服务端,服务端读取了相关配置后,再由将请求通过路由转发功能传给后方的各原子平台,完成整套请求流程。为了提升性能,这套架构也引入了 Redis 处理了一些缓存加速等场景。

1.0 版本的架构在内部运行了几年后,随着请求量的增长和场景的复杂化,缺点开始逐渐显现出来。比如:
框架性能不佳。当时实现时选择了 Django 框架,它在高并发场景下表现一般,处理海量请求时性能捉襟见肘。
路由实现性能一般。API 路由采用的算法性能较低,影响到路由的匹配和转发速度。
DB 压力大。路由策略全部存储在 MySQL 中,规则多时需承载大量检索请求,查询压力大。
网络开销大。Redis 在多种场景下被高强度使用,导致网络开销太大。
为了解决上述问题,我们在 1.0 版本的基础上进行了迭代,设计实现了 2.0 版本。2.0 版本相比前代,最大的改动就是使用 Go 语言重新实现了网关的框架和转发层。因为 Go 相比 Python,在处理大并发请求的场景下会更有优势。

同时还进行了其他优化变动。比如在内存中维护了一个更高效的路由实现;在中间层引入了基于内存的缓存,以减少对 Redis 的依赖。新架构也增加了对网关多版本和多环境的生命周期管理,引入扩展插件机制,方便开发者通过插件对网关能力进行扩展。
总体来说,2.0 版本解决了 1.0 版本中遇到的性能问题和大部分痛点。但随着时间的推移,新的问题也开始慢慢浮出水面。
技术选型
进入云原生时代后,我们发现网关 2.0 版本在一些方面渐渐无法满足业务需求,主要的问题包括以下几点:
隔离性不足:无法实现真正的物理隔离;发布过程会导致长连接闪断。
协议支持单一:仅支持 HTTP,而实际场景中对非 HTTP 协议的需求在增多。
不支持动态路由规则:不支持按条件匹配的动态路由规则;对灰度发布场景不够友好;缺乏场景化组合封装的能力。
缺乏服务发现能力:缺乏自动服务发现能力,对微服务架构不友好。
结合实际业务场景来说,公司内部有很多业务系统都需要使用这个 API 网关,它们对网关的需求虽然大部分相似,但细微处其实又各有差异。假如把对网关的所有多样化需求都整合在同一套网关中,难度很大。因此,我们有了设计分布式网关的想法。即把一个大网关拆分成许多个微网关,利用这些微网关去平衡不同业务系统对网关的需求差异。

分布式网关架构的组件主要分为两类:管理端和微网关实例。管理端统一管控着各个微网关,由各网关的管理员对网关进行配置和管理。微网关实例是独立部署的各个网关服务,各自独立承担特定的某一组服务的访问流量,根据管理端的设定进行相关访问控制。所有微网关实例由同一套管理端管控。
在微网关的技术选型方面,我们当时参考了市面上比较流行的各网关开源产品,从流行度、技术栈、协议支持等各个层面对比后,最终选择了使用 APISIX 作为微网关最重要的后端技术。

之所以选择 APISIX,是因为它是基于 NGINX+Lua 实现的,所以整体性能相比 Go 而言是有优势的。同时 APISIX 的扩展性非常好,还支持通过多语言插件去扩展能力,在当时也有非常多的成熟的用户案例。
不过,APISIX 在当时对于我们的内部使用场景来说,还是存在一些缺不足的。比如它的控制面能力有些缺乏,控制面板比较简陋。因此在 APISIX 的基础上,我们按照内部需求去实现定制了 APISIX 的控制面。
基于 APISIX 的蓝鲸网关 3.0 迭代
云原生环境下,K8s 就是我们需要关注的最重要的基础组件。因为整个微网关都是面向云原生设计,所以 3.0 版本的网关就基于 K8s 去做了新的架构设计。其中最核心的部分,就是使用 K8s 提供的 CRD 自定义资源,实现了对网关的整套操作和扩展。

如上图所示,网关引入了一整套 K8s 的 CRD 资源定义,包括:BkGatewayStage(网关环境)、BkGatewayService(后端服务)等。通过这些 CRD,我们得以控制每个微网关实例的具体行为。
图中的几个“Operator”是这套架构中最核心的部分。上方是 Plugin Operators 服务,里面包含一系列的插件 Operator。比如负责服务发现的 Operator,会将后端服务注册在服务发现中心的地址写入到 K8s 集群中。
中间的核心 Operator 监听着所有和网关相关的 CRD 资源。其中的资源调和器(reconciler),负责将读取到的网关配置转换成 APISIX 微网关实例能理解的格式,从而实现微网关的全套生命周期管理。
目前,这套微网关主要分为两种部署类型:
共享网关:默认类型,平台统一部署,由平台统一生成与管理 API 访问地址;
专享网关:使用方自行部署“微网关”实例,接入平台后变为“专享网关”,需手动管理 API 访问地址,流量直接从“专享网关”流入后端服务。

从上图可以看到,管理端统一只有一套,因此它的能力并不提供任何差异化,像多环境管理、权限管理等功能都是所有网关共有。但在它所管理的不同类型的微网关实例中,所支持的特性集就会互有差异。
拿共享网关实例来说,它所支持的特性集是比较基础的。主要包含统一的登录鉴权、限流熔断和多协议支持等。而到了每个业务独立的专享网关实例,就可以拥有一些不一样的个性化能力。因为专享网关和业务同属一个集群,所以它可轻松实现动态路由、自定义服务发现等能力,也利用 APISIX 的强拓展性去自定义更多能力。
基于这套架构和模式,蓝鲸 API 网关 3.0 版本在 APISIX 能力的加持下,提供了更加丰富的功能。

基于 APISIX 的蓝鲸网关 3.0 实践场景
服务发现
服务发现是微服务架构所需的一个基本能力,在内部,我们主要通过自定义资源 CRD 去实现。一份有效的服务发现 YAML 定义如下图右侧代码所示。

将上述 CRD 资源写入 K8s 集群后,就会触发服务发现相关的控制器的相关动作。之后调和器(Reconciler)会捕获到对应的服务发现配置,创建服务发现相关的程序对象。
然后它会通过内置的服务发现接口(包含 Watcher、Lister)读取服务发现中心的相关地址信息,将获取到的地址通过 BkGatewayEndpoints 这个 CRD 资源重新写回到 K8s 集群内。再经由右侧的核心 Operator 进行一些复杂处理后,这些 endpoints 最终被同步到 APISIX 对应的上游中,一次完整的服务发现流程就此完成。
为了方便开发,我们实现了一个通用的服务发现框架。它提供了统一的开发接口和规范,使用它可以低成本地支持其他类型的服务发现场景。
统一认证授权
统一认证部分比较简单。在业务实践中,我们需要支持来自三种不同来源的请求,分别是浏览器、平台和个人用户。我们基于 APISIX 实现了一个认证插件,来实现统一认证。

具体实现流程如上图所示。请求进来后,插件会从 Header 中读取相关的凭据信息,然后统一调用 BK-Auth 认证服务去校验该凭据,并读取对应的 SaaS 信息。之后再用和后端约定好的私钥,签发一个 JWT token 并将其写入请求头中,最后写入 APISIX 变量。
除统一认证外,内部业务中也存在一些复杂的鉴权场景。其主要解决的问题是,当一个 SaaS 调用某原子平台的某个资源时,判断该 SaaS 有没有这个权限。目前统一的资源鉴权,也是通过 APISIX 插件实现,这里我们使用了 Go 语言实现,如下图所示。

当客户端的请求过来后,会首先经过认证环节拿到 SaaS 应用信息。然后经过 ext-plugin 处,基于 RPC 与鉴权插件进行交互。此时鉴权插件会直接去查询缓存中的鉴权相关数据(通过全量和增量机制由管理端同步),之后完成鉴权。
动态路由

比较典型的动态路由应用场景来自蓝鲸的容器管理平台。蓝鲸容器平台管理着非常多的 K8s 集群,有一些是业务的服务集群,有一些是业务的数据集群。
作为用户,常常需要请求这些集群的 apiserver 去完成一些事情。当用户请求进入到微网关时,网关需要根据请求路径,判断该将其转发到哪个集群的 apiserver 中。具体实现流程如下图所示。

请求进入后,动态路由插件首先提取出集群的 ID 信息,然后对路由进行重写,之后判断该集群是否为直连集群。
对于非直连集群,首先会生成一个 BCS 集群管理器上游,然后借其和 BCS Agent 进行交互,最后将请求传递给集群的 APIServer。
对于直连的集群,流程则类似于前文的统一鉴权插件,插件会定期同步一些与集群相关的基本信息。找到集群信息后生成相关上游,再通过 APISIX 插件去重定义连接逻辑,最后将请求发送到集群 APIServer。
客户端证书管理
在我们的业务场景中,有一类原子系统在将资源注册到网关时,使用的是比较复杂的客户端证书验证模式。因此,如果某个用户要请求它的资源,就必须提供一份有效的客户端证书。

具体实现如上图所示。对于网关管理者,首先要在管理端配置好网关在不同环境中使用的客户端证书。创建完成后,证书会被发布到对应的微网关所在的 K8s 集群中。这个过程会使用一些 CRD 资源和 K8s 官方的 Secret 资源,并由核心的 Operator 服务进行持续地调和处理,比如根据域名寻找对应证书等。有效的客户端证书配置,最终会体现在 APISIX Service 的相关配置中(如上图右上方代码红框处)。
总结
本文从蓝鲸 API 网关的诞生背景与迭代过程入手,为大家呈现了一个 API 网关产品的从 0 到 1 的探索过程。也带来了选择 APISIX 进行网关产品迭代的过程中,一些业务实践场景的细节分享。希望对于想要使用 APISIX 进行网关迭代的企业有一些建设性的启发。
版权声明: 本文为 InfoQ 作者【Apache APISIX 中文社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/9c0cd30aa35733504fda43a90】。文章转载请联系作者。
评论