修改运行时环境 - AWS Lambda
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

修改运行时环境

您可以使用内部扩展来修改运行时进程。内部扩展不是单独的进程 — 它们作为运行时进程的一部分运行。

Lambda 提供了特定于语言的环境变量,您可以将这些变量设置为向运行时添加选项和工具。Lambda 还提供包装脚本,它允许 Lambda 将运行时启动委托给脚本。您可以创建包装脚本来自定义运行时启动行为。

特定于语言的环境变量

Lambda 支持仅配置方法,以便在函数初始化期间通过以下特定语言的环境变量预加载代码:

  • JAVA_TOOL_OPTIONS – 在 Java 11 和 Java 8 (java8.al2) 上,Lambda 支持此环境变量以在 Lambda 中设置其他命令行变量。此环境变量允许您指定工具的初始化,特别是使用 agentlibjavaagent 选项启动本机或 Java 编程语言代理。

  • NODE_OPTIONS – 在 Node.js 10x 及更高版本上,Lambda 支持此环境变量。

  • DOTNET_STARTUP_HOOKS – 在 .NET Core 3.1 及更高版本上,Lambda 可以采用此环境变量提供的程序集 (dll) 的路径。

使用特定于语言的环境变量是设置启动属性的首选方法。

示例:使用 javaagent 拦截 Lambda 调用

Java 虚拟机 (JVM) 尝试将使用 javaagent 参数指定的类定位到 JVM,并在应用程序的入口点之前调用其 premain 方法。

以下示例使用 Byte Buddy,这是一个库,用于在 Java 应用程序的运行时期间创建和修改 Java 类,而无需编译器的帮助。Byte Buddy 提供了一个额外的 API 来生成 Java 代理。在此示例中,Agent 类将拦截对 RequestStreamHandler 类进行的每一次 handleRequest 方法调用。此类在运行时内部使用,用于包装处理程序调用。

import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import net.bytebuddy.matcher.ElementMatchers; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String agentArgs, Instrumentation inst) { new AgentBuilder.Default() .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager()) .type(ElementMatchers.isSubTypeOf(RequestStreamHandler.class)) .transform((builder, typeDescription, classLoader, module) -> builder .method(ElementMatchers.nameContains("handleRequest")) .intercept(Advice.to(TimerAdvice.class))) .installOn(inst); } }

前面示例中的代理使用 TimerAdvice 方法。TimerAdvice 测量方法调用所花的毫秒数,并记录方法时间和详细信息,例如名称和传递的参数。

import static net.bytebuddy.asm.Advice.AllArguments; import static net.bytebuddy.asm.Advice.Enter; import static net.bytebuddy.asm.Advice.OnMethodEnter; import static net.bytebuddy.asm.Advice.OnMethodExit; import static net.bytebuddy.asm.Advice.Origin; public class TimerAdvice { @OnMethodEnter static long enter() { return System.currentTimeMillis(); } @OnMethodExit static void exit(@Origin String method, @Enter long start, @AllArguments Object[] args) { StringBuilder sb = new StringBuilder(); for (Object arg : args) { sb.append(arg); sb.append(", "); } System.out.println(method + " method with args: " + sb.toString() + " took " + (System.currentTimeMillis() - start) + " milliseconds "); } }

上面的 TimerAdvice 方法具有以下依赖关系。

*'com.amazonaws'*, *name*: *'aws-lambda-java-core'*, *version*: *'1.2.1'* *'net.bytebuddy'*, *name*: *'byte-buddy-dep'*, *version*: *'1.10.14'* *'net.bytebuddy'*, *name*: *'byte-buddy-agent'*, *version*: *'1.10.14'*

创建包含代理 JAR 的层后,可以通过设置环境变量将 JAR 名称传递给运行时的 JVM。

JAVA_TOOL_OPTIONS=-javaagent:"/opt/ExampleAgent-0.0.jar"

使用 {key=lambdaInput} 调用函数后,您可以在日志中找到以下行:

public java.lang.Object lambdainternal.EventHandlerLoader$PojoMethodRequestHandler.handleRequest (java.lang.Object,com.amazonaws.services.lambda.runtime.Context) method with args: {key=lambdaInput}, lambdainternal.api.LambdaContext@4d9d1b69, took 106 milliseconds

示例:将关闭挂钩添加到 JVM 运行时进程

在 <code>Shutdown</code> 事件期间注册扩展时,运行时进程最多可获得 500 毫秒来处理正常关闭。您可以挂钩到运行时进程,当 JVM 开始关闭进程时,它会启动所有注册的挂钩。要注册关闭挂钩,您必须注册为扩展。您不需要显式注册 <code>Shutdown</code> 事件,因为该事件会自动发送到运行时。

import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String agentArgs, Instrumentation inst) { // Register the extension. // ... // Register the shutdown hook addShutdownHook(); } private static void addShutdownHook() { // Shutdown hooks get up to 500 ms to handle graceful shutdown before the runtime is terminated. // // You can use this time to egress any remaining telemetry, close open database connections, etc. Runtime.getRuntime().addShutdownHook(new Thread(() -> { // Inside the shutdown hook's thread we can perform any remaining task which needs to be done. })); } }

示例:检索 InvokedFunctionArn

@OnMethodEnter static long enter() { String invokedFunctionArn = null; for (Object arg : args) { if (arg instanceof Context) { Context context = (Context) arg; invokedFunctionArn = context.getInvokedFunctionArn(); } } }

包装脚本

您可以创建一个包装脚本来自定义 Lambda 函数的运行时启动行为。使用包装脚本,您可以设置无法通过特定于语言的环境变量设置的配置参数。

注意

如果包装脚本未成功启动运行时进程,则调用可能会失败。

以下 Lambda 运行时支持包装脚本:

  • Node.js 12.x

  • Node.js 10.x

  • Python 3.8

  • Ruby 2.7

  • Java 11

  • Java 8 (java8.al2)

  • .NET Core 3.1

当您为函数使用包装脚本时,Lambda 使用脚本启动运行时。Lambda 向脚本发送指向解释器的路径以及用于标准运行时启动的所有原始参数。您的脚本可以扩展或转换程序的启动行为。例如,脚本可以注入和更改参数、设置环境变量或捕获指标、错误和其他诊断信息。

您可以通过将 AWS_LAMBDA_EXEC_WRAPPER 环境变量的值设置为可执行二进制文件或脚本的文件系统路径来指定脚本。

示例:使用 Python 3.8 创建并使用包装脚本

在以下示例中,您创建一个包装脚本,以使用 -X importtime 选项启动 Python 解释器。当您运行该函数时,Lambda 生成一个日志条目,以显示每次导入的导入时间的持续时间。

使用 Python 3.8 创建并使用包装脚本

  1. 要创建包装脚本,请将以下代码粘贴到名为 importtime_wrapper 的文件中:

    #!/bin/bash # the path to the interpreter and all of the originally intended arguments args=("$@") # the extra options to pass to the interpreter extra_args=("-X" "importtime") # insert the extra options args=("${args[@]:0:$#-1}" "${extra_args[@]}" "${args[@]: -1}") # start the runtime with the extra options exec "${args[@]}"
  2. 要授予脚本可执行文件权限,请从命令行输入 chmod +x importtime_wrapper

  3. 将脚本部署为 Lambda 层

  4. 使用 Lambda 控制台创建函数。

    1. 打开 Lambda 控制台

    2. 选择创建函数

    3. 基本信息 下,对于函数名称,输入 wrapper-test-function

    4. 对于运行时,选择 Python 3.8

    5. 选择创建函数

  5. 将层添加到函数。

    1. 配置页面上,选择

    2. 选择 Add a layer

    3. 选择层下,选择您之前创建的兼容层的名称版本

    4. 选择 Add

  6. 将代码和环境变量添加到您的函数中。

    1. 在函数代码编辑器中,粘贴以下函数代码:

      import json def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
    2. 选择 Save

    3. 环境变量下,选择管理环境变量

    4. 选择 Add environment variable (添加环境变量)

    5. 对于,输入 AWS_LAMBDA_EXEC_WRAPPER

    6. 对于,输入 /opt/importtime_wrapper

    7. 选择 Save

  7. 要运行该函数,请选择测试

    由于您的包装脚本使用 -X importtime 选项启动了 Python 解释器,因此日志会显示每次导入所需的时间。例如:

    ... 2020-06-30T18:48:46.780+01:00 import time: 213 | 213 | simplejson 2020-06-30T18:48:46.780+01:00 import time: 50 | 263 | simplejson.raw_json ...