云计算时代,Serverless 作为云原生重要技术组成部分,一开始便承载了太多的使命 —— 承诺了云计算时代最典型并极具挑战的多维度服务指标: 无服务运维、极速弹性伸缩、按量付费等。 这些极具挑战并富有吸引力的服务指标带给了行业极大的想象空间,但任何技术红利的普及并不是一蹴而就,或者像宣称的那样美好。Serverless 在阿里巴巴落地的时候,同时面临了两类诉求不同的用户:
第一类是积极拥抱 Serverless 愿意将其代码修改成 FaaS 形态的用户,这类用户以 BFF(Backend For Frontend)层为典型,这类用户的代码天生具备良好的轻量、无状态等特性。
第二类则是存量应用,存量应用作为目前商业系统的核心组成部分,是最需要享受 Serverless 服务红利,但也是最难享受到这些服务红利的群体, 面对 Serverless 严格的无状态要求,以及客户对系统稳定性、业务正确性的担忧,存量应用的改造代码设计的难度和成本太高,因此他们希望能够在不更改代码结构的前提下享受 Serverless 红利。
Book 模式会按照应用的峰值进行部署,且业务 Owner 在进行容量评估时往往会留有余地,导致资源池利用率低。CSE 采用 Serverless 按需模式进行部署,通过快速弹缩、分时复用和高密度部署来大幅度降低成本。
假设业务单次请求的 SLA 在 50ms,如果业务启动时间能做到 50ms 以下,就可以等待请求到来时再按需进行扩容,反之则只能根据水位进行 Book。启动时间越短,扩容时机可推迟到越晚,缩容时机可提前至越早,成本越低。
但现实情况是,大多数在线业务的启动时间都在分钟级,甚至有的达到 30 分钟。在线业务一般基于HSF 和潘多拉框架进行研发,同时会依赖大量的中间件富客户端以及其它部门提供的二方包,启动过程中包含了大量的初始化工作,比如网络连接、缓存加载、配置加载等。
因在线业务的启动瓶颈在于业务自身的代码,像 AWS Lambda、函数计算等 Function 场景对 IaaS或 Runtime 框架的弹性优化手段,在在线业务的场景下基本没有用武之地。在无法侵入业务代码的前提下,如何将业务启动时间从分钟级优化至秒级甚至毫秒级是 CSE 研发阶段重点攻坚的方向。
Zizz 方案基于一个核心和一个假设:
核心是热备(Standby):既然应用的冷启动时间如此长且很难优化,那么提前启动一批实例进行Standby,在流量峰值临时进行上线接流,在流量低谷时进行离线处理(服务发现层面的离线);
假设是离线的实例功耗非常低:如果热备的实例需要占据全部的物理资源,那么与 Book 模式并没有本质的区别。但理论上,处于离线状态的实例没有前端流量的驱动,应该仅存在少量的后台任务在运行。所以对于 Standby 的实例,可以将其换入一个低功耗的状态,将 CPU 和内存的规格降至很低,以极低的成本进行 Standby,为分时复用和高密度部署带来更高的灵活性。
基于这两点,CSE Zizz 研发了几种核心技术:
一种基于弹性堆的低功耗技术;
基于内核态和用户态的 Swap 能力的内存弹性技术;
基于 Inplace Update 的实例规格动态升降配技术。
在嵌入式领域, 低功耗运行模式被广泛接受, 包括 OS 设计和应用设计。 针对 OS 在设计阶段都会考虑到系统低功耗运行的情况,而对于应用,也会在设计和开发阶段考虑到低功耗运行的情况,通常应用会注册系统低功耗运行事件,在应用收到系统的低功耗运行事件之后,会进行一系列逻辑处理使自己进入低功耗运行状态。 基于这样的启发,在未来云计算环境下,应用的运行模式应该也需要支持类似的设计,这也是在 CSE Zizz 方案中引入应用低功耗运行时的概念,从而使整个 Zizz 方案更好地适配Serverless 场景需求。 Zizz 低功耗运行时核心技术主要包括下面三个部分。
CPU 低功耗
按照 Zizz 架构设计,进入低功耗模式,我们会对实例的资源配置进行降级; 针对 CPU 资源,我们维护一个低功耗 CPU Pool(CPU Set),CPU Pool 初始值是 1C,会随着加入这个 CPU Pool 中低功耗实例的数量,增加其 CPU Core 的数量,这样同一个 Node 上的多个低功耗实例争抢一个 CPUPool 中的资源。从 CPU 资源利用角度,多个低功耗实例构成了一个分时复用 CPU 资源的低功耗Serverless 场景。
内存低功耗
经过探索和论证,我们认为 Linux Swap 技术能够完成这样的使命。Linux Swap 向系统提供了一种透明的与进程运行时无关的内存扩展能力:根据系统当前内存压力,将系统的匿名内存按照 LRU 访问活跃度从低到高依次换出到外部低速存储介质,从而达到扩展系统可用内存的目的;在具体换出过程中,系统会根据 Swap 子系统的其它设置,根据当前内存 Anonymous 和 Filecache 的占比,将不活跃的 Anonymous 内存 Page 换出到外部低速存储设备上;从系统内存的角度来看,Swap 是一个扩展低速内存 Pool;从成本的角度,Swap 提供了一种低成本的进程运行时上下文; 面向未来,我们希望提供一种不限于内存和磁盘的多级存储架构,承载实例低功耗运行需要的上下文。
结合 Serverless 弹性场景,要求运行实例能够快速进入和退出低功耗模式,要求能够对实例的运行时状态根据流量完全可控,而目前 Linux swap 设计和实现更多的是面向系统内存 Overcommit,其内存换入的实现更多偏向于 Lazy 方式基于 Page 粒度的内存访问形式,这种内存换入的方式在真实的业务系统中会造成 RT 抖动,在商业系统中是不可接受的。这要求我们必须基于目前的 Linux Swap 技术提供一套用户态完全可控的 Swap 换入换出实现。
考虑到未来大规模使用场景, 以及目前 Linux Kernel Swap 存在的一些问题,我们开发了 User-space swap, 实现了 Per Process Swap 隔离存储,基于此,能够实现大粒度快速 Swap In,性能对齐不同存储介质顺序读性能。
Elastic Heap
考虑到目前阿里巴巴内部普遍以 Java 技术栈为基础的运行时现状,存量应用尤为如此。针对 Java 应用,CSE Zizz 方案利用 AJDK Elastic Heap 技术,使其作为 Java 应用实例低功耗运行的基础;Elastic Heap 技术能够有效避免 Full GC 对 JVM Heap 区域及关联内存区域的大面积访问,只对局部 Heap 区域的快速内存回收实现了低功耗运行,只使用相对较小的 Working Set Size 内存,避免了传统 Java 运行时堆区内存快速扩张对 Swap 子系统造成的压力,有效减轻了系统 IO 负载。
在 Zizz 研发过程中,考虑到未来云计算的场景,我们做出了一个极具挑战的决策:抛弃传统的本地数据盘, Zizz 方案 Swap 存储全面使用阿里云 ESSD 存储服务。
IO 层面,目前计划和 CPU 一样,希望能够利用 Cgroup 为多个低功耗运行实例构造一个抢占式动态的IO 复用 Pool, 这样既能从系统层面控制低功耗实例 IO 请求对系统 IO 的影响,同时能最大限度的动态利用分配给每一个低功耗实例的 IO。目前实现并未涉及 Zizz IO 低功耗特殊实现,作为 Zizz IO 低功耗下一阶段的工作。
CSE Fn�架构里的所有组件(triggers, functions and proxies)相互间的网络通信经过处于枢纽位置的 Broker。
Broker 负责服务发现(Service discovery)、路由(Routing)、负载均衡(LB)、追踪(Tracing)、流量调度(Traffic shifting)、流控(Circuit breaker)等应用基础设施服务;
rigger 组件(如 HSF gateway)负责接收外部请求产生事件,将网络协议转化成 RSocket,再转发给 Broker;
Proxy 组件(如 Tair Proxy)负责代理中间件的服务,将 RSocket 协议翻译成中间件原始的协议;
用户编写的每一个函数(如 Fn1.v1)都运行在独立的容器内。它们被来自 Trigger 的事件触发,并可调用 Proxy 来使用中间件的服务。
早期 Serverless 自动弹性只要做到用户体感上的这种体验(基于流量的自适应能力)就可以了,在底层基础设施能够保证用户所需要资源的基础上(当然底层也是需要提供足够的稳定性),多数用户是很高兴不再关心自己底下到底部署了多少这个服务器的,作为底层的基础设施,在用户的低流量期,“偷偷”保留好一个用户实例,以备用户不时之需使用即可。
随着技术进步以及工程师们追求的极致信念,价值的体现某种程度上也是在已有能力的基础上改进和构建出更为先进和合理的基础设施。另一方面,随着按需付费基础需求的不断涌现,真正闲时全数回收资源的能力更突显其重要性。
基于 CSE Fn� 自身在阿里巴巴落地的广泛场景和大量用户这一基础,CSE 团队很快就将� CSE Fn�的零实例能力提到了比较高的优先级。以下为结合�CSE Fn�自身架构的零实例架构:
从上面描述的扩缩过程中,我们可以看到这里结合了 ginkgo 自身架构特点几个核心组件的作用:
1.ginkgo-broker:CSE Fn� 的调用流量入口,具备双重身份,既承担平时的指标收集职责,也肩负实例缩零后的流量承接。广义上说,这两个职责都是流量收集的范畴(只是处在一个 Fn 生命周期的不同阶段);
2.cse-xscaler:CSE 弹性的核心决策组件,基于 metrics 计算 Fn 需要的实例数量,结合缩零静默期等配置,完成优雅缩零和实例拉起的重任。
为了更好地解决预发资源的利用率问题,充分发挥预发公共资源池的错峰利用作用,开启 CSE 的零实例能力是一个非常不错的选择。为了降低预发用户的缩零抖动波动,目前,CSE 的预发环境设置了默认6 小时的缩零静默期。这样在用户不再使用 Fn 的 6 小时之后,系统会自动全数回收 Fn 占据的物理资源,为其他玩家空出场地继续玩耍。
Docker 使得容器镜像成为软件及运行环境交付的事实标准,从而使 docker 容器成为一种事实标准的、轻量的、可以细粒度限制资源的沙箱。轻量的沙箱就意味着 docker 容器天生适合少量进程,那么在独占一个 pid namespace 的容器中,1 号进程自然就是 Dockerfile 或者启动容器的命令行中指定的进程。 然而,当年第一批吃螃蟹尝 docker 的人很快发现,没有 init system 的 docker 容器会带来两个令人无法忽视的问题:
僵尸进程
优雅退出(SIGTERM)
解决这两个问题的方法有很多,包括让容器内只有一个进程;容器内的 1 号进程负责 wait 所有子进程以避免出现僵尸进程,同时负责转发 SIGTERM 信号使得子进程都能够优雅退出;或者让 docker 支持 init system 等等。
问题原因搞清楚了之后,解决思路也有很多,但我们很自然地选择了最云原生的那个:剥离运维组件到sidecar 容器,利用容器的资源隔离能力来保证业务资源不被争抢。
在剥离运维 sidecar 的过程中,我们解决了如下问题:
1.资源隔离及 QoS:给 sidecar 分配合适的资源并保证与业务容器资源隔离;
2.CMDB 集成:将原本业务容器内汇报 cmdb 数据的逻辑剥离到 sidecar;
3.系统监控:将原本基于系统 metrics 日志的监控转换到 K8s 的 Node 上报采集;
4.日志采集:剥离日志采集 agent 到 sidecar,打通业务容器和 sidecar 的日志文件共享;
5.Web Terminal:将 Web Terminal 功能剥离到 sidecar。
在 Serverless 场景下,为了让实例根据负载变化自动扩缩,就需要应用尽可能快速地启动。影响应用启动速度的因素包括但不限于以下几点:
镜像分发
容器启动
进程启动
容器轻量化显然是云原生化过程中绕不开的一步,除了能够实实在在地提升应用启动速度以外,还有很多额外的好处:
解耦业务应用与运维组件
更好的资源隔离
更小的镜像(层少文件少)
镜像复用 运维镜像因为能跨多个应用复用而不必重复分发
更快的分发速度(体积小) 应用镜像因体积减小而能够显著提升分发效率
更快的启动速度(进程少)镜像瘦身能够提高容器启动速度
更简单可靠的运维管理(readiness/liveness probe,迁移等)
更高的集群整体资源利用率(容器资源需求小)
容器轻量化只有从应用开发侧提供轻量的镜像才能够实际应用,而应用侧在基础设施没有 ready 的情况下是不可能有任何变化的,但应用侧不去改变的话,基础设施就必须按照既有方式继续支持和适配,这看起来像是个死循环。CSE 努力推动应用侧往更轻量的镜像去发展,同时推动底层基础设施不断演进,继而收获容器轻量化的价值。
自 2017 年第一批小程序上线以来,越来越多的移动端应用以小程序的形式呈现。小程序拥有触手可及、用完即走的优点,这大大减少了用户的使用负担,使小程序得到了广泛的传播。在阿里巴巴小程序也被广泛地应用在淘宝/支付宝/钉钉/高德等平台上,例如今年 双11 大家在淘宝/天猫上参加的活动,大部分都是通过小程序提供的。
一个小程序可以分为客户端和服务端:客户端包括界面的展示和交互逻辑,服务端则包括数据的处理和分析。为了支撑大量的小程序,平台在服务端面临的挑战有:
1.大量的小程序是不活跃的,传统的至少一台服务器的方式会造成资源浪费
2.在活动高峰期小程序的调用量激增,要求服务端能够快速进行弹性伸缩
针对小程序场景,阿里云提供了完整的小程序解决方案:小程序云。资源的有效利用和弹性伸缩,是小程序云提供的核心能力之一,而这背后依托的,就是阿里云函数计算服务。函数计算是一个全托管Serverless 计算服务,让开发者无需管理服务器等基础设施,只需编写和上传代码,就能够构建可靠、弹性、安全的服务。下面就以 双11 小程序场景为例,解析函数计算在弹性伸缩上的核心技术。
1.用户在手机淘宝点击店铺活动,就进入了小程序。界面及交互由小程序客户端提供
2.在用户参与活动过程中,需要向服务端请求或者发送数据时,由客户端发起函数调用
3.函数调用先经过淘宝接入网关,进行必要的鉴权认证,然后调用到小程序云
4.用户的函数代码执行在小程序云中,用户可以实现自定义的业务逻辑。利用小程序云提供的丰富的扩展能力,用户可以方便地构建完整的电商应用
数据存储:存储结构化的数据
文件存储:存储文本/图片/视频等文件
电商服务:获取用户信息/创建支付交易
统计分析:自动统计小程序的使用信息及用户分析,支撑商业决策
可以看到,函数是整个小程序的业务逻辑的核心,它将云端的基础能力组合串联起来,对客户端提供服务能力。如果函数能力成为瓶颈,将影响整个小程序的运行。在这样的架构下,要支撑大量的小程序,需要函数能够做到:一是随时在线以支持小程序即开即用,二是弹性伸缩以应对小程序访问突增。为了做到以上两点,让我们看一下函数计算的技术架构:
其中几个核心组件的功能如下:
1.API 服务:函数计算的网关,实现鉴权/流控等功能
2.资源调度:为函数调用分配管理计算资源,负责调度效率和性能
3.函数执行引擎:执行函数代码的环境,做到安全和隔离
对于一些“秒杀”的场景,要求瞬间提供大量的计算资源。此时靠实时的弹性伸缩是不够的:一是冷启动的时间即使是 200ms,对于秒杀场景也太慢了;二是底层的计算资源在扩容时也会有流控。针对这种场景,函数计算提供了预留实例的功能。使用预留实例,用户可以为一些可预测的活动提前预留好资源,彻底消除冷启动。
和传统的基于服务器的做法不同,用户不需要按峰值来预留资源,而是可以结合预留实例和按量实例的混合模式:请求先被预留实例处理,当预留实例用满时,会自动弹性伸缩出更多的按量实例来处理请求。由于有一定的资源基础,结合调度优化,按量实例的冷启动所产生的影响就被大大减小了。这就是利用函数计算的弹性伸缩能力,在性能和成本之间达到很好的平衡。
不同于“监控”,监控更加注重问题的发现与预警,而“可观测性”的终极目标是为一个复杂分布式系统所发生的一切给出合理解释。监控更注重软件交付过程中以及交付后(Day 1 & Day 2),也就是我们常说的“事中与事后”,而“可观测性”则要为全研发与运维的生命周期负责。
回到“可观测性”本身,依旧是由老生常谈的“链路(Tracing)”、“指标(Metric)”和“日志(Log-ging)”构成,单独拉出来看都是非常成熟的技术领域。只不过这三样东西与云基础设施的如何整合?它们之间如何更好地关联、融合在一起?以及他们如何更好地和云时代的在线业务做结合?是我们团队这一两年来努力探索的方向。
团队对集团内的历年故障做了一次仔细梳理,集团内的核心应用通常有四类故障(非业务自身逻辑问题),资源类、流量类、时延类、错误类,再往下细分:
1.资源类:� 比如 cpu、load、mem、线程数、连接池;
2.流量类:业务流量跌零 OR 不正常大幅度上涨下跌,中间件流量如消息提供的服务跌零等;
3.时延类:系统提供的服务 OR 系统依赖的服务,时延突然大幅度飙高了,基本都是系统有问题的前兆;
4.错误类:服务返回的错误的总数量,系统提供服务 OR 依赖服务的成功率。
有了上面这些故障分类作为抓手后,后面要做的就是“顺藤摸瓜”,可惜随着业务的复杂性,这根“藤”也来越长,以时延突增这个故障为例,这背后就隐藏着很多可能的根因,可能的原因多种多样:有可能是上游业务促销导致请求量突增导致,有可能是应用自身频繁 GC 导致应用整体变慢,也有可能是下游数据库负载过大导致响应变慢导致,还有数不胜数的其他各种原因。鹰眼以前仅仅提供了这些指标信息,维护人员光看单条调用链数据,鼠标就要滚上好几番才能看完一条完整的 tracing 数据,更别说跨多个系统之间来回切换排查问题,效率也就无从谈起。
提起智能化,很多人第一反应是把算法关联在一起,把算法过度妖魔化。其实了解机器学习的同学应该都知道:数据质量排第一,模型排第二,最后才是算法。数据采集的可靠性、完整性与领域模型建模才是核心竞争力。只有把数据化这条路走准确后,才有可能走智能化。
故障定位智能化的演进路线也是按照上面的思路来逐步完成的,但在这之前我们先得保障数据的质量:得益于鹰眼团队在大数据处理上深耕多年,数据的可靠性已经能得到非常高质量的保障,否则出现故障还得先怀疑是不是自己指标的问题。接下来就是数据的完备性和诊断模型的建模,这两部分是智能化诊断的基石,决定了故障定位的层级。同时这两部分也是相辅相成的,通过诊断模型的构建可以对可观测性指标查漏补缺,通过补齐指标也可以增加诊断模型的深度。
主要通过三方面结合来不断的完善这两部分:第一,历史故障推演,历史故障相当于已经知道标准答案的考卷,通过部分历史故障+人工经验来构建最初的诊断模型,然后迭代推演其余的历史故障,但是这一步出来的模型容易出现过拟合现象;第二,利用混沌工程模拟常见的异常,不断修正模型;第三,线上人为打标的方式,来继续补齐可观测性指标、修正诊断模型。
首先,我们来对齐一个概念,什么是“最后一公里”?在日常生活中,它具备以下特点:
走路有点远,坐车又太近,不近不远的距离很难受;
最后一公里的路况非常复杂,可能是宽阔大道,也可能是崎岖小路,甚至是宛如迷宫的室内路程(这点外卖小哥应该体会最深)。
那么分布式问题诊断领域的最后一公里又是指什么呢,它又具备哪些特征?
在诊断流程上,此时已经离根因不会太远,基本是定位到了具体的应用、服务或节点,但是又无法
确定具体的异常代码片段;
能够定位根因的数据类型比较丰富,可能是内存占用分析,也可能是 CPU 占用分析,还可能是特
首先,我们需要一种方法,可以准确的到达最后一公里的起点,也就是问题根因所在的应用、服务或是机器节点。这样可以避免根源上的无效分析,就好像送外卖接错了订单。那么,如何在错综复杂的链路中,准确的定界根因范围?这里,我们需要使用 APM 领域较为常用的链路追踪(Tracing)的能力。通过链路追踪能够准确的识别、分析异常的应用、服务或机器,为我们最后一公里的定位指明方向。
然后,我们通过在链路数据上关联更多的细节信息,例如本地方法栈、业务日志、机器状态、SQL 参数等,从而实现最后一公里的问题定位,如下图所示:
软件供应链通常包括三个阶段-软件研发阶段,软件交付阶段和软件使用阶段,在不同阶段的攻击面如
下:
Helm:� Helm 是 Kubernetes 的包管理工具,它提出了 Chart 这个概念。
1.一个 Chart 描述了一个部署应用的多个 Kubernetes 资源的�YAML 文件,包括文档、配置项、版
本等信息;
2.提供 Chart 级别的版本管理、升级和回滚能力。
Kata Containers 在官方网站上展示的格言就是——
The speed of containers, the security of VMs
而且,这类技术最广为接受的名字也是“安全容器”。然而,隔离性所带来的好处远不止安全性这一
点,在阿里巴巴和蚂蚁金服的大规模实践中,它至少还意味着四个方面的优势:
分层内核可以改善节点调度效率,容器的进程在自己的内核上被调度,对于主机来说,就只是一个进程,这在节点上运行着大量容器的时候,对节点的调度效率和稳定性都有极大的帮助;
良好的封装可以降低运维的压力,如果可以把应用进程乃至数据全部放入安全容器中,不对主机展示,那么对于主机的运维来说,将变得更加简单;
有利于系统的 QoS 保障和计费系统的准确工作,如果所有用户的相关的计算和数据流量都放在一个完全封装的安全容器的中,那么,对于主机管理网的 QoS 保障、对用户的 SLA 保障、对用户的计费都将是可操作且集中的;
有利于保障用户的数据隐私,普通容器的情况下,容器的文件系统数据和进程数据是完全暴露给主机的,对于云服务场景,这就要求用户授权服务方来访问用户数据,否则无异于掩耳盗铃,但如果可以通过安全容器的沙箱把这些都放在容器中,那么对于用户的授权要求也就可以最小化了。
gVisor 是安全容器技术,更是全新的操作系统。它实现了一个用 Go 语言写的“安全内核”。这个内核实现了 Linux Kernel 的绝大部分功能。由于 Go 语言在内存安全与类型安全上的优势,gVisor 的“安全内核”被视为一道重要的防御纵深。我们在开发中会 review 每一行代码的“安全性”。为了进一步加强安全,蚂蚁搭建了一套 fuzz 测试系统,一刻不停的测试。为了弥补 Go 语言在性能上的不足,蚂蚁重构了网络协议栈,在获取安全的同时,也大幅提升了性能。
面向云原生,gVisor 的最大优势是“资源可伸缩”。它不会预留 CPU 与内存资源,跟普通的 Linux进程并无太大差异。在 gVisor 容器中,不用的资源会立刻还给宿主机。我们在尝试基于 gVisor 的进程级虚拟化技术,打破传统容器的资源边界。在 Serverless 等场景下,实现超高密度的容器部署。
就目前来看,虽然云原生应用期待沙箱有更好的隔离性,但目前的沙箱技术尚有一些需求没有完全满足,所以,我们在规划 2.0 乃至未来的 Kata 的时候,就将演进方向定为:
在保持沙箱边界清晰的同时,可以跨沙箱共享一部分资源,极致降低开销;
更加按需、弹性、即时地根据应用的需求来提供资源,而不是硬性地切分;
让用户空间工具、沙箱、容器应用的内核联合为应用提供服务,因为应用的服务边界是内核 ABI 而不是模拟的硬件。