跟踪AWS Lambda函数的正确方法

本文要点

  • 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函数,以及像KnativeApache 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函数(例如对于.NETPython)方面,还有一些有趣的概念验证。然而现实是,当用户在生产中出问题时,所能调试的开箱即用功能是非常有限的,它往往成为”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通过开源项目实现,比如ZipkinJaeger,以及像一些无关的项目OpenCensusHTrace,分布式跟踪最近已经在监测和可观察性的前沿拥有了很大话语权。虽然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

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/wLaVJwPagCVmhgZFqdeQ
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券