0
0

LocalStack に Lambda 関数を作成して実行する

Posted at

このエントリについて

  • こちらのエントリ と同じく、以前残したメモを供養する目的で書きました
  • 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 コマンドで lambdalogs を実行

作成

  1. zip コマンドで関数のソースコードをアーカイブ
    • パッケージを含めないなら、対象のファイルだけを zip すればよい
  2. 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 コマンドで実行

  1. aws lambda list-functions で、既存の関数が無いことを確認
  2. aws lambda create-function で、zipファイルから関数を作成
  3. aws lambda list-functions を改めて実行し、目的の関数が作成されていることを確認
  4. aws lambda get-function で目的の関数の詳細を確認
    • create-function 直後の StatePending なので、ここで 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 コマンドで実行

  1. 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 で実行するか選ぶだけで、基本的にやることは実環境と変わらない

実行

  1. 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
  1. lambda invoke の実行時、レスポンスの出力先に指定したファイルの中身を確認
    • 期待したレスポンスが返ってきているか
  2. 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
        }
    ],
    ...
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0