これは、なにをしたくて書いたもの?
LocalStackのドキュメントを見ていて、AWS Lambdaのリモートデバッグについて記載があるのに気づきまして。
このドキュメントに書かれているのはPythonとJavaなのですが、読んでいてNode.jsでもできるのでは?と思って試してみました。
結論
LocalStackのLAMBDA_DOCKER_FLAGS
環境変数を使い、AWS Lambda関数のDockerコンテナ実行時にオプションを指定します。
Configuration / Local AWS Services / Lambda
指定するのは-p [port]:[port]
、それからNODE_OPTIONS
環境変数です。NODE_OPTIONS
環境変数には--inspect
や--inspect-brk
、
--inspect-port
オプションを指定します。
Command-line API | Node.js v16.16.0 Documentation
Google Chrome DevTools等のツールで、デバッグできるようになるオプションですね。
--inspect
や--inspect-brk
、--inspect-port
を指定する際には、0.0.0.0
にバインドするところがポイントです。また、-p
オプションで
指定するポートも--inspect
や--inspect-brk
、--inspect-port
に合わせる必要があります。
つまり、こうなります。
LAMBDA_DOCKER_FLAGS='-p 9229:9229 -e NODE_OPTIONS="--inspect=0.0.0.0:9229"'
この発想に至ったのは、こちらのドキュメントに書かれているリモートデバッグの方法を見て、同じようにリモートデバッグできる言語なら
環境変数経由でオプションを指定すればできるのでは?と思ったからですね。
では、実際に試してみましょう。
環境
今回の環境は、こちらです。
$ localstack --version 1.0.0
AWS Lambda関数の作成には、AWS SAMとLocalStack提供のCLIを使うことにします。
$ samlocal --version SAM CLI, version 1.36.0
AWS Lambda関数を作成する
今回のAWS Lambda関数は、AWS SAM CLIで生成できるデフォルトのものを使うことにします。
$ samlocal init --name localstack-nodejs-lambda-debug --runtime nodejs14.x --app-template hello-world --package-type Zip
プロジェクト内へ移動。
$ cd localstack-nodejs-lambda-debug
主要な生成されたファイルを確認。
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > localstack-nodejs-lambda-debug Sample SAM Template for localstack-nodejs-lambda-debug # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello-world/ Handler: app.lambdaHandler Runtime: nodejs14.x Architectures: - x86_64 Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
hello-world/package.json
{ "name": "hello_world", "version": "1.0.0", "description": "hello world sample for NodeJS", "main": "app.js", "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", "author": "SAM CLI", "license": "MIT", "dependencies": { "axios": "^0.21.1" }, "scripts": { "test": "mocha tests/unit/" }, "devDependencies": { "chai": "^4.2.0", "mocha": "^9.1.4" } }
hello-world/app.js
// const axios = require('axios') // const url = 'http://checkip.amazonaws.com/'; let response; /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format * * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html * @param {Object} context * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format * */ exports.lambdaHandler = async (event, context) => { try { // const ret = await axios(url); response = { 'statusCode': 200, 'body': JSON.stringify({ message: 'hello world', // location: ret.data.trim() }) } } catch (err) { console.log(err); return err; } return response };
app.js
がソースコード本体ですね。
これで、AWS Lambda関数のソースコードの準備は完了です。
LocalStackを起動して、AWS Lambda関数をデプロイ、デバッグする
では、LocalStackを起動します。
$ LAMBDA_EXECUTOR=docker-reuse LAMBDA_DOCKER_FLAGS='-p 9229:9229 -e NODE_OPTIONS="--inspect=0.0.0.0:9229"' localstack start ## もしくは、こちら $ LAMBDA_DOCKER_FLAGS='-p 9229:9229 -e NODE_OPTIONS="--inspect-brk=0.0.0.0:9229"' localstack start
何回もデプロイする場合は、--inspect-brk
を使った方がよいかもしれません。
なお、デフォルトのタイムアウト設定ではデバッグしている間にタイムアウトすると思うので、こちらの値は調整しておいた方が良いでしょう。
Globals: Function: Timeout: 3
AWS Lambda関数をデプロイ。
$ yes | samlocal sync --stack-name $(uuidgen) --region us-east-1
デプロイが完了しました。
CloudFormation outputs from deployed stack ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://he20jha0yf.execute-api.amazonaws.com:4566/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:us-east-1:000000000000:function:19c4bea0-0970-404d-908c-ac1bd01f520-HelloWorldFunction-fc689a38 Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::000000000000:role/19c4bea0-0970-404d-908c-ac1bd01-HelloWorldFunctionRole-2f771769 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Stack creation succeeded. Sync infra completed. {'StackId': 'arn:aws:cloudformation:us-east-1:000000000000:stack/19c4bea0-0970-404d-908c-ac1bd01f5206/c5b866a6', 'ResponseMetadata': {'RequestId': '2WT2XE0560OEW4WLQTTLXB8DLX77TYAXAQR714J9P02OIVWT0HRD', 'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'text/xml', 'content-length': '409', 'access-control-allow-origin': '*', 'access-control-allow-methods': 'HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH', 'access-control-allow-headers': 'authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request', 'access-control-expose-headers': 'etag,x-amz-version-id', 'date': 'Fri, 15 Jul 2022 16:08:41 GMT', 'server': 'hypercorn-h11'}, 'RetryAttempts': 0}}
AWS Lambda関数にアクセスしてみます。
$ REST_API_ID=$(awslocal apigateway get-rest-apis --query 'reverse(sort_by(items[], &createdDate))[0].id' --output text) $ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello {"message":"hello world"}
動いていますね。
この状態でchrome://inspect
にアクセスすると、「Remote Target」でAWS Lambda関数を認識しているのが確認できます。
「inspect」を選択してみます。
この時点ではファイルはわからないので
「ファイルを開く」から
目的のファイルを探します。
あとは、開いたファイルに対してブレークポイントを貼って、アクセスすればOKです。
これで、LocalStackにデプロイしたAWS Lambda関数をGoogle Chrome DevToolsでデバッグできるようになりました。