vlambda博客
学习文章列表

从传统网关Nginx走向百花齐放的云原生网关

在互联网,网关主要指API网关,其主要作用是作为流量的入口,在负载均衡的基础上执行具有共性的操作(诸如鉴权,限流等操作)。

传统网关的困局

传统网关的事实标准是Nginx(Spring Cloud Gateway针对性适配于Spring体系),其优点我不再赘述,本文仅说说它的缺点:

  • 控制面能力弱(网关可分为控制面CP、数据面DP),仅提供静态文本配置;
  • 通过reload方式加载配置,会导致所有代理服务受到波动影响;
  • 路由功能薄弱,完成诸如灰度发布等场景具有难度;
  • 新协议支持慢(支持grpc、http2,但仅限于简单使用场景);
  • 不支持集群部署,只能由运维人员逐个手动部署,或者通过ansible同步配置+手动reload;
  • 缺少指标监控手段,现有监控手段多是基于access log进行统计,无法满足时效性的需求。

虽然市面上针对以上问题,存在较多的开源解决方案,但是其整合以及后续版本的维护都存在较多问题。此外,众多开源方案属于螺蛳壳里做道场,在功能、性能、动态各方面不得不做出妥协。

Openresty在Nginx的基础上,利用LuaJIT进行二次开发,大大的减轻了网关的定制化开发难度(感谢agentzh的伟大工作)。不过受限于Nginx的架构设计(Nginx第一个版本于2004年发布),Openresty开发仍然具有较高难度。因此,为了应对如今的需求(云原生、各类定制化需求),基于各种新方案的网关涌现出来。

此处列举下最近涌现的云原生网关:APISIX、BFE、Easegress。

云原生

云原生这个词虽然已经泛滥了,但是至今没有明确的定义。
借用APISIX主要开发者王院生的总结,云原生需要满足以下两点:

  • 支持通过Docker进行部署;
  • 支持弹性伸缩部署,便于进行横向拓展。

第一点实现难度不大,Nginx/Openresty均提供现成的Dockerfile,只需要针对实际部署环境进行微调即可。但是第二点支持弹性伸缩部署,这就隐性要求网关需要支持集群部署。Nginx/Openresty仅支持静态文件配置;基于Openresty的Kong采用PostgreSQL作为配置数据库,难以进行横向拓展。

上述介绍的三个网关,均满足要求。

  • BFE现阶段仅支持配置文件(后续如何不清楚),不过百度内部是搭配BGW(未开源的四层负载均衡)使用,应该有集群部署措施;
  • APISIX以及Easegress均基于Etcd完成集群部署。

网络协议栈

四层协议(TCP/UDP)此处略去不谈,仅关注七层网络协议栈。

  • APISIX基于Nginx的网络协议栈进行二次开发,其稳定性毋庸置疑;
  • BFE基于Golang的网络协议栈进行定制化开发(涉及iobuf,以及生命周期改善等);
  • Easegress基于Golang的网络协议栈,现阶段未进行定制化开发。

APISIX在Nginx已有的网络协议栈上进行开发,现阶段满足使用要求(已填补Grpc相关生态),但是对于后续的http3支持,需要依赖于活跃的社区。

BFE以及Easegress均依赖于Golang的网络协议栈,现有的网络协议,基本都由Google推动,并且相关协议变动均能快速反馈至Golang;此外,Golang使用者广泛,存在的Bug或性能缺陷均能得到快速修正。

插件编排

Nginx可以编写插件,但是其插件开发具有难度,需要对Nginx的架构以及请求生命周期具有深刻的理解,具有很强的C语言开发能力,此门槛无疑劝退了很多开源作。agentzh利用Lua作为胶水语言,简化了插件的开发难度,然而其开发受限于Nginx暴露的变量或接口,在某些需求实现上,不免仍需要使用C语言进行开发。

插件编排,是新一代网关从架构设计之初,就重点关注的地方。官方大多提供了基础插件,例如限流、负载均衡、控制接入等插件,插件支持按照特定顺序进行组合,按照pipeline形式进行业务执行。若是有定制化需求,可以自行编写插件嵌入代理流程中。

不过各家采用的插件编排方式存在不同,具体如下所示:

  • BFE:BFE支持配置/插件的加载,用户通过访问BFE对外监控端口的特定URL,触发具体配置或者插件的动态加载;因为BFE在Golang原生net/http库上进行了高度定制化操作,划分了具体生命周期,因此其插件可针对生命周期的具体阶段完成细粒度的操作;
  • APISIX:APISIX支持多语言插件(Lua的语言表达力终究是比较孱弱),其插件管理工具Plugin runner作为子进程运行;用户编写的插件与APISIX部署在同一实例上,通过unix socket向plugin runner执行RPC调用,
  • Easegress:Easegress采用WebAssembly来接入多语言插件(现阶段主流语言均提供WebAssembly库),WebAssembly结构小巧,贴近原生语言性能,在网关中调用以及释放影响较小;

在上述三种网关中,BFE插件稳定,可定制化程度高,不过没有多语言接入能力,用户必须熟练使用Golang开发插件;APISIX的plugin runner现阶段仍在开发过程中,功能尚未稳定,插件的管理以及部署有待验证(Nginx worker之间的配置同步比较麻烦);Easegress的WebAssembly构想比较美丽,但是对于开发者仍存在学习负担(Easegress提供了各语言的开发sdk,以降低开发者的学习成本),不过仍有很多地方需要探索(例如WebAssembly无法识别业务是运行正常还是处于异常状态,只能通过超时机制完成WebAssembly资源的释放)。

集群支持-Etcd

Etcd是基于Raft协议的分布式KV数据库(提供线性一致性),其在Kubernets系统中被大规模使用,产品性能以及稳定性均得到了保证。

APISIX以及Easegress,均基于Etcd构建网关集群。为什么不参照Kong采用postgres作为集群的配置中心呢?我认为原因如下:

(1)网关配置信息灵活多变,如果基于传统的关系型数据库存储配置信息,数据表格式确定/维护/变更均需要消耗较多精力;
(2)搭建网管集群的目的即为了避免单点性能或安全故障,而关系型数据库集群的搭建比较复杂,且性能消耗颇为可观;
(3)消息推送机制,传统的关系型数据库不具备配置信息推送机制,例如Openresty需要启动定时器去不间断轮询配置,然而拉取频率/配置解析/配置拉取粒度均要纳入设计考虑;

基于以上几点,似乎只能选择Etcd了:

(1)配置信息直接使用Json格式,抑或是YAML格式均可以,序列化之后直接往Etcd里丢即可;(2)无论是Embed Etcd Server,或者单独部署Etcd Cluster,部署方式简单,性能需求较小(Etcd作为KV数据库,较关系型数据库小巧很多);(3)Etcd提供Watcher接口,当配置信息变更时,接入Etcd cluster的client Watcher即会接收配置变更的信息;

Etcd在网关集群里不仅仅扮演存储的角色,也扮演消息队列的角色(基于推模式的消息队列)。在Kubernets中将消息抽象为k8s event,通过Etcd完成生产者和消费者的解耦。虽然Etcd的CP属性限制了其在现网环境下的使用,但是在局域网内可以利用这点完成架构的简化。

Easegress的embed etcd cluster搭建,与开发人员沟通后得知其需要先启动一个节点作为leader,等待后续节点加入集群。这与Raft协议的leader election理念存在偏差(不过影响不大,建立好的集群可以正常完成leader election),其开发团队计划后续改进。

四层代理的支持

四层代理,相对于七层代理(http/https/http2等),难以在功能点上做出花来。

因此,例如BFE、Easegress均未提供四层流量代理,仅有ApiSix提供了四层代理。

  • BFE定位是七层负载均衡系统,在百度内部有配套的四层负载均衡系统BGW(未开源);
  • Easegress有四层流量代理规划,限于现在开发力量,估计要等等;
  • APISIX的四层代理,沿用Nginx的四层流量代理(暂未看出有新的功能点)。

正在编写Easegress的四层代理功能准备提交,但是可能跳票。

通过常规软件实现四层代理,是比较尴尬的,主要原因有以下几点:

  • 四层流量基本基于二进制格式(而非七层的文本格式),常规协议解析/维护难度高(诸如MySQL/DNS等),私有协议直接GG;
  • 性能不够强劲,现有的网络架构无法绕开内核导致的性能开销,因此爱奇艺的DPVS以及百度的BGW均基于DPDK以提高代理性能。

最近几年一直流行kernal bypass,内核在高性能网络编程方面饱受诟病,后续io_uring若能完善起来,四层代理的需求重要性也许会被重新评估,拭目以待。