基于 AWS Lambda 的无服务器可观测性
在分布式系统和无服务器计算的世界中,实现可观测性是确保应用程序可靠性和性能的关键。它不仅仅是传统监控。通过利用 Amazon CloudWatch 和 AWS X-Ray 等 AWS 可观测性工具,您可以深入了解无服务器应用程序、排查问题并优化应用程序性能。在本指南中,我们将学习为基于 Lambda 的无服务器应用程序实施可观测性的基本概念、工具和最佳实践。
实施基础设施或应用程序可观测性之前的第一步是确定您的关键目标。这可能是增强用户体验、提高开发者生产力、满足服务级别目标 (SLOs)、增加业务收入或任何其他取决于应用程序类型的特定目标。因此,明确定义这些关键目标并确定如何衡量它们。然后从这些目标倒推来设计您的可观测性策略。参考"监控重要的事项"了解更多信息。
可观测性的三大支柱
可观测性有三个主要支柱:
- Logs:在应用程序或系统中发生的离散事件的时间戳记录,如故障、错误或状态转换
- Metrics:在不同时间间隔测量的数字数据(时间序列数据);SLIs(请求率、错误率、持续时间、CPU% 等)
- Traces:Trace 代表单个用户跨多个应用程序和系统(通常是微服务)的旅程
AWS 提供原生和开源工具来促进日志记录、metrics 监控和追踪,以获取 AWS Lambda 应用程序的可操作洞察。
Logs
在可观测性最佳实践指南的这一部分中,我们将深入探讨以下主题:
- 非结构化与结构化日志
- CloudWatch Logs Insights
- 记录关联 ID
- 使用 Lambda Powertools 的代码示例
- 使用 CloudWatch Dashboards 进行日志可视化
- CloudWatch Logs 保留策略
Logs 是应用程序中发生的离散事件。这些可以包括故障、错误、执行路径或其他事件。Logs 可以以非结构化、半结构化或结构化格式记录。
非结构化与结构化日志
我们经常看到开发者在应用程序中使用 print 或 console.log 语句开始记录简单的日志消息。这些在大规模时难以编程方式解析和分析,特别是在基于 AWS Lambda 的应用程序中,这些应用程序可能跨不同日志组生成许多行日志消息。因此,在 CloudWatch 中整合这些日志变得具有挑战性且难以分析。您需要进行文本匹配或正则表达式来查找日志中的相关信息。以下是非结构化日志的示例:
[2023-07-19T19:59:07Z] INFO Request started
[2023-07-19T19:59:07Z] INFO AccessDenied: Could not access resource
[2023-07-19T19:59:08Z] INFO Request finished
如您所见,日志消息缺乏一致的结构,使得从中获取有用洞察变得困难。同时,也很难向其中添加上下文信息。
而结构化日志是一种以一致格式(通常是 JSON)记录信息的方式,允许将日志视为数据而不是文本,这使得查询和过滤变得简单。它使 开发者能够高效地以编程方式存储、检索和分析日志。它还有助于更好的调试。结构化日志通过日志级别提供了一种更简单的方式来修改不同环境中日志的详细程度。注意日志级别。 记录过多会增加成本并降低应用程序吞吐量。确保在记录之前对个人身份信息进行脱敏处理。以下是结构化日志的示例:
{
"correlationId": "9ac54d82-75e0-4f0d-ae3c-e84ca400b3bd",
"requestId": "58d9c96e-ae9f-43db-a353-c48e7a70bfa8",
"level": "INFO",
"message": "AccessDenied",
"function-name": "demo-observability-function",
"cold-start": true
}
优先使用结构化和集中化日志记录到 CloudWatch logs,以输出有关事务的操作信息、不同组件之间的关联标识符以及应用程序的业务结果。
CloudWatch Logs Insights
使用 CloudWatch Logs Insights,它可以自动发现 JSON 格式日志中的字段。此外,JSON 日志可以扩展为记录特定于应用程序的自定义元数据,这些元数据可用于搜索、过滤和聚合日志。
记录关联 ID
例如,对于来自 API Gateway 的 HTTP 请求,关联 ID 设置在 requestContext.requestId 路径中,可以使用 Lambda Powertools 在下游 Lambda 函数中轻松提取和记录。分布式系统通常涉及多个服务和组件协同处理请求。因此,记录关联 ID 并将其传递给下游系统对于端到端追踪和调试变得至关重要。关联 ID 是在请求开始时分配的唯一标识符。当请求通过不同服务时,关联 ID 包含在日志中,允许您追踪请求的整个路径。您可以手动将关联 ID 插入 AWS Lambda 日志,或使用 AWS Lambda Powertools 等工具从 API Gateway 轻松获取关联 ID 并将其与应用程序日志一起记录。例如,对于 HTTP 请求,关联 ID 可以是在 API Gateway 发起的 request-id,然后传递给后端服务(如 Lambda 函数)。
使用 Lambda Powertools 的代码示例
作为最佳实践,尽早在请求生命周期中生成关联 ID,最好在无服务器应用程序的入口点,如 API Gateway 或 Application Load Balancer。使用 UUID、request id 或任何其他可用于跨分布式系统跟踪请求的唯一属性。将关联 ID 作为自定义 header、body 或元数据的一部分随每个请求传递。确保关联 ID 包含在下游服务的所有日志条目和 traces 中。
您可以手动捕获并将关联 ID 作为 Lambda 函数日志的一部分包含,或使用 AWS Lambda Powertools 等工具。使用 Lambda Powertools,您可以从受支持的上游服务的预定义请求路径映射中轻松获取关联 ID,并自动将其添加到应用程序日志中。同时,确保将关联 ID 添加到所有错误消息中,以便在发生故障时轻松调试和识别根本原因,并将其关联回原始请求。
让我们看一下代码示例,演示以下无服务器架构中带有关联 ID 的结构化日志记录以及在 CloudWatch 中查看:

// Initializing Logger
Logger log = LogManager.getLogger();
// Uses @Logger annotation from Lambda Powertools, which takes optional parameter correlationIdPath to extract correlation Id from the API Gateway header and inserts correlation_id to the Lambda function logs in a structured format.
@Logging(correlationIdPath = "/headers/path-to-correlation-id")
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
...
// The log statement below will also have additional correlation_id
log.info("Success")
...
}
在此示例中,基于 Java 的 Lambda 函数使用 Lambda Powertools 库来记录来自 API Gateway 请求的 correlation_id。
代码示例的 CloudWatch 日志样本:
{
"level": "INFO",
"message": "Success",
"function-name": "demo-observability-function",
"cold-start": true,
"lambda_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"correlation_id": "<correlation_id_value>"
}_
使用 CloudWatch Dashboards 进行日志可视化
一旦您以结构化 JSON 格式记录数据,CloudWatch Logs Insights 将自动发现 JSON 输出中的值并将消息解析为字段。CloudWatch Logs Insights 提供专门构建的类 SQL 查询语言来搜索和过滤多个日志流。您可以使用 glob 和正则表达式模式匹配对多个日志组执行查询。此外,您还可以编写自定义查询并保存它们以便再次运行,无需每次重新创建。
在 CloudWatch Logs Insights 中,您可以通过包含一个或多个聚合函数的查询生成折线图、柱状图和堆叠面积图等可视化。然后您可以轻松地将这些可视化添加到 CloudWatch Dashboards。下面的示例 dashboard 显示了 Lambda 函数执行持续时间的百分位报告。此类 dashboard 将快速为您提供有关应该在哪里集中精力改善应用程序性能的洞察。平均延迟是一个很好的 metrics,但**您应该以优化 p99 而不是平均延迟为目标。**
要将(平台、函数和扩展)日志发送到 CloudWatch 以外的位置,您可以使用 Lambda Telemetry API 与 Lambda Extensions。许多合作伙伴解决方案提供使用 Lambda Telemetry API 的 Lambda Layer,使与其系统的集成更加容易。
为了最好地利用 CloudWatch Logs Insights,请考虑您必须以结构化日志的形式将哪些数据摄入到日志中,这将帮助更好地监控应用程序的健康状况。
CloudWatch Logs 保留策略
默认情况下,Lambda 函数中写入 stdout 的所有消息都保存到 Amazon CloudWatch 日志流中。Lambda 函数的执行角色应具有创建 CloudWatch 日志流和向流写入日志事件的权限。重要的是要注意,CloudWatch 按摄入的数据量和使用的存储量计费。因此,减少日志记录量将帮助您最小化相关成本。默认情况下,CloudWatch 日志会无限期保留且永不过期。建议配置日志保留策略以降低日志存储成本,并将其应用于所有日志组。您可能希望每个环境有不同的保留策略。日志保留可以在 AWS 控制台中手动配置,但为了确保一致性和最佳实践,您应该将其作为基础设施即代码 (IaC) 部署的一部分进行配置。以下是一个示例 CloudFormation 模板,演示如何为 Lambda 函数配置日志保留:
Resources:
Function:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Runtime: python3.8
Handler: main.handler
Tracing: Active
# Explicit log group that refers to the Lambda function
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${Function}"
# Explicit retention time
RetentionInDays: 7
在此示例中,我们创建了一个 Lambda 函数和相应的日志组。RetentionInDays 属性设置为 7 天,意味着该日志组中的日志将保留 7 天后自动删除,从而帮助控制日志存储成本。
Metrics
在可观测性最佳实践指南的这一部分中,我们将深入探讨以下主题:
- 监控和告警开箱即用的 metrics
- 发布自定义 metrics
- 使用嵌入式 metrics 从日志中自动生成 metrics
- 使用 CloudWatch Lambda Insights 监控系统级 metrics
- 创建 CloudWatch 告警
监控和告警开箱即用的 metrics
Metrics 是在不同时间间隔测量的数字数据(时间序列数据)和服务级别指标(请求率、错误率、持续时间、CPU 等)。AWS 服务提供了许多开箱即用的标准 metrics,帮助监控应用程序的运营健康状况。确定适用于您应用程序的关键 metrics,并使用它们来监控应用程序性能。关键 metrics 的示例可能包括函数错误、队列深度、失败的状态机执行和 API 响应时间。
开箱即用 metrics 的一个挑战是知道如何在 CloudWatch dashboard 中分析它们。例如,查看并发时,我应该看最大值、平均值还是百分位?每个 metrics 的正确统计量都不同。
作为最佳实践,对于 Lambda 函数的 ConcurrentExecutions metrics,查看 Count 统计量以检查它是否接近账户和区域限制或接近 Lambda 预留并发限制(如适用)。
对于 Duration metrics(指示函数处理事件所需的时间),查看 Average 或 Max 统计量。要测量 API 的延迟,查看 API Gateway Latency metrics 的 Percentile 统计量。P50、P90 和 P99 是比平均值更好的延迟监控方法。
一旦您知道要监控哪些 metrics,请在这些关键 metrics 上配 置告警,以便在应用程序组件不健康时通知您。例如:
- 对于 AWS Lambda,告警 Duration、Errors、Throttling 和 ConcurrentExecutions。对于基于流的调用,告警 IteratorAge。对于异步调用,告警 DeadLetterErrors。
- 对于 Amazon API Gateway,告警 IntegrationLatency、Latency、5XXError、4XXError
- 对于 Amazon SQS,告警 ApproximateAgeOfOldestMessage、ApproximateNumberOfMessageVisible
- 对于 AWS Step Functions,告警 ExecutionThrottled、ExecutionsFailed、ExecutionsTimedOut
发布自定义 metrics
根据应用程序所需的业务和客户成果确定关键绩效指标 (KPIs)。评估 KPIs 以确定应用程序成功和运营健康状况。关键 metrics 可能因应用程序类型而异,但示例包括网站访问量、订单数量、购买的航班、页面加载时间、独立访客等。
发布自定义 metrics 到 AWS CloudWatch 的一种方式是调用 CloudWatch metrics SDK 的 putMetricData API。但是,putMetricData API 调用是同步的。它将增加 Lambda 函数的持续时间,并可能阻止应用程序中的其他 API 调用,导致性能瓶颈。此外,Lambda 函数执行时间的增加将产生更高的成本。另外,您还需要为发送到 CloudWatch 的自定义 metrics 数量和进行的 API 调用数量(即 PutMetricData API 调用)付费。
发布自定义 metrics 的更高效和经济的方式是使用 CloudWatch Embedded Metrics Format (EMF)。CloudWatch Embedded Metric Format 允许您以写入 CloudWatch logs 的日志方式**异步**生成自定义 metrics,从而以更低的成本提高应用程序性能。使用 EMF,您可以将自定义 metrics 与详细的日志事件数据一起嵌入,CloudWatch 会自动提取这些自定义 metrics,以便您像开箱即用的 metrics 一样对其进行可视化和设置告警。通过以嵌入式 metric 格式发送日志,您可以使用 CloudWatch Logs Insights 进行查询,您只需为查询付费,而不是 metrics 的费用。
为实现这一目标,您可以使用 EMF 规范生成日志,并使用 PutLogEvents API 将其发送到 CloudWatch。为简化此过程,有两个客户端库支持以 EMF 格式创建 metrics。
- 低级客户端库 (aws-embedded-metrics)
- Lambda Powertools Metrics。
使用 CloudWatch Lambda Insights 监控系统级 metrics
CloudWatch Lambda Insights 为您提供系统级 metrics,包括 CPU 时间、内存使用量、磁盘利用率和网络性能。Lambda Insights 还收集、聚合和汇总诊断信息,如**冷启动**和 Lambda worker 关闭。Lambda Insights 利用 CloudWatch Lambda 扩展(打包为 Lambda Layer)。启用后,它收集系统级 metrics,并以嵌入式 metrics 格式为该 Lambda 函数的每次调用向 CloudWatch Logs 发出单个性能日志事件。
CloudWatch Lambda Insights 默认未启用,需要按 Lambda 函数逐一开启。
您可以通过 AWS 控制台或基础设施即代码 (IaC) 启用它。以下是使用 AWS Serverless Application Model (SAM) 启用它的示例。您将 LambdaInsightsExtension 扩展 Layer 添加到 Lambda 函数,并添加托管 IAM 策略 CloudWatchLambdaInsightsExecutionRolePolicy,该策略授予 Lambda 函数创建日志流和调用 PutLogEvents API 以写入日志的权限。
// Add LambdaInsightsExtension Layer to your function resource
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Layers:
- !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:14"
// Add IAM policy to enable Lambda function to write logs to CloudWatch
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Policies:
- `CloudWatchLambdaInsightsExecutionRolePolicy`