serverless在微店node领域的探索应用

背景

目前微店中台团队为了知足公司大部分产品、运营以及部分后端开发人员的尝鲜和试错的需求,提供了一套基于图形化搭建的服务端接口交付方案,利用该方案及提供的系统可生成一副包含运行时环境定义可当即运行的工程代码,最后,经过 “某种serverless平台” 实现生成后代码的部署、CI、运行、反向代理、进程守、日志上报、进程分组扩容等功能。前端

这样,产品和运营人员可基于此种方式搭建的接口配合经常使用的cms系统实现简单查询需求如活动大促的自主“研发”上线,代码的可靠性、稳定性由中台研发侧提供的“某种serverless平台”保障,有效支撑了多个业务快速上线,节省后端开发人员的人力与硬件资源的开销(大多数需求下,nodejs业务对虚拟机的资源开销小于java业务)。java

接口搭建系统

此处并不讲解接口搭建系统的原理与实现,只说明其与上文提到的 “某种serverless平台” 的关系。node

![enter image description here](https://user-gold-cdn.xitu.io/2019/8/20/16cacb3d159561c2?w=1798&h=1012&f=png&s=127439)

这是系统的全貌,部分细节因为敏感信息而省略。平台使用方可基于每一个功能组件搭建出一套复杂的业务流,在搭建阶段,提供在线debug和日志功能,可用于排错;在部署CI阶段,可集成不一样的运行时平台,既能够是自主实现的运行时,也但是第三方云平台;在运行阶段,经过使用agentool工具实时监控当前服务的性能,并可经过traceId一览请求在各系统的全貌。git

serverless方案

本节以资源隔离粒度为度量,介绍了我对三种serverless方案的取舍以及最终为什么选择了隔离程度更高的kubeless云平台。github

基于函数隔离的Parse Server方案

Parse Server提供了基础功能:基于类与对象的权限控制、基于document的nosql存储、简易的用户身份认证、基于hook的自定义逻辑等,但通过笔者的调查与论证,最终并无采用相似单进程下基于函数隔离的Parse Server及相似方案,这有几点缘由:sql

  1. Parse Server方案很重,额外引入了很是多不须要的功能,如权限控制、认证、存储等
  2. 服务隔离级别低,多个服务在一个进程运行,多个服务会在libuv层互相抢占CPU,互相影响对方的业务处理
  3. 水平扩容难度大,针对单个服务的扩容没法作到
  4. 底层基于express框架,没法知足运行时接口调用链路的trace追踪
  5. 当多个服务同时引入不一样的资源如db、es或者服务建立的对象足够多时,会存在Parse Server主进程溢出的风险,毕竟64位机的node堆内存是有1.4GB上限的,尽管这个上限是可配置的
  6. Parser Server发布的接口需经过其client调用,这在公司商用状况下须要作许多额外的配置才能知足

Parse Server官网docker

基于进程隔离的super-agent方案

为了解决多个服务抢占libuv的问题,我选择了自主研发的 super-agent方案,经过其名称即可知它是一个超级代理,但它不只是代理,仍是一个具备极简功能且可靠的发布系统和运行时容器;它是一个分布式应用,节点间功能划分明确;它同时提供实时调试功能。express

super-agent是一个多角色分布式系统,它便可以看作node容器,也可当作serverless的node实现,它以进程为粒度管理服务。它分为“协调者”和“参与者”,协调者实现 应用CI部署、启动、进程维护与管理和反向代理功能,参与者实现 业务请求代理、接受协调者调度后端

enter image description here
在super-agent架构中,端口是区分服务的惟一标识,端口对客户端而言是透明的,这层端口资源的隔离由super-agent来作掉,所以多个服务可避免在libuv层的互相竞争,提供水平扩容的可能性。

反向代理

super-agent最核心的功能在于反向代理。因为每一个服务都被包装成有单独端口的独立HTTP应用,所以当用户请求流量通过前端转发层后进入super-agent,由其根据相关规则作二次转发,目前支持基于 “路径、端口”规则的转发。安全

部署

后端应用部署须要进行 “优雅降级、流量摘除、健康检查、应用初始化完毕检查、流量导入、全部参与节点的部署状态查询” 等步骤,须要妥善处理;同时,协调者负责协调众多参与节点所有完成部署操做,这是一个分布式事务,须要作好事务出错后的相关业务补偿。

关于流量

上图并未画出节点角色的区别,实际上只有参与者节点才真正接受用户请求。

协调者流量所有来自于内部系统,包括接受 “接口搭建系统”调用或者其余系统实现的dashboard服务;同时其会向参与者发送相关信令信息,如部署、扩容、下线、debug等。

参与者流量来自于内部系统和外部流量,其中大部分来自于外部流量。内部流量则承载协调者的信令信息。

水平扩容

服务的水平扩容重要性不言而喻。super-agent方案中,协调者负责管理服务的扩容与逻辑分组。

此处的服务是经过服务搭建平台经过拖拽生成的nodejs代码,它是一个包含复杂业务逻辑的函数,能够是多文件。具体的,super-agent经过将该服务包装成一个HTTP服务在单独的进程中执行。所以,若是要对服务进行水平扩容,提供多种策略的选择:

  1. 默认每台虚拟机或物理机一个服务进程,整体上N个机器N个服务进程
  2. 扩容时默认每台机器再fork一个服务进程,N机器2*N个服务进程
  3. 为了更充分利用资源,可为每台机器划分为逻辑组,同时选择在某几个组的机器单独扩容

这些策略对下游应用透明,只需选择相关策略便可。

水平扩容的缺点:每台虚拟机或物理机资源有上限,一个正常的node进程最多消耗1.4GB内存,计算资源共享,在一台8C16G的虚拟机上,最多可同时运行16个服务。及时经过分组扩容的方式,每当扩展新的虚拟机或物理机,都须要由super-agent根据分组信息实现进程守护,同时每次服务CI部署也一样如此。运维管理上须要配合很是清晰明了的dashboard后台才能快速定位问题,这点在多服务的问题上尤为突出。

在线调试

super-agent提供消息机制,由搭建平台中组件开发人员使用提供的serverless-toolkit工具debug相关逻辑,最终可在super-agent的协调者后台查看实时debug结果。

总结

super-agent是符合常规的基于业务进程隔离的解决方案,它有效的支撑了微店的几个活动及产品,虽然峰值QPS不高(100左右),但它也论证了super-agent的稳定性及可靠性(线上无事故,服务无重启,平稳升级)。

可是,super-agent仍然存在几个问题,它让咱们不得不另觅他法:

  1. 平常运维困难,须要开发一系列后台系统辅助运维,这须要很多人力成本
  2. 这是一个典型的一机多应用场景,当部署super-agent时会对运行其上的服务有所影响(重启),尽管这个影响并不影响用户访问(此时流量已摘除),但仍然是个风险点
  3. 水平扩容实现繁琐,当下掉某几台参与节点时会带来很多影响

基于内核namespace隔离的kubeless方案

基于kubeless的方案则是隔离最为完全的解决方法,kubeless是创建在K8s之上的serverless框架,所以它能够利用K8s实现一些很是有用的特性:

  1. 敏捷构建 - 可以基于用户提交的源码迅速构建可执行的函数,简化部署流程;
  2. 灵活触发 - 可以方便地基于各种事件触发函数的执行,并能方便快捷地集成新的事件源;
  3. 自动伸缩 - 可以根据业务需求,自动完成扩容缩容,无须人工干预。

其中,自动伸缩则解决了 super-agent 的痛点。

kubeless中与咱们紧密相关的有两个概念:“function和runtime” ,function是一个统称,它包括运行时代码、依赖以及其余配置文件;runtime则定义运行时依赖的环境,是一个docker镜像。

若要接入kubeless平台,咱们须要解决以下几个问题:

  1. 开发自定义运行时,知足商用需求,如trace、日志分片、上报采集
  2. 自定义构建镜像,实现ts编译
  3. 基于yaml的function建立、更新、删除流程探索,并自动化
  4. function部署,包括流量摘除、流量导入、业务健康检查
  5. 中间件日志、业务日志、trace日志隔离与挂载
  6. function运行时用户权限问题
  7. 水平扩容探索与尝试
  8. 资源申请规范指定与部署规范约定

所以,前进的道路仍然很曲折,并且不少需求须要本身从源码上去寻找解决方法。

一些说明

kubeless实现的serverless体系中,function所在pod中的全部容器共享网络和存储namespace,可是默认外网是不可访问k8s集群的全部pods,所以须要经过一层代理实现请求的转发,这就是“Service”。Service负责服务发现及转发(iptables四层),所以在Kubeless或者K8s中不会直接经过pod IP来访问服务,而是经过Service转发四层流量完成。Service有K8s分配的cluserIp,clusterIp是集群内部虚拟IP,没法被外部寻址,而是经过Kube-Proxy在容器网络之上又抽象了一层虚拟网络,Kube-Proxy负责Service的路由与转发(关于kube-proxy细节,请看参考资料)。

Service后端对应是一个或多个pods,这些pods中的一个容器则运行相同的业务代码。那么流量是如何路由至Service上来呢?这就涉及到Service的“发布”,经常使用的是Ingress。Ingress包括HTTP代理服务器和ingress controller,代理服务器负责将请求按照规则路由至对应Service,这层须要Kube-Proxy实现虚拟网络的路由;ingress controller负责从K8s API拉取最新的HTTP匹配规则。

![enter image description here](https://user-gold-cdn.xitu.io/2019/8/20/16cacb3d1682ae1c?w=1390&h=1074&f=png&s=132284)

问题解决

  1. 自定义镜像:这里的镜像包括两部分:构建镜像和运行时镜像。运行时镜像须要解决宿主代码的鲁棒性,以及提供 livenessProbe、readinessProbe、metric接口实现;构建镜像则负责构建阶段的操做,如编译、依赖安装、环境变量注入等操做。具体编写可参考 implement runtime images
  2. 构建镜像参考1
  3. 关于function的CRUD操做,笔者先经过命令行走通整个流程后,又切换成基于yaml的配置文件启动,使用yaml启动好处在于:a,可以使用kubeless自带的流量导入与摘除策略 b,水平扩容简单 c,命令简单 d,配置文件模板化,自动化
  4. 部署策略因为涉及到业务特色,此处不详细介绍
  5. 日志的挂载是必要的,不然pod一旦重启,容器内的全部日志所有丢失。尽管会存在日志收集的操做,但是日志收集进程大多数都是异步进行,所以会存在丢失日志的状况。所以,必须经过挂载volumn的形式在K8s node上映射文件。但在这过程当中会出现权限的问题,这在下一点说明
  6. 权限问题在于kubeless将function的执行权限设置为非root。这是安全且符合常理的设定,但有时function须要root权限,这须要修改K8s的security context配置,须要谨慎处理
  7. 水平扩容基于K8s的HPA组件完成,目前支持基于CPU和QPS等指标进行扩容,目前笔者并未专门测试这项内容,由于它足够可靠
  8. 资源申请的指定须要符合每一个公司的实际状况以及业务特色,以node技术栈为例,pod中每一个容器设置1C2GB的内存符合实际状况;而至于部署规范,则要兼顾运行时容器的特色,合理配置K8s的node与pod、function的对应关系

总结

运行在kubeless 中的函数和运行在super-agent的代码没有什么不一样,但是周边的环境准备可大大不一样。为了让kubeless中的function能够接入公司内部中间件服务,笔者费了很多功夫,主要集中在日志及收集部分。好在事在人为,解决的办法老是多于失败的方法。

进度

目前,super-agent方案已承载了10+个线上应用或活动,稳定运行4个月,资源使用率符合预期;

kubeless方案还未正式接入流量,等待进一步作相关异常测试。

参考

kubeless介绍

security-context

kube-proxy

相关文章
相关标签/搜索