ランタイム環境の変更 - AWS Lambda

ランタイム環境の変更

内部拡張機能を使用して、ランタイムプロセスを変更できます。内部拡張機能は個別のプロセスではなく、ランタイムプロセスの一部として実行されます。

Lambda には、ランタイムにオプションやツールを追加するために設定できる言語固有の環境変数が用意されています。また、Lambda には、Lambda がランタイムの起動をスクリプトに委譲できるようにするラッパースクリプトも用意されています。ラッパースクリプトを作成して、ランタイムの起動動作をカスタマイズできます。

言語固有の環境変数

Lambda は、次の言語固有の環境変数を通じて、関数の初期化中にコードをプリロードできるように設定専用の方法をサポートしています。

  • JAVA_TOOL_OPTIONS – Java 11 および Java 8 (java8.al2) では、Lambda はこの環境変数をサポートし、Lambda で追加のコマンドライン変数を設定します。この環境変数では、ツールの初期化を指定できます。具体的には、agentlib または javaagent オプションを使用して、ネイティブまたは Java プログラミング言語エージェントの起動を指定できます。

  • NODE_OPTIONS – Node.js 10x 以降では、Lambda はこの環境変数をサポートします。

  • DOTNET_STARTUP_HOOKS –.NET Core 3.1 以降では、この環境変数は、Lambda が使用できるアセンブリ (dll) へのパスを指定します。

起動プロパティを設定するには、言語固有の環境変数を使用することをお勧めします。

例: javaagent で Lambda 呼び出しをインターセプトする

Java Virtual Machine (JVM) は、JVM に対して javaagent パラメータで指定されたクラスを探し、アプリケーションのエントリポイントの前に premain メソッドを呼び出します。

次の例では、Byte Buddy を使用しています。これは、コンパイラの支援を受けることなく Java アプリケーションのランタイム時に Java クラスを作成および変更するためのライブラリです。Byte Buddy は、Java エージェントを生成するための追加の API を提供しています。この例では、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 ランタイムプロセスへのシャットダウンフックの追加

Shutdown イベント中に拡張機能が登録されると、ランタイムプロセスがグレースフルシャットダウンを処理するために、最大 500 ms がかかります。ランタイムプロセスにフックすることができます。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.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. [Create function] を選択します。

    3. [基本的な情報] の [関数名] に「wrapper-test-function」と入力します。

    4. [Runtime (ランタイム)] で [Python 3.8] を選択します。

    5. [関数の作成] を選択します。

  5. レイヤーを関数に追加します。

    1. 関数を選択し、[ コード ] が選択されていない場合、選択します。

    2. [Add a layer] を選択します。

    3. [Choose a layer (レイヤーの選択)] で、前に作成した互換性のあるレイヤーの [名前] と [バージョン] を選択します。

    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. [環境変数の追加] を選択します

    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 ...