このエントリについて
- こちらのエントリ と同じく、以前残したメモを供養する目的で書きました
- LocalStack はv3がリリース済みですが、ここに書いたのはv2の使用時に調べた設定やコマンドです。v3のコンテナでも通用することを確認したうえで書いていますが、v3に最適化したわけでないことはご承知おきください
- LocalStack の CloudWatch Logs を使って動作確認していますが、操作は全てCLIでやっています。コンソールなどで確認できるのかも知れませんが、そのあたりは調べきれておらず、割愛させてください
-
aws lambda
のコマンドリファレンスは こちら でご確認下さい
ソースコード
以下の関数を用意
関数名を "simple_function" に、ファイル名を "simple_function.py" とした
import json
def lambda_handler(event, context):
value1 = event['value1']
value2 = event['value2']
print(f'[CALLED] simple_function: value1 = {value1}, value2 = {value2}')
try:
print(f'value1 + value2 = {int(value1) + int(value2)}')
result = True
except ValueError as e:
print(f'ERROR: {e}')
result = False
return json.dumps({"result": result})
関数の作成
zipファイルを使って関数を1つ作成する方法で実施
とはいえ基本的には、以下のどちらでやるか選ぶだけで、やることは実環境と変わらない
-
aws
コマンド実行時に--profile
と--endpoint-url
を指定 -
aws
ではなくawslocal
コマンドでlambda
やlogs
を実行
作成
-
zip
コマンドで関数のソースコードをアーカイブ- パッケージを含めないなら、対象のファイルだけを
zip
すればよい
- パッケージを含めないなら、対象のファイルだけを
-
lambda create-function
を発行して、zipファイルから関数をデプロイaws lambda --profile xxxxx --endpoint-url xxxxx create-function
-
awslocal lambda create-function
create-function
実行時の注意点
-
--role
をARNで指定(必須)- 指定する文字列は、ARNのフォーマットにさえ合致していれば適当でOK
- LocalStack v1 時代はフォーマット関係なく任意の文字列でOKだった。v1の頃のサンプルもネットに転がっているが、それらはv2以降だと使えないことに注意
- 詳しくはこちらを参照
- 上記のページには『The legacy Lambda implementation has been removed since LocalStack 3.0 (Docker
latest
since 2023-11-09)』とあるので、原則ARNを指定すると考えておいた方がよさそう
-
--timeout
の指定(推奨)- 実環境の
lambda create-function
と同じく、指定しないとデフォルトである 3秒 が設定される。これより大きな値を設定しておいた方が無難 - 実行にかかった時間が
--timeout
に設定した時間を超過すると、「Task timed out after 3.00 seconds」と出るだけで結果が分からない - 例外の発生源に
try
-except
を仕掛けてログ出しなどをしても、そこに到達するまで3秒を超えるとタイムアウトするので、ログが出ず何が起きて失敗したのかが判別できない - 他のAWSリソースに接続するなら3秒を超えることは十分あり得る。デバッグに支障が出る可能性を考慮すると、ある程度の時間を設定するのが無難かと
- 実環境の
関数の状態を確認
以下のコマンドを使って最新のデプロイ状態を確認できる
-
lambda list-functions
- デプロイ済みの関数一覧を表示
-
lambda get-function --function-name xxxxx
- 任意の関数について、関数名を指定してその詳細を表示
(例) 関数の作成〜状態確認
コンテナの外から aws
コマンドで実行
-
aws lambda list-functions
で、既存の関数が無いことを確認 -
aws lambda create-function
で、zipファイルから関数を作成 -
aws lambda list-functions
を改めて実行し、目的の関数が作成されていることを確認 -
aws lambda get-function
で目的の関数の詳細を確認-
create-function
直後のState
はPending
なので、ここでActive
になっていることを確認
-
% aws lambda --profile localstack --endpoint-url http://localhost:4566 list-functions
{
"Functions": []
}
% aws lambda create-function \
--profile localstack \
--endpoint-url http://localhost:4566 \
--function-name simple_function \
--runtime python3.12 \
--zip-file fileb:///Users/paratroops74/workspaces/aws/LocalLambda/simple_function.zip \
--handler simple_function.lambda_handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--timeout 30
{
"FunctionName": "simple_function",
"FunctionArn": "arn:aws:lambda:ap-northeast-3:000000000000:function:simple_function",
...
"State": "Pending",
...
}
% aws lambda --profile localstack --endpoint-url http://localhost:4566 list-functions
{
"Functions": [
{
"FunctionName": "simple_function",
"FunctionArn": "arn:aws:lambda:ap-northeast-3:000000000000:function:simple_function",
...
"LoggingConfig": {
"LogFormat": "Text",
"LogGroup": "/aws/lambda/simple_function"
}
}
]
}
% aws lambda --profile localstack --endpoint-url http://localhost:4566 get-function --function-name simple_function
{
"Configuration": {
"FunctionName": "simple_function",
"FunctionArn": "arn:aws:lambda:ap-northeast-3:000000000000:function:simple_function",
...
"State": "Active",
...
},
"Code": {
...
}
}
コンテナに入って awslocal
コマンドで実行
-
awslocal lambda list-functions
で、コンテナの外から実行したのと同じ結果が得られることを確認
% docker container exec -it localstack-main /bin/bash
# awslocal lambda list-functions
{
"Functions": [
{
"FunctionName": "simple_function",
"FunctionArn": "arn:aws:lambda:ap-northeast-3:000000000000:function:simple_function",
"Runtime": "python3.12",
"Role": "arn:aws:iam::000000000000:role/lambda-role",
"Handler": "simple_function.lambda_handler",
"CodeSize": 409,
"Description": "",
"Timeout": 30,
...
}
]
}
関数の実行と結果確認
これも aws
に --profile
と --endpoint-url
をつけて実行するか、 aws
でなく awslocal
で実行するか選ぶだけで、基本的にやることは実環境と変わらない
実行
-
lambda invoke
で関数を実行- ここでは
--invocation-type RequestResponse
として、同期的に実行している
- ここでは
(例) 関数の実行とレスポンスの確認
コンテナに入って awslocal
で実行
- 正常終了する値を与えた場合
# awslocal lambda invoke \
--function-name simple_function \
--invocation-type RequestResponse \
--payload '{"value1":"10","value2":"20"}' \
/tmp/response_simple_function.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
# cat /tmp/response_simple_function.json
"{\"result\": true}"
- 例外になる値を与えた場合
# awslocal lambda invoke \
--function-name simple_function \
--invocation-type RequestResponse \
--payload '{"value1":"hoge","value2":"fuga"}' \
/tmp/response_simple_function_err.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
# cat /tmp/response_simple_function_err.json
"{\"result\": false}"
結果の確認
CloudWatch Logs で出力を確認したければ、 SERVICES
環境変数に logs
を含めること
SERVICES=lambda,logs
-
lambda invoke
の実行時、レスポンスの出力先に指定したファイルの中身を確認- 期待したレスポンスが返ってきているか
-
logs
コマンドで CloudWatch Logs に出力された内容を確認- 今回はシンプルに
print()
でログに出力しているので、期待した内容が出力されているか -
describe-log-groups
でロググループを、describe-log-streams
でログストリームを特定して、get-log-events
でログイベントの内容を確認
- 今回はシンプルに
(例) 実行確認の確認
コンテナに入って awslocal
で実行
- 正常終了する値を与えた場合
# awslocal logs describe-log-groups
{
"logGroups": [
{
"logGroupName": "/aws/lambda/simple_function",
"creationTime": 1721635186611,
...
}
]
}
# awslocal logs describe-log-streams --log-group-name '/aws/lambda/simple_function'
{
"logStreams": [
{
"logStreamName": "2024/07/22/[$LATEST]30951136d89e941f531bdc375a500dfc",
"creationTime": 1721635186613,
...
}
]
}
# awslocal logs get-log-events --log-group-name '/aws/lambda/simple_function' --log-stream-name '2024/07/22/[$LATEST]30951136d89e941f531bdc375a500dfc'
{
"events": [
{
"timestamp": 1721635186581,
"message": "START RequestId: 8744aa43-547c-4140-b9af-dbcd349f7183 Version: $LATEST",
"ingestionTime": 1721635186615
},
{
"timestamp": 1721635186586,
"message": "[CALLED] simple_function: value1 = 10, value2 = 20",
"ingestionTime": 1721635186615
},
{
"timestamp": 1721635186592,
"message": "value1 + value2 = 30",
"ingestionTime": 1721635186615
},
{
"timestamp": 1721635186597,
"message": "END RequestId: 8744aa43-547c-4140-b9af-dbcd349f7183",
"ingestionTime": 1721635186615
},
{
"timestamp": 1721635186603,
"message": "REPORT RequestId: 8744aa43-547c-4140-b9af-dbcd349f7183\tDuration: 5.89 ms\tBilled Duration: 6 ms\tMemory Size: 128 MB\tMax Memory Used: 128 MB\t",
"ingestionTime": 1721635186615
}
],
...
}
- 例外になる値を与えた場合
- 正常終了させたあとに実行したのでロググループは既存。そのため
describe-log-groups
は割愛
- 正常終了させたあとに実行したのでロググループは既存。そのため
# awslocal logs describe-log-streams --log-group-name '/aws/lambda/simple_function'
{
"logStreams": [
{
"logStreamName": "2024/07/22/[$LATEST]30951136d89e941f531bdc375a500dfc",
"creationTime": 1721635186613,
...
},
{
"logStreamName": "2024/07/22/[$LATEST]c98be5efdc03dd4338b92565869e6013",
"creationTime": 1721636530097,
...
}
]
}
# awslocal logs get-log-events --log-group-name '/aws/lambda/simple_function' --log-stream-name '2024/07/22/[$LATEST]c98be5efdc03dd4338b92565869e6013'
{
"events": [
{
"timestamp": 1721636530093,
"message": "START RequestId: 162e74ab-8465-4d74-a4d3-bdb70539a1d9 Version: $LATEST",
"ingestionTime": 1721636530098
},
{
"timestamp": 1721636530093,
"message": "[CALLED] simple_function: value1 = hoge, value2 = fuga",
"ingestionTime": 1721636530098
},
{
"timestamp": 1721636530094,
"message": "ERROR: invalid literal for int() with base 10: 'hoge'",
"ingestionTime": 1721636530098
},
{
"timestamp": 1721636530095,
"message": "END RequestId: 162e74ab-8465-4d74-a4d3-bdb70539a1d9",
"ingestionTime": 1721636530098
},
{
"timestamp": 1721636530095,
"message": "REPORT RequestId: 162e74ab-8465-4d74-a4d3-bdb70539a1d9\tDuration: 6.02 ms\tBilled Duration: 7 ms\tMemory Size: 128 MB\tMax Memory Used: 128 MB\t",
"ingestionTime": 1721636530098
}
],
...
}