在 Amazon Lambda 函数中监控网络流量

作者: Anton Aleksandrov, Daniel Pruessner |

网络监控为大型组织的云应用程序流量模式提供了基本的可见性。它使安全和合规团队能够检测异常情况并保持合规性,同时允许开发团队在多租户云软件服务 (SaaS) 环境中解决问题、优化性能和跟踪成本。实施强大的网络监控使组织能够有效地管理其安全性、合规性和运营需求,同时不断增强其应用程序。

在这篇文章中,您将学习在 Amazon Lambda 函数中进行网络监控的方法以及如何将其应用到您的场景中。

概述

Lambda 是一种安全且高度可扩展的无服务器计算服务,其中每个函数都在具有严格安全边界的隔离执行环境中运行。该架构提供了关键优势,例如增强的安全性、自动扩展计算容量和最小的运营开销。最大限度地减少基础设施管理允许 Lambda 使组织能够将注意力从管理服务器转移到其他关键方面,例如性能优化和网络流量分析。反过来,它们使组织能够构建更安全、更高效的应用程序。

Lambda 网络监控可满足不同的组织需求,例如审计日志和异常检测的合规性要求、流量计量和客户账单的业务需求以及网络问题故障排除的开发需求。传统的基于代理或基于主机的监控方法通常与 Lambda 高度隔离的短暂执行环境不兼容,后者需要其他方法来满足这些关键要求。

您可以使用亚马逊云科技原生集成网络监控解决方案,例如亚马逊虚拟私有云 (Amazon VPC) 流量日志,也可以构建自己的定制解决方案,如本文所述。每种解决方案都提供不同的功能,具有不同的粒度和实时可见性。选择方法时,必须评估关键因素,例如所需的数据粒度、操作复杂性、延迟容限和成本影响。

使用 VPC 流日志

VPC 流日志是亚马逊云科技推荐的网络活动监控工具。如果您的方案需要监控 Lambda 函数的网络活动,则可以将这些函数连接到 VPC 并启用流日志。这会捕获详细的网络流量数据,例如源和目标 IP、端口、协议以及流经函数所用网络接口的所有流量的流量。

当您将函数连接到 VPC 时,Lambda 服务会自动为函数创建弹性网络接口 (ENI),以与基于 VPC 的资源进行通信。默认情况下,VPC 附加函数只能访问 VPC 内的私有资源。如果您需要函数与其他亚马逊云科技服务通信,则应使用 VPC 终端节点。如果您的函数需要与公共互联网通信,则应使用 NAT 网关传输出流量。下图显示了如何使用 VPC 流日志执行 Lambda 函数。

流量日志可详细了解流入和流出 VPC 内网络接口的 IP 流量,为网络审计和活动监控提供有价值的数据。这种方法促进了应用程序层和网络层之间的明确分离,VPC 结构通常由专门的运营或基础设施团队管理。

VPC 流日志提供强大的网络监控解决方案。但是,将其与 Lambda 函数一起使用时,应评估以下注意事项:

  • 它捕获了 ENI 级别的信息。ENI 可以在多个函数中重复使用,因此它不会提供每个函数或每次调用的粒度。
  • 它只记录 IP 地址,不记录 DNS 名称(如果您需要捕获 DNS 名称)。
  • 它将基础设施管理引入您的无服务器应用程序。您必须学习 VPC 结构或让您的基础设施团队参与进来。
  • 可能增加的数据传输成本。有关更多详细信息,请参阅 NAT 网关、VPC 终端节点和流日志的定价。

以下部分探讨了 Lambda 网络监控方法,这些方法可以使用 VPC 流日志进行增强以提高精度,也可以在不将您的函数附加到 VPC 的情况下使用。

代理网络流量

您可以配置 Lambda 运行时,通过侧车代理路由出口网络流量,该代理在 Lambda 执行环境中作为 Lambda 层运行并记录网络活动。代理层应与语言运行时无关。亚马逊云科技建议您使用 Rust 或 Golang 等编译语言,以最大限度地提高可重用性并最大限度地减少延迟。下图显示了从网络代理层发射日志。

应用代理配置因语言运行时而异。在 Python 中,您可以设置 proxy_http 和 proxy_https 环境变量。Java 使用 JVM 标志。Node.js 不提供使用命令行标志或环境变量配置代理的原生方式。因此,您需要更改代码,例如为您的亚马逊云科技开发工具包配置代理或使用第三方开源库,例如全球代理或拦截器。

如果您可以进行可能因运行时而异的功能代码或配置更改,则代理方法最合适。此外,在执行环境中添加网络代理服务器进程会消耗与函数代码共享的资源,这可能会增加网络延迟。

有关拦截 Lambda Runtime API 和运行时进程之间的入站请求/响应的方法,请参阅使用 Amazon Lambda Runtime API 代理扩展增强运行时安全和治理一文。

与运行时无关的技术

以下技术使用了 Lambda 执行环境是基于 Linux 的微型虚拟机这一事实。Lambda 运行时在受限的用户空间内运行,从而防止使用需要提升权限的传统操作系统级监控工具,例如 tcpdump、iptables、ptrace 或 eBPF。以下技术是专门为在这些用户空间限制下工作而设计的,无需提升权限即可使用。

从 procfs 读取操作系统网络层信息

当您需要获取操作系统级别的信息(例如计量传输的字节数)或查看所有打开的连接时,请使用此方法。您可以使用它来实施租户退款或检测网络流量模式的变化。该方法基于 Linux 操作系统中可用的 proc 伪文件系统(也称为 procfs),它为内核数据提供了接口并允许您读取操作系统级别的信息。例如,/proc/cpuinfo 和 /proc/meminfo 伪文件提供有关当前 CPU 和内存使用情况的信息,而 /proc/net/* 则为您提供网络层信息。读取 /proc/net/tcp 和 /proc/net/udp 会给你一个活跃的 TCP/UDP 连接列表,例如远程 IP 地址和端口。读取 /proc/net/dev 可提供网络设备列表以及详细的使用情况统计信息,例如传输和接收的字节数。

"procfs 方法为从 Lambda 函数收集关键网络遥测数据(例如最新的网络统计数据和文件描述符计数)提供了一种简单但强大的方法,这使我们能够监控来自 Lambda 函数的出站连接。更好的是,它使我们能够在基于 Rust 的下一代 Lambda 扩展中通过单一实现来支持多个 Lambda 运行时" ——Datadog 的参谋工程师 AJ Stuyvenberg。

该示例项目提供了 LambdaNetworkMonitor-procFS 堆栈来展示这种技术。对于每次调用,该函数都会读取 /proc/net/dev,并将网络统计数据发送到日志和亚马逊 CloudWatch 指标,如下图所示。

读取 /proc/net/dev 伪文件是在不增加延迟的情况下监控 Lambda 函数网络的一种简单而有效的方法。但是,它不会捕获 DNS 名称及其解析到的 IP 地址。

拦截与网络相关的 libc 调用

Linux 中的低级网络操作,例如 DNS 查询和连接创建,由 C 标准库 (libc) 管理。您可以拦截 Lambda 运行时发出的 libc 函数调用,以监控操作系统级别的网络流量。这是一种更为先进、复杂的机制,可以查看操作系统级别的活动,如下图所示。

截取 libc 函数调用,例如 getaddrinfo(DNS 查询)和连接,允许您详细记录 DNS 名称、IP 地址、端口和协议等详细信息,如下图所示。此方法允许您捕获有关 DNS 查询和已启动的网络连接的全面信息。它可以提供精确的每个功能和每次调用的网络监控,例如主机名和 IP 地址。

以下是简化的流程:

  1. 一个函数向 example.com 发送请求。
  2. 运行时调用 libc getaddrinfo 来解析 DNS 名称。
  3. 你可以拦截这个调用,记录 DNS 名称,然后将调用转发到原来的 libc getaddrinfo。
  4. 最初的 libc getaddrinfo 返回已解析的 IP 地址。您记录它们并返回运行时。
  5. 运行时调用 libc 连接方法来创建新连接。
  6. 您可以拦截此调用,记录 IP 地址,将调用转发到原始 libc 连接,依此类推。

要实现这种技术,你需要使用一种编译成共享对象 (.so) 文件的语言。要实现 libc 方法签名,你应该使用 C、C++ 或 Rust 等语言。以下示例代码使用 Rust 来提供强大的安全保障,并实现了覆盖 getaddrinfo libc 函数(DNS 查询)的实现。

pub extern "C" fn getaddrinfo(
    node: *const c_char,
    service: *const c_char,
    hints: *const addrinfo,
    res: *mut *mut addrinfo,
) -> c_int {
    let printable_node = format!("{}", PrintableCString::from(node));
    let printable_service = format!("{}", PrintableCString::from(service));

    log::debug!("> getaddrinfo node={printable_node} service={printable_service}");

    LIBC_GETADDRINFO(node, service, hints, res)
}

应考虑以下几点:

  • 方法签名与同名的 libc 函数签名完全匹配。
  • 节点服务参数通常是 DNS 名称和端口。
  • 最后,该函数调用真正的 libc getaddrinfo 并返回结果。

编译为 .so 文件时,必须将其打包为 Lambda 层,将该层附加到您的函数,并配置执行环境以通过称为预加载的 Linux 动态链接器功能使用它。将 LD_PRELOAD 环境变量设置为指向您的 .so 文件,以指示操作系统在加载任何其他库(例如 libc)之前对其进行预加载。您可以将其配置为函数环境变量或通过包装脚本进行配置,如下图所示。

#!/bin/sh
echo "running wrapper..."
args=("$@")
export LD_PRELOAD=/opt/liblambda_network_monitor.so
exec "${args[@]}"

此技术允许您获得详细的连接级监控,例如 DNS 查询、解析的 IP 地址、端口、协议和传输的字节数。根据您的要求,可以根据需要对其进行调整以跟踪更多与网络相关的信息。

该示例项目提供了 LambdaNetworkMonitor-LDPreload 堆栈来展示这种技术,如下图所示。对于每次调用,该函数都会打印截获的 libc 函数、DNS 名称和连接 IP 地址。

注意事项

  • 操作系统级别的技术需要对 Linux 基础知识的深刻理解和仔细的实施。亚马逊云科技建议您仔细评估使用哪种方法并保持解决方案的微创性。
  • LD_PRELOAD 是一种高级低级技术,允许您覆盖 libc 方法和操作系统行为。钩子实现不当可能会导致未定义的行为和崩溃。确保你的代码具有强大的递归能力和线程安全性。在生产环境中使用之前,请在受控环境中对其进行彻底测试。
  • LD_PRELOAD 技术依赖于动态链接。它适用于动态链接的运行时,例如 Node.js、Python 和 Java。它不适用于使用静态链接的运行时,例如 Golang。
  • 使用与运行时相关的功能时,可以考虑使用 Lambda 运行时更新控件来确保运行时仅在下次函数更新时更新。
  • 始终仅安装来自可信来源的图层。使用基础设施即代码 (IaC) 工具来附加和审计层配置,例如 Amazon Identity and Access Management (IAM) 权限。

结论

监控 Lambda 函数中的网络流量是许多组织的常见要求。如果您需要审计 IP 级别的网络日志,亚马逊云科技建议您将函数附加到 VPC 并使用流日志。如果你需要每个函数或每次调用的粒度,那么你可以使用本文中描述的技巧对其进行增强。

这些技术为调试、审计和监控提供了宝贵的见解,但它们也需要对 Linux 基础知识有扎实的理解和仔细的实施。它们为需要 Lambda 网络监控的组织提供了实用的解决方案,使他们能够解决问题并保持合规性。

要了解有关无服务器架构和异步 Lambda 调用模式的更多信息,请访问 Serverless Land。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。