本文要点
AWS Lambda 是许多云原生应用程序和用例的关键要素。
AWS Lambda 的本质要求特别注意可观察性。
分布式跟踪对于成功运行复杂的、基于 Lambda 的应用程序非常有必要。
Lambda 的分布式跟踪需求强调了对全面的、即时的、低维护成本的分布式跟踪的需要。
AWS Lambda 可能是过去几年软件开发中云原生转换最典型的技术之一。根据其官方网站的公告:“使用 AWS Lambda,用户无需提供或管理服务器即可运行代码,用户只需为所消耗的计算时间付费。”从 2014 年最开始只支持 Node.js,到现在 AWS Lambda 已经可以支持在各种编程语言中开发和部署函数,包括 Go、Java、Python、Ruby 和 C#等。
AWS Lambda 的发布向我们推出了作为主流云原生范例的无服务器计算(以函数即服务作为“计算”基本面)。其他主流的云平台也提供过类似的功能,如Google Cloud函数和Microsoft Azure函数,以及像Knative和Apache OpenWhisk这样的开源项目也提供过。
在推出五年多之后,AWS Lambda 可以说仍然是最知名的以及采用度最高的的无服务器平台,尽管没有其被采用的精确数据,而且相对公平来讲,它已经超越了“早期采用者”阶段(即仅在开发初期选择使用)。
然而,AWS Lambda 仍然与过去几年云原生的另一个典型主题-可观察性有些矛盾。尽管 Lambda 集成了 CloudWatch 的 metrics 和 logs,以及 X-Ray 的分布式跟踪,但调试出错时要找出生产中某个函数的问题仍然是一个相当大的挑战。
本文重点关注分布式跟踪,并基于 AWS Lambdas 在当今计算环境中所使用的用例,讨论了在 AWS Lambda 函数中获取和利用可观察性的最佳实践。
为什么 Lambda 用户会有这么大的增长?
使用 AWS Lambda,用户可以定义同步执行的函数,例如为 HTTP 请求提供服务,或者异步地响应其他 AWS 服务生成的事件。能触发AWS Lambda函数的事件非常丰富,并且还在不断增长,其中被采用最多的事件类型有:
CloudWatch事件:可以使用描述 AWS 资源变化的简单规则来定义。
S3事件:在 S3 buckets 中创建或删除对象时发出。
SQS事件:它将队列在 SQS 中的消息传递给 Lambda 函数进行处理。
但是除了 AWS Lambda 的基本功能之外,采用者也不需要对它的典型特性多加处理:
无基础设施管理:AWS Lambda 自动管理分配给运行函数的基础设施,弹性增配和释放计算资源。随着每周一早上,工作日伊始人们回到办公桌时,应用程序需要提供的负载增加,AWS Lambda 会在幕后自动增加函数的实例数量。在工作日结束之时,所需负载下降,未充分利用的实例将自动释放。AWS Lambda 的承诺是为开发人员减负,这些本来也不是开发人员真正需要关注的,开发人员只需专注开发。
无固定成本:当提供函数时,用户只需根据提供工作负载期间分配的 CPU 时间和内存付费即可,而当没有工作负载时,不会产生任何成本。成本随需变化,不固定。
AWS Lambda 达到了上面的要求吗?这里面最主要的是,用户可以只在需要的时候将代码放到 AWS Lambda 运行,并且只支付函数为工作负载服务的时间(虽然会四舍五入到最接近的 100ms)。然而,这是以性能上的不可预测性为代价的。当 AWS Lambda 启动一个实例来处理负载时,因为初始化需要运行时间,通过该实例的第一个请求将遭受相当高的延迟。
这种现象被称为“冷启动”,为此 Amazon 提出来一个创造性的解决方案来保持函数“温暖”,允许用户通过付费的方式来为一定数量的实例”保暖“,通过这种方式开发人员可以专心于基础设施,这可以算是固定成本,但是这对于“对 Lambda 工作负载延迟峰值”非常敏感的用户来说非常有用。
最常见的 Lambda 用例
与所有通用计算平台一样,用户可以使用 AWS Lambda 做很多不同的事情。在实践中,最经常出现的用例如下:
原型设计和早期开发:由于没有前期的基础设施成本,AWS Lambda 对于新产品和新功能来说是一个非常有吸引力的原型设计平台,特别是对于那些不希望或不能投入人员和资金来维护虚拟机和持久化容器部署的初创企业和小型企业。然后,随着产品逐渐成熟,其工作负载变得更加可预测,开始出现迁移趋势,从 AWS Lambda 转向更自管理的计算平台,如 EC2 或 Fargate,原因如下:
成本:如果用户的工作负载不需要 Lambda 的“调整为 0”功能(即不需要工作负载时完全释放计算资源),而且愿意自己处理容器或虚拟机的扩容或释放,这种既定工作负载的情况用户还要为 AWSLambda 的灵活性和随需应变性支付一定规模的涨价成本时,成本就变得相当大。
复杂性:尽管没有什么能真正阻止用户将大型代码库部署到 AWS Lambda(用户有250MB的可用空间用于部署包,这相当于大量代码),但函数最好应该相对小和简单,因为它们在生产环境中很难观察和调试。
业务流程 &系统集成:许多 AWS 服务中事件触发器的存在使得 Lambda 函数自然成为系统间(业务)流程集成的候选对象。例如,在 Instana 中我们使用 Lambda 函数对许多不同类型的自动化,从 Quality Assurance 任务(像自动供应基础设施)到测试最新的构建,到将我们的支持门户整合为项目管理系统,再到自动为我们的工程师创建工作项(以响应客户即工程师打开 Support Tickets 的操作)。
人们似乎对将 AWS Lambda 用于机器学习用例越来越感兴趣,尤其是与AWS Sagemaker结合使用。
对于 Lambda 是一个优秀的业务流程和系统集成工具这一事实,有一个有趣的推论,(几乎)没有 Lambda 函数是孤岛。Lambda 函数,往往召集其他 Lambda 函数,以及没运行在 Lambda 上的系统。这些从 Lambda 函数中调用的系统,要么是 AWS 管理的服务,要么是部署在其他 AWS 计算平台(如 EC2、ECS 或 Fagate,甚至是本地平台)上的其他客户系统。相关地,后面将讨论关于分布式跟踪 AWS Lambda 函数的需求。
Lambda 的模棱两可
每一个计算范例都伴随着权衡,Lambda 也不例外:
难以调试:“无服务器”这一事实意味着,在不可避免地出现错误时,用户基本上无法访问生产基础设施进行调试。(“服务器”当然存在,但是用户无法控制它,所以对用户来说它看起来像是“无服务器”。)当然,AWS 提供了在本地运行Lambda代码的方法。无服务器框架也有本地测试。在将远程调试工具附加到 Lambda 函数(例如对于.NET和Python)方面,还有一些有趣的概念验证。然而现实是,当用户在生产中出问题时,所能调试的开箱即用功能是非常有限的,它往往成为”Cloud Printf“的游戏(也就是添加更多的日志到 CloudWatch,推出一个新的 Lambda 版本,然后祈祷它受欢迎),如果你正忙着修复故障的话,这并不是一个有趣的游戏。更糟糕的是,由于一次 AWS Lambda 调用的成本取决于函数运行的时间,让 AWS Lambda 函数卡住的 Bug(比如处理意外的大数据库结果集)既难以调试,也会给用户的云预算带来巨大开销。这常常使用户陷入两难。
“无状态”只是意味着用户从其他地方提取状态:就运行期间而言,Lambda 需要函数是无状态的。用户不能依赖任何一个 Lambda 函数来保留状态,而不处理之前的请求。然而,需要状态来处理的业务逻辑是非常罕见的。因此,大多数 Lambda 函数需要从其他服务加载一些状态信息,这可能会导致不可预测的执行时间,而且通常 Lambda 函数也需要存储一些状态修改。公平地说,AWS 基础设施内部的输入输出问题似乎很少,像“调用出一半 RDS 数据库就失败”这样的编程疏忽也很少。
分布式复杂性:复杂的场景通常涉及大量 Lambda 函数,这些函数通过事件彼此松散耦合。来看看作者所描述的“AWS 中典型的 100%无服务器架构”,这其中有很多移动的部分需要跟踪。考虑到许多 Lambda 函数都是异步操作的,那么要找出哪些函数涉及到哪个请求以及哪里出错了,就像尝试完成一个百万块的拼图游戏一样。
许多 AWS Lambda 架构中固有的分布式复杂性,以及在 AWS“生产”中运行的 AWS Lambdas 的有限调试能力,都要求用户集中 Lambda 函数的所有可观察性。这意味着,除了 CloudWatch 中明显的 logs 和 metrics 之外,还要对 Lambda 函数采用分布式跟踪。
分布式跟踪有助于防丢失
自本世纪初以来,分布式跟踪已成为应用程序性能监测方法中不可分割的一部分。而且由于OpenTracingAPI 和 implementations 通过开源项目实现,比如Zipkin,Jaeger,以及像一些无关的项目OpenCensus和HTrace,分布式跟踪最近已经在监测和可观察性的前沿拥有了很大话语权。虽然 OpenTracing 和 OpenCensus 项目已经停止了,但是他们的继承者OpenTelemetry仍在积极地工作着。
分布式跟踪概述
微服务和云原生架构的出现无疑使我们的分布式系统比以往任何时候都更加分布式。与此同时,信号软件的组件变得越来越小。如果用户越希望有更多的活动部件协同工作以服务于工作负载,那么他的集体和个体行为就越需要可见,特别是与“如何为最终用户服务”有关的行为。有人说,开发人员的工作越来越像水管工的工作,专注于连接各种微服务的“管道”。
这种增加的分布性和相互依赖性正是分布式跟踪变得如此重要和有价值的原因。分布式跟踪是一种监测实践,它涉及到服务,以集体和协作的方式记录那些描述它们在服务一个请求时所采取的操作的 spans。与相同请求相关的 spans 被分组在一个跟踪中。为了明了哪个跟踪被记录了,每个服务都必须在它自己对其他上游服务的请求中包含跟踪上下文。简而言之,你可以把分布式追踪想象成一场接力赛,一种田径运动,运动员轮流跑,互相传递接力棒。
将分布式跟踪类比为接力赛,每个服务都是运动员,跟踪上下文是接力棒。如果其中一个服务丢失了它,或者服务之间的切换不成功(例如由于实现了不同的分布式跟踪协议而导致的不成功),那么跟踪就会中断。分布式跟踪和接力赛之间的另一个相似之处是,比赛的每个环节都很重要,都有可能会让你输掉比赛,但除了在每个环节不掉链子之外,同时你必须在每个环节都跑得快才能出类拔萃。
AWS Lambda 函数的分布式跟踪
在讨论跟踪 AWS Lambda 有什么可用之处之前,让我们讨论一下分布式跟踪解决方案应该满足的功能性和非功能性需求::
多轨并行的运行时间:AWS Lambdas 可以用多种语言编写,最常用的是 Node.js 和 Python,但还有更多的语言,比如 Java,Ruby,.Net Core 和 Powershell。与微服务发生的情况类似(因为 Lambda 函数实际上是一种非常微小的微服务),开发团队选择他们认为最适合这项任务的语言,涉及到他们想采用的 libraries 和 SDKs,以及团队对该语言的熟悉程度等。我从与我交互过的几乎每一个采用 Lambda 的人那里听到的都是,他们至少使用两个不同的 AWS Lambda 运行时间。
多平台:如前所述,没有一个 AWS Lambda 函数是孤岛。有些情况下,架构是仅作为 AWS Lambda 函数实现的,但根据我们的经验,这种情况是(相当特殊的)例外,而不是规则。通过分布式跟踪实现的洞察力的价值随着相互连接的系统数量的增加而增加,这意味着无论用户希望在 AWS Lambda 函数中使用什么分布式跟踪框架,都应该更好地用于基础设施的其他部分。对于 Lambda 使用的基础设施来说,这种做法尤其正确,但网络效应毫无疑问适用于分布式跟踪。顺便说一句,这正是W3C跟踪上下文规范的目的所在,该规范旨在提供分布式跟踪 implementations 之间互操作性的措施。顺便提一下,用户在 Lambda 函数中使用的语言可能与用户在数据中心的“遗留”应用程序中使用的语言不同,这就增加了 Lambda 跟踪的多轨并行的运行时间需求。
间接成本低:分布式跟踪实现带来的间接成本不仅为最终用户带来了延迟,还直接影响了 AWS 账单(Lambda 函数按 CPU 时间和最大内存分配来收费)。凡是考虑成本的分布式跟踪 implementation,都不会给 Lambda 函数的内存增加几十或几百兆字节的占用(不过,我在其他平台上看到过这种情况)。但是,CPU 时间可能会受到影响,特别的,由于 Lambda 是无状态的,跟踪数据必须在 Lambda 函数完成之前发送到 APM 解决方案,这通常会阻塞函数的完成,直到跟踪数据上传完成,不这样做就可能会丢失这些跟踪数据。
我想了很久,很难在上面的列表中添加一个名为“与其他 AWS 服务集成”的条目。毕竟,Lambda 函数是从其他服务生成的事件异步调用的,以及从 AWS 的 API 网关和应用程序负载均衡器同步调用的。而拥有来自 API 网关的相同跟踪 spans 将有助于回答“这个延迟是从哪里来的?”,这真的和分布式系统一样古老。但是,我仍然决定没加进来。因为延迟的根源是很少使用负载平衡器和 Lambda 函数之间的 AWS 网络,而不是 AWS Lambda 函数本身,也不是他的 dependencies,阻碍或等待长时间运行的同步调用,或其内部运算。
Instrumentation 类型
追踪数据的收集是由专门的 Instrumentation 来执行的。一般来说可以分为两大类:
ProgrammaticInstrumentation 是提供 API 来进行编码的 Instrumentation,例如OpenTracing或 AWS 的X-Ray SDK。
Automatic instrumentation 是通过修改代码和用于提取跟踪数据的框架(不需要额外的代码)来执行的。例如,通常在 Node.js 中通过 monkeypatching 来实现,在 Java 中通过 bytecode manipulation 来实现。
注意,如果在用户消费的框架、库,以及供应商管理的服务(如 AWS RDS)中内置 ProgrammaticInstrumentation,它们实现 Instrumentation 的方式让您感觉像是自动的,因为用户不需要维护这些代码。对我来说,这正是问题的关键:实现 Instrumentation 的好代码是用户不需要持有和维护的。
Instrumentation 的交付
但是,用户如何将 Instrumentation 交付到生产系统以便利用它收集需要的数据呢?要实现也是有梯度的,从手动到自动。
随代码内置 Instrumentation 或者在运行代码期间运行 Instrumentation:无论何时部署函数的新版本,Instrumentation 代码都会随之嵌入。Programmatic instrumentation 主要是内置的,尽管有方法将 API 和 implementation 分割,但这样做的话就需要额外的可用 dependencies。当然,也可能需要配置内置的 Instrumentation。
Drop-inInstrumentation 包含在程序运行时添加激活 Instrumentation 的一些 dependencies 和配置。例如,由 Lambda 层提供的 Instrumentation,通过配置选项激活,像定制包装器脚本(也通过 Lambda 层提供),它将执行委托给实际的 Lambda 函数处理程序。(顺便说一下,Instana 就是这样做的。)
正如我认为自动化配置优于 Programmatic 配置一样,交付 Instrumentation 所需的工作量越少越好。从这个角度来看,人们可能认为 ProgrammaticInstrumentation 有其优点,只要它也是内置的。但在我的经验中,情况并非如此。维护 ProgrammaticInstrumentation 的工作比交付 Drop-inInstrumentation 的工作要昂贵得多。在大多数情况下,自动化的交付可以在 CI/CD 管道中一次性设置完成,或者只需要很少的维护。
但是 ProgrammaticInstrumentation 需要随着代码不断变化,而且只要代码不断变化,就需要付出一定的代价。根据Lehman的软件进化法则,软件需要不断变化才能保持有用。在考虑 ProgrammaticInstrumentation 时,那些保持软件有用所需的更改可能需要调整 Instrumentation。在 Lambda 中,这个变化的可能性会造成的需要调整 Instrumentation 的频率高于往常,随着用户应用于 Lambda 的变化越多,集成软件系统需要关心如何与这些变化进行交互,这通常需要调整跟踪数据的收集,并需要在 Lambda 码库的生命周期持续投入大量 ProgrammaticInstrumentation。
最好的 Instrumentation 类型
总结一下,我之前已经讨论过,最好的 Instrumentation 类型是自动的,并且在没有太多开销的情况下交付它是非常重要的。那么,这将如何实现呢?在目前的技术水平下,有两种方法可以实现这些需求:
定制的 Lambda 运行时间:AWS Lambda 允许用户为 Lambda 函数提供定制运行时间,一些分布式跟踪解决方案的供应商确实提供了定制的 Lambda 运行时间作为一个总承包解决方案。然而,这意味着 AWS Lambda 运行时间提供的安全和维护更新、bug 修复和新特性让分布式跟踪提供程序充当“守门员”。用户的里程可能会有所不同,但我个人会对此感到不舒服:这让我想起了太多关于 Android 移动生态系统的问题,移动设备制造商在跟上 Android 发布和维护旧设备方面的记录通常都很糟糕。当然,用户体验有多好取决于分布式跟踪供应商的实际工作质量,我的初衷并不是说这是一个不应该使用的选项。
Lambda 层上的 Instrumentation:用户可以配置函数来使用 AWS Lambda层,这些层基本上是 Lambda 实例文件系统中可用的附加文件。这可以用非常小的操作开销来交付跟踪函数所需的 Instrumentation。许多分布式跟踪供应商确实做到了这一点,用户也不难发现许多分布式跟踪供应商,例如,专注监视解决方案的λ AWSome Lambda Layers列表。在激活 Instrumentation 的容易程度方面,技术水平存在差异。对于像 Instana 这样的供应商,用户只需要设置一些环境变量;对于其他类型,用户则需要对代码进行小的更改。我个人的观点是配置比代码更改更容易处理,但同样,具体到每个用户的情况可能有所不同。
总而言之,在我看来,最好的 Instrumentation 是自动的,并且以尽可能最简单的方式交付。
结论
AWS Lambda 仍旧是无服务器计算的标准,即使随着无服务器函数在应用程序开发中的使用达到了空前的高度,Amazon 公司仍在探索无服务器计算在生产环境中的有效性能达到一个怎样的程度。
随着越来越多的组织将无服务器函数作为其应用程序开发过程和平台的重要组成部分,他们正在考虑如何在生产应用程序中使用更多的无服务器函数(以及是否应该使用)。
还有一个担心就是可观察性,对于实现无服务器生产代码的团队来说,这仍然是一个主要的挑战,尤其是因为遗留 APM 工具仍然努力达到开发团队所需的可见性水平。
AWS Lambda 为开发人员和操作人员带来了显著的差异,强调了专门构建云原生监测工具的需求,这些工具能够应对无服务器监测的挑战,并使用不同的方法来获取应用程序的可观察性。
任何考虑将无服务器作为其生产环境的一部分的人都应该将端到端可观察性作为一个必要的需求,并专注于能够跨分布式系统的监视和跟踪解决方案,这些分布式系统是基于云原生技术、像 Lambda 这样的无服务器平台、以及通过 Lambda 集成的可能较老的技术构建的(因为无服务器很少是孤岛)。
作者简介:
Michele Mancioppi 担任 Instana 高级技术产品经理,负责代理、分布式跟踪、Cloud Foundry 和 VMware Tanzu 的所有产品开发工作。在加入 Instana 之前,他是 SAP 云平台性能团队的开发专家和技术领导。Michele 拥有意大利 Trento 大学计算机科学学士和硕士学位,以及荷兰 Tilburg 大学信息系统博士学位。
查看英文原文:The Right Way of Tracing AWS Lambda Functions
评论