修改运行时环境
您可以使用内部扩展来修改运行时进程。内部扩展不是单独的进程 – 其作为运行时进程的一部分运行。
Lambda 提供特定语言的环境变量,您可以设置此变量,向运行时添加选项和工具。Lambda 还提供包装程序脚本,此脚本允许 Lambda 将运行时启动委托给脚本。您可以创建包装脚本来自定义运行时启动行为。
特定于语言的环境变量
Lambda 支持仅配置方法,以便在函数初始化期间通过以下特定语言的环境变量预加载代码:
-
JAVA_TOOL_OPTIONS
– 在 Java 上,Lambda 支持此环境变量以在 Lambda 中设置其他命令行变量。此环境变量允许您指定工具的初始化,特别是使用agentlib
或javaagent
选项启动本机或 Java 编程语言代理。 -
NODE_OPTIONS
– 在 Node.js 10x 及更高版本中,Lambda 支持此环境变量。 -
DOTNET_STARTUP_HOOKS
– 在 .NET Core 3.1 及更高版本中,此环境变量指定了 Lambda 可以使用的程序集 (dll) 的路径。
使用特定于语言的环境变量是设置启动属性的首选方法。
示例:使用 javaagent
拦截 Lambda 调用
Java 虚拟机 (JVM) 尝试将使用 javaagent
参数指定的类定位到 JVM,并在应用程序的入口点之前调用其 premain
方法。
以下示例使用 Byte BuddyAgent
类将拦截对 RequestStreamHandlerhandleRequest
方法调用。此类在运行时内部使用,用于包装处理程序调用。
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 运行时进程
在 Shutdown
事件期间注册扩展时,运行时进程最多可在 500 毫秒内处理正常关闭。您可以挂钩到运行时进程,当 JVM 开始关闭进程时,它会启动所有注册的挂钩。要注册关闭挂钩,您必须注册为扩展。您不需要显式注册 Shutdown
事件,因为该事件会自动发送到运行时。
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 14.x
-
Node.js 12.x
-
Node.js 10.x
-
Python 3.9
-
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 创建并使用包装脚本
-
要创建包装脚本,请将以下代码粘贴到名为
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[@]}"
-
要授予脚本可执行文件权限,请从命令行输入
chmod +x importtime_wrapper
。 -
将脚本部署为 Lambda 层。
-
使用 Lambda 控制台创建 Lambda 函数。
-
打开 Lambda 控制台
。 -
选择 Create function(创建函数)。
-
在基本信息 下,对于函数名称,输入
wrapper-test-function
。 -
对于运行时,选择 Python 3.8。
-
选择 Create function(创建函数)。
-
-
将层添加到函数。
-
选择您的函数,然后选择 Code (代码)(如果尚未选择)。
-
选择 Add a layer。
-
在选择层下,选择您之前创建的兼容层的名称和版本。
-
选择 Add (添加)。
-
-
将代码和环境变量添加到您的函数中。
-
在函数代码编辑器中,粘贴以下函数代码:
import json def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
-
选择 Save(保存)。
-
在 Environment variables (环境变量) 下,选择 Edit (编辑)。
-
选择 Add environment variable (添加环境变量)。
-
对于 Key(键),输入
AWS_LAMBDA_EXEC_WRAPPER
。 -
对于 Value(值),输入
/opt/importtime_wrapper
。 -
选择 Save(保存)。
-
-
要运行该函数,请选择 Test(测试)。
由于您的包装脚本使用
-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 ...