Lambdaのカスタムランタイムで無限ループをなくしてみた #reinvent

先日のAWS re:Invent 2018において発表されたLambdaのカスタムランタイムによってAWS Lambda Custom Runtimes芸人 Advent Calendar 2018 - Qiitaが開催されるなど、みなさん好みの言語をLambda上で実行することを試されているかと思います。今回は視点を変えて、カスタムランタイムの無限ループをなくすとどうなるのかを試してみたいと思います。

結論

先に結論を書いておきます。カスタムランタイムの無限ループをなくしても当然ですがLambda関数自体は実行されますし、CloudWatch Logsにログも出力されます。ただし、X-Rayは正常に動作しませんでした。

また、今回は検証できていませんが、リクエスト毎にbootstrapの初期化処理を行うことになるため、レスポンスタイムが増えることも予想されます。そのため、本番環境で利用するカスタムランタイムを作成する際は、関数本体で処理中にエラーが発生しても無限ループ自体は継続できるようにエラー処理の実装が必要であると考えます1

前提

無限ループをなくした場合の挙動の確認が目的のためカスタムランタイム自体はチュートリアルのBashのサンプルを利用します。また、影響範囲の確認という観点でX-Rayも有効化しています。

X-Rayの有効化

X-Rayを有効化できるように作成するLambda関数にはAWSLambdaBasicExecutionRole管理ポリシーだけでなくAWSXrayWriteOnlyAccess管理ポリシーも付与してください。詳細はLambda での AWS X-Ray のセットアップ - AWS Lambdaおよび【アップデート】AWS X-Ray が AWS Lambda に対応しました(プレビュー) | DevelopersIOを参照してください。

正常系の確認

無限ループをなくした場合に正常な場合と比較できるよう、まずはTutorial – Publishing a Custom Runtime - AWS Lambdaの内容をそのまま実施して、以下の4点について確認したいと思います。

  • レスポンス
  • X-Ray
  • CloudWatch Logs
  • CloudWatch

まずは基本となるチュートリアルのbootstrapのサンプルソースです。

#!/bin/sh

set -euo pipefail

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Execute the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done

またfunction.shの内容は以下のとおりです。

function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Echoing request: '$EVENT_DATA'"

  echo $RESPONSE
}

手順通りruntime-tutorialディレクトリを作成し、ZIPファイルを作成します。詳細な手順は公式ドキュメントおよびAWS Lambda の新機能でサーバーレス・シェルスクリプト! カスタムランタイムのチュートリアルを動かしてみた #reinvent | DevelopersIOを参照してください。

$ zip function.zip function.sh bootstrap
updating: function.sh (deflated 24%)
updating: bootstrap (deflated 39%)

作成したZIPファイルを元にLambda関数を作成します。この際--tracing-configオプションを指定し、X-Rayを有効化するようにしてください。

$ aws lambda create-function --function-name bash-runtime \
--zip-file fileb://function.zip --handler function.handler --runtime provided \
--role arn:aws:iam::123456789012:role/lambda_basic_execution_with_xray \
--tracing-config Mode=Active
{
    "FunctionName": "bash-runtime",
    "FunctionArn": "arn:aws:lambda:ap-northeast-1: 123456789012:function:bash-runtime",
    "Runtime": "provided",
    "Role": "arn:aws:iam::123456789012:role/lambda_basic_execution_with_xray",
    "Handler": "function.handler",
    "CodeSize": 830,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2018-12-10T10:20:26.140+0000",
    "CodeSha256": "mv/xRv84LPCxdpcbKvmwuuFzwo7sLwUO1VxcUv3wKlM=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "Active"
    },
    "RevisionId": "2e1d51b0-6144-4763-8e5c-7d5672a01713"
}

Lambda関数ができたので実際に実行してみます。普通に動作しますね。

$ aws lambda invoke --function-name bash-runtime --payload '{"text":"Hello"}' response.txt
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}
$ cat response.txt
Echoing request: '{"text":"Hello"}'

次はX-Rayです。きれいに表示されていますね。

CloudWatch Logsにログも出力されています。

最後にCloudWatchです。こちらは見づらいかもしれませんが、特にエラーなどもありません。

無限ループをなくした場合

正常な動作も確認できたので、次は無限ループをなくした場合にどのような結果になるのか確認したいと思います。bootstrapのソースから無限ループ部分をコメントアウトしてZIPファイルを作成します。

#!/bin/sh

set -euo pipefail

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
#while true
#do
  HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Execute the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
#done

ZIPファイルを上書きして作成します。

$ zip function.zip function.sh bootstrap
updating: function.sh (deflated 24%)
updating: bootstrap (deflated 38%)

bash-runtime-without-loopという名前で関数を作成します。

$ aws lambda create-function --function-name bash-runtime-without-loop \
--zip-file fileb://function.zip --handler function.handler --runtime provided \
--role arn:aws:iam::123456789012:role/lambda_basic_execution_with_xray \
--tracing-config Mode=Active
# レスポンスは正常時とほぼ差がないため割愛します

ではLambda関数を実行します。分かりやすいようにペイロードも変更しておきます。実行すると"FunctionError": "Unhandled"というエラーが発生していることが分かります。ただし、"StatusCode": 200であり、response.txtの中身も正常に動作していることが分かります。

$ aws lambda invoke --function-name bash-runtime-without-loop --payload '{"text":"Hello-without-loop"}' response.txt
{
    "StatusCode": 200,
    "FunctionError": "Unhandled",
    "ExecutedVersion": "$LATEST"
}
[runtime-tutorial]$ cat response.txt
Echoing request: '{"text":"Hello-without-loop"}'

次はX-Rayです。Statusがアラート表示され、InitializationやInvocationの情報が取得できていないことが分かります。

CloudWatch Logsにログは出力されています。ただし、最終行にProcess exited before completing requestというログが追加で出力されています。

最後にCloudWatchです。こちらは見づらいかもしれませんが、Errors, Availabilityに正常時とは逆にErrosが1件記録されていることが分かります。

最後に

いかがだったでしょうか。無限ループをなくしたとはいえ、function.shとInvocation ResponseのAPI自体は実行しているので処理は実行されますし、クライアントにレスポンスも返ってくることが分かりました2。後は関数本体でエラーが発生した際のエラーハンドリング方法に関して実装例が公開されるとよいなと考えます。


  1. ただし、公式ドキュメントに指針は記載されていても具体的な実装例は例示されていないようです。 
  2. 最初に検証した際はCloudWatch Logsにログが出力されなかったのですが、こちらはブログ執筆中に修正されたようでログが出力されるようになりました。そのため、X-Rayに関しても動作するような改修が入るかもしれません。