CLOVER🍀

That was when it all began.

LocalStackを使って、AWS Lambdaを試してみる

これは、なにをしたくて書いたもの?

  • AWS Lambdaを試してみたいが、適当な環境がないのでとりあえずLocalStackで
  • AWS SAM CLIは、今回は気にしない

要するに、AWS Lambdaを代替でもいいからとりあえず手元で動かしてみたくて書いたエントリです。

お題

Node.jsでLambda関数を作成することとし、ペイロードで与えられたパラメータを含めて、メッセージを返却する
コードを作成します。

また、その時にnpmでモジュールを引き込んで使用します。

環境

コードを作成した環境は、Ubuntu Linux 18.04 LTS。

Node.js、npmのバージョンは、こちら。

$ node -v
v8.12.0


$ npm -v
6.4.1

ですが、実行環境はまた別の話ですね。

LocalStackは、Docker Composeで立てました。

オフィシャルのものを使用しても良かったのですが
localstack/docker-compose.yml at master · localstack/localstack · GitHub

自分で作ろうかなとこんな感じに。
docker-compose.yml

version: "3"
services:
  localstack:
    image: localstack/localstack:0.8.7
    ports:
      - "4567-4584:4567-4584"
      - "8080:8080"
    environment:
      - SERVICES=${SERVICES- }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - PORT_WEB_UI=${PORT_WEB_UI- }
      - LAMBDA_EXECUTOR=docker
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

LocalStackで、LambdaをNode.jsをランタイムとして動かす場合は、LAMBDA_EXECUTORにDockerを要求するみたいです。

Dockerを指定しない場合は、Lambdaの実行時にこんなエラーを見ることになります。

Exception: Unable to find executor for Lambda function "xxx". Note that Node.js and .NET Core Lambdas currently require LAMBDA_EXECUTOR=docker

なので、ホスト側のdocker.sockをVolumeとして見るようにしておきます。

起動。

$ docker-compose up

Lambdaのエンドポイントのポートは、4574です。また、このDockerが起動しているホストのIPアドレスは、「192.168.0.3」とします。

これで、準備は完了です。

Lambda関数を作成する

それでは、Lambda関数を作成しましょう。

とりあえず、なにかライブラリを使いたいので、文字列操作のライブラリでも使うことにします。

今回は、Vocaを使うことにしました。

Voca: The JavaScript string library

インストール。

$ npm i --save voca

バージョン。

  "dependencies": {
    "voca": "^1.4.0"
  }

ソースコードは、こんな感じで。
lambda.js

const voca = require('voca');

exports.myHandler = async (event, context) => {
    console.log(`event = ${JSON.stringify(event)}`);
    console.log(`context = ${JSON.stringify(context)}`);

    return `${voca.repeat(event.word, event.count)}!!!`;
};

このあたりを参考にしています。

Node.js の AWS Lambda 関数ハンドラー - AWS Lambda

Node.js の AWS Lambda context オブジェクト - AWS Lambda

Node.js の AWS Lambda 関数ログ作成 - AWS Lambda

AWS Lambda 実行環境 - AWS Lambda

AWS Lambda 関数を使用するためのベストプラクティス - AWS Lambda

Lambda関数の登録

では、この作成したLambda関数を、LocalStackのLambdaにデプロイしてみます。

まずは、デプロイパッケージの作成。

.zip ファイルアーカイブで Node.js Lambda 関数をデプロイする - AWS Lambda

とりあえず、全部まとめてzipに放り込みました。zipファイル名は、「dist.zip」とします。

$ zip -r dist.zip lambda.js node_modules package.json package-lock.json

では、こちらをLambdaに登録します。

参考にしたのは、Lambdaのドキュメントのユースケースより。

AWS Lambda

Lambda関数の登録。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda create-function \
   --function-name hello-lambda \
   --runtime nodejs8.10 \
   --zip-file fileb://`pwd`/dist.zip \
   --role test-role \
   --handler lambda.myHandler \
   --region us-east-1
{
    "TracingConfig": {}, 
    "FunctionName": "hello-lambda", 
    "VpcConfig": {
        "SubnetIds": [
            null
        ], 
        "SecurityGroupIds": [
            null
        ]
    }, 
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:hello-lambda", 
    "Environment": {
        "Variables": {}, 
        "Error": {}
    }, 
    "Handler": "lambda.myHandler", 
    "Role": "test-role", 
    "Runtime": "nodejs8.10"
}

LocalStackなので、「--endpoint-url」を指定する必要があります。

あとは、「--function-name」で登録する関数の名前、「--runtime」でランタイムを、「--zip-file」でデプロイ対象のzipファイルを、
「--role」でロールを、「--handler」で実行する関数を指定します。

実行する関数は、今回は「lambda.js」の「myHandler」という関数を登録したので「lambda.myHandler」となります。

exports.myHandler = async (event, context) => {

登録したLambda関数の一覧。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda list-functions
{
    "Functions": [
        {
            "Version": "$LATEST", 
            "FunctionName": "hello-lambda", 
            "CodeSize": 50, 
            "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:hello-lambda", 
            "Environment": {}, 
            "Handler": "lambda.myHandler", 
            "Runtime": "nodejs8.10"
        }
    ]
}

Lambda関数の呼び出し。「--payload」でパラメーターを渡します。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda invoke --function-name hello-lambda --region us-east1 --payload '{"word": "Lambda", "count": 5}' result.log

結果は、このコマンド例だと「result.log」に出力されます。

$ cat result.log

"LambdaLambdaLambdaLambdaLambda!!!"

次に、Lambda関数を更新してみましょう。returnする値を、少し変更してみます。

    return `${voca.repeat(event.word, event.count)}!!!???`;

再度zipにまとめて

$ zip -r dist.zip lambda.js node_modules package.json package-lock.json

Lambda関数定義の更新。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda update-function-code --function-name hello-lambda --zip-file fileb://`pwd`/dist.zip

実行。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda invoke --function-name hello-lambda --region us-east1 --payload '{"word": "Lambda", "count": 5}' result.log

定義が変更されました。

$ cat result.log 

"LambdaLambdaLambdaLambdaLambda!!!???"

Lambda関数の削除。

$ aws --endpoint-url=http://192.168.0.3:4574 lambda delete-function --function-name hello-lambda

ところで、最初の方でLocalStackでのLambdaでNode.jsを使う場合はDockerが必要だという話なのですが、Lambdaの実行の度に
コンテナが起動し、終了していきます。これ、「docker container ps -a」で見るとどんどん残っていくので、適当に削除が
必要ですね。

Lambdaのログを見るには、「docker container logs」で見ることになります。

$ docker container logs 430d2b75a324
START RequestId: 1d0e0e82-6c90-1718-3755-d4cd9431bbeb Version: $LATEST
2018-10-23T15:20:12.415Z    1d0e0e82-6c90-1718-3755-d4cd9431bbeb    event = {"count":5,"word":"Lambda"}
2018-10-23T15:20:12.416Z    1d0e0e82-6c90-1718-3755-d4cd9431bbeb    context = {"callbackWaitsForEmptyEventLoop":true,"logGroupName":"/aws/lambda/hello-lambda","logStreamName":"2018/10/23/[$LATEST]4ac47aebf4367f612e6e4d8a25b5f078","functionName":"hello-lambda","memoryLimitInMB":"1536","functionVersion":"$LATEST","invokeid":"1d0e0e82-6c90-1718-3755-d4cd9431bbeb","awsRequestId":"1d0e0e82-6c90-1718-3755-d4cd9431bbeb","invokedFunctionArn":"arn:aws:lambda:us-east-1:000000000000:function:hello-lambda"}
END RequestId: 1d0e0e82-6c90-1718-3755-d4cd9431bbeb
REPORT RequestId: 1d0e0e82-6c90-1718-3755-d4cd9431bbeb  Duration: 13.83 ms  Billed Duration: 100 ms Memory Size: 1536 MB    Max Memory Used: 32 MB  

"LambdaLambdaLambdaLambdaLambda!!!???"

「console.log」などの内容は、こんな感じで確認できます。

コンテナが残るので消すのは面倒なのですが、かといって終了時に消えてしまうと、ログを確認する手段がなくなって
しまうような気も…。難しい…。

まあ、確認としてはこんなところですね。

まとめ

LocalStackを使って、AWS Lambdaを試してみました。

AWS Lambda自体使ったことがなかったので、まずはとっかかりということで。Lambdaの実行にDockerを使うところで
若干つまづきましたが、それ以外は割とあっさり。

良い素振りになりました。