CLOVER🍀

That was when it all began.

AWS Lambdaをaws-sam-cliを使ってローカルで動かす

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

  • AWS Lambdaを、aws-sam-cliを使ってローカルで動かせるらしいので
  • ちょっと試してみようかと

AWS SAM Local(ベータ版) – サーバーレスアプリケーションをローカルに構築してテストする | Amazon Web Services ブログ

aws-sam-localから、今はaws-sam-cliになったみたいです。

以前にLocalStackを使って、ローカルでAWS Lambdaを動かしたことはありましたが、今度はAWSの提供するツールで
行ってみたいと思います。

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

aws-sam-cliのインストール

まずは、aws-sam-cliのインストールを行いましょう。

最初に、Linux用のインストール方法を見たのですが、Linuxbrew…。

Installing the AWS SAM CLI on Linux - AWS Serverless Application Model

他に見ると、pipでインストールする方法もあるようなので、こちらを使うことにしました。

Additional Installation Topics - AWS Serverless Application Model

venvを使って、仮想環境にインストールすることにしました。

$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install aws-sam-cli

確認。

$ sam --version
SAM CLI, version 0.11.0

これで、aws-sam-cliが使えるようになりました。

なお、aws-sam-cliを使ってローカルでAWS Lambdaを動かす場合、Dockerが必要になるのでインストールしておきましょう。

lambci/lambda

使い方については、GitHubにドキュメントがあるので、こちらを参照。

GitHub - awslabs/aws-sam-cli: AWS SAM CLI 🐿 is a CLI tool for local development and testing of Serverless applications

https://github.com/awslabs/aws-sam-cli/blob/v0.11.0/docs/usage.md

https://github.com/awslabs/aws-sam-cli/blob/v0.11.0/docs/advanced_usage.md

JavaでAWS Lambdaを書いてみる

このあたりを見つつ、まずはJavaでAWS Lambdaをを使うアプリケーションを書いてみましょう。

環境。

$ java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.18.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)


$ mvn -version
Apache Maven 3.6.0 (97c98ec64a1fdfee7767ce5ffb20918da4f719f3; 2018-10-25T03:41:47+09:00)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 1.8.0_191, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-45-generic", arch: "amd64", family: "unix"

Mavenの設定は、こちらを参考に。

Maven および Eclipse IDE を使用した .jar デプロイパッケージの作成 (Java) - AWS Lambda

Maven依存関係。

        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-lambda-java-core</artifactId>
            <version>1.2.0</version>
        </dependency>

ライブラリ依存関係も含めてひとつのJARファイルにするために、Maven Shade Pluginも適用しておきます。

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

実装方法は、以下の2つがあるようです。

ハンドラーの入出力タイプ (Java) - AWS Lambda

ハンドラ作成用に事前定義されているインターフェイスの利用 (Java) - AWS Lambda

今回は、RequestHandlerインターフェースを実装して作成してみましょう。

こんな感じで。
src/main/java/org/littlewings/aws/lambda/MyLambdaFunction.java

package org.littlewings.aws.lambda;

import java.time.LocalDateTime;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class MyLambdaFunction implements RequestHandler<MyLambdaFunction.Request, MyLambdaFunction.Response> {
    @Override
    public Response handleRequest(Request request, Context context) {
        System.out.printf("[%s] request = %s %s%n", LocalDateTime.now(), request.getLastName(), request.getFirstName());

        return new Response("Hello, " + request.getLastName() + " " + request.getFirstName() + "!!");
    }

    public static class Request {
        String firstName;
        String lastName;

        public String getFirstName() {
            return firstName;
        }

        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    }

    public static class Response {
        String message;

        public Response() {
        }

        public Response(String message) {
            this.message = message;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }
    }
}

パッケージング。

$ mvn package

aws-sam-cliでAWS Lambdaをローカルで動かすには、テンプレートファイルが必要なようです。

今回は、このようなものを作成。 template.yml

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  MyLambdaJavaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: org.littlewings.aws.lambda.MyLambdaFunction
      CodeUri: ./target/aws-lambda-local-java-example-0.0.1-SNAPSHOT.jar
      Runtime: java8

Resources配下のFunction名は任意の名前です。PropertiesのHandlerには、AWS Lambdaから呼び出される起点となる
クラスを、CodeUriには作成したJARファイルのパスを、Runtimeには実行環境となるDockerイメージのタグを指定します。

使用できる言語のランタイム、バージョンは、GitHubのREADME.mdで確認することができます。

GitHub - awslabs/aws-sam-cli: AWS SAM CLI 🐿 is a CLI tool for local development and testing of Serverless applications

実行はsam local invokeで行います。今回はAWS CLIなしで実行しているので、「--region」でリージョンを指定しています。
リクエストの内容は、標準出力経由でJSONで渡せばOKです。

$ echo '{"firstName": "カツオ", "lastName": "磯野"}' | sam local invoke --region us-west-1

ランタイムのDockerイメージが起動して、AWS Lambdaが動きます。

Fetching lambci/lambda:java8 Docker container image......
2019-03-01 00:03:52 Mounting /tmp/tmp9j3o9vgu as /var/task:ro inside runtime container
START RequestId: dcf625f3-f02d-45bf-8803-e270e282679b Version: $LATEST
END RequestId: dcf625f3-f02d-45bf-8803-e270e282679b
REPORT RequestId: dcf625f3-f02d-45bf-8803-e270e282679b  Duration: 28.98 ms  Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 17 MB  
[2019-02-28T15:03:54.057] request = 磯野 カツオ

{"message":"Hello, 磯野 カツオ!!"}

OKですね。

sam local invokeで指定できるオプションは、以下のページや

sam local invoke - AWS Serverless Application Model

ヘルプで確認できます。

$ sam local invoke --help

なお、テンプレートファイルは「--template」で指定することもできますし(デフォルトは「template.yaml」または「template.yml」)、

  -t, --template PATH             AWS SAM template file  [default:
                                  template.[yaml|yml]]

リクエストのJSONの内容をファイルで渡すこともできます。

  -e, --event PATH                JSON file containing event data passed to
                                  the Lambda function during invoke. If this
                                  option is not specified, we will default to
                                  reading JSON from stdin

環境変数を渡したりもできるので、確認してみるとよいでしょう。

特に実行が高速なわけではないですが、ローカルでも動かせるというのはよいですね。

オマケ:Node.js

オマケとして、Node.jsでも動かしてみました。

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

作成したLambda関数。
lambda.js

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

    return {"message": `Hello ${event.lastName} ${event.firstName}!!`};
}

テンプレートファイル。
template.yml

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  MyLambdaNodeJsFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda.myHandler
      Runtime: nodejs8.10

実行。

$ echo '{"firstName": "カツオ", "lastName": "磯野"}' | sam local invoke --region us-west-1

結果。

Fetching lambci/lambda:nodejs8.10 Docker container image......
2019-03-01 00:12:51 Mounting /path/to as /var/task:ro inside runtime container
START RequestId: 24ca0313-f3fd-110d-7525-c885abc6bdb7 Version: $LATEST
2019-02-28T15:12:54.894Z    24ca0313-f3fd-110d-7525-c885abc6bdb7    event = {"firstName":"カツオ","lastName":"磯野"}
2019-02-28T15:12:54.895Z    24ca0313-f3fd-110d-7525-c885abc6bdb7    context = {"callbackWaitsForEmptyEventLoop":true,"logGroupName":"/aws/lambda/test","logStreamName":"2019/02/28/[$LATEST]776c5662a876fe3374d5bc59b2b82f58","functionName":"test","memoryLimitInMB":"128","functionVersion":"$LATEST","invokeid":"24ca0313-f3fd-110d-7525-c885abc6bdb7","awsRequestId":"24ca0313-f3fd-110d-7525-c885abc6bdb7","invokedFunctionArn":"arn:aws:lambda:us-east-1:426374449862533:function:test"}
END RequestId: 24ca0313-f3fd-110d-7525-c885abc6bdb7
REPORT RequestId: 24ca0313-f3fd-110d-7525-c885abc6bdb7  Duration: 2.33 ms   Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 31 MB  

{"message":"Hello 磯野 カツオ!!"}