ローカル環境でgoで書いた aws lambda用のコードを実行しちゃう

Tsukasa OISHI

Large

この馬の写真は、宮崎県の都井岬にいる野生の馬です。で真横を通らせていただきました。

サーバーレスアプリケーション

このブログ、kaeruspoonの開発ですが、脱Railsして golang で再実装しようと思います。
EC2上でgoを動かしてもいいのですが、せっかくなのでサーバーレスアプリケーションとして構築しようかと考えました。
とりあえず今回は、ローカル環境でaws lambda用のgoコードが動かせるようにするところまでやってみます。

AWS SAM(Serverless Application Model)

ローカルマシン(Mac)でサーバーレスアプリケーションを開発するために AWS SAMを利用します。SAMには aws-sam-cli というツールがあって、ローカルでの開発には便利そうなのでした。

golangを使うのでgoenvで環境を整え、aws-sam-cliを使うためにpyenvでpython環境を整えました。

ひな形作成

aws-sam-cliを使ってアプリケーションのひな形を用意します。

sam init --runtime go1.x --name kaeru_go

golangを使うのでgoを指定しています。--name で指定したプロジェクト名がそのままディレクトリ名になります。

最初に作られるファイル構成は以下のような感じ。

├── Makefile              <-- 自動ビルド用Makefile
├── README.md
├── hello-world           <-- サンプル
│   ├── main.go           <-- goで書かれたlambdaのコード
│   └── main_test.go      <-- テストコード
└── template.yaml

Makefile

goをコンパイルするためにMakefileを以下のように修正しました。適宜、使いやすいように、また必要なものを追加してください。

.PHONY: deps clean build                                                                                                                                                          

all: deps clean build

deps:
  go get -u github.com/aws/aws-lambda-go/events
  go get -u github.com/aws/aws-lambda-go/lambda

clean: 
  rm -rf ./hello-world/hello-world

build:
  GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world

サンプルを動かしてみる

現時点ですでに動かすことができます。

コンパイル

まず、makeを実行してサンプルコードhello-worldをコンパイルします。

$ make
go get -u github.com/aws/aws-lambda-go/events
go get -u github.com/aws/aws-lambda-go/lambda
rm -rf ./hello-world/hello-world
GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world

hello-worldディレクトリの下に実行ファイルが生成されます。

$ ls -l hello-world
total 16976
-rwxr-xr-x  1 tsukasa  staff  8683300  9 16 05:23 hello-world
-rw-r--r--  1 tsukasa  staff     1096  9 16 05:15 main.go
-rw-r--r--  1 tsukasa  staff     1557  9 16 05:15 main_test.go

Api Gatewayのイベント作成

Api Gatewayがリクエストを受けると、イベントを発行してaws lambdaに渡されます。
このイベントを表しているjsonファイルを作成します。

sam local generate-event api --method GET > get_event.json

lambdaをローカルで動かす

sam local invoke を実行すると、lambdaが動きます。

$ sam local invoke HelloWorldFunction --event get_event.json
2018-09-16 05:28:51 Invoking hello-world (go1.x)
2018-09-16 05:28:51 Starting new HTTP connection (1): 169.254.169.254

Fetching lambci/lambda:go1.x Docker container image......
2018-09-16 05:28:55 Mounting /Users/tsukasa/dev/test/kaeru_go/hello-world as /var/task:ro inside runtime container
START RequestId: 578e697c-138f-1161-d9f7-ac84372c2b60 Version: $LATEST
END RequestId: 578e697c-138f-1161-d9f7-ac84372c2b60
REPORT RequestId: 578e697c-138f-1161-d9f7-ac84372c2b60  Duration: 893.11 ms     Billed Duration: 900 ms Memory Size: 128 MB     Max Memory Used: 10 MB
{"statusCode":200,"headers":null,"body":"Hello, 10.0.0.1\n"}

lambda HelloWorldFunction に対して get_event.jsonのイベントを渡してあげた結果、{"statusCode":200,"headers":null,"body":"Hello, 10.0.0.1\n"} という結果が返ってきました。
サンプルコードは https://checkip.amazonaws.com にアクセスした結果、得られたIPアドレスを返すプログラムです。

Api Gatewayをローカルで動かす

以下を実行すると、ローカルでApi Gatewayが起動します。

$ sam local start-api                            

2018-09-16 05:33:41 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-09-16 05:33:41 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-09-16 05:33:41  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

ブラウザで http://127.0.0.1:3000/hello にアクセスしてみましょう。lambdaの結果が表示されるはずです。

記事詳細API の作成

kaeruspoonの 記事詳細ページ(/articles/:id) を表示するためのAPIを作ってみます。

まず一番大切な template.yml を修正します。Resourcesの下に、aws lambdaの定義をします。
HelloWorldFunctionとなっているので、Resoucesの下を以下のように書き換えます。

Resources:
  ArticleShowFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: article-show/
      Handler: article-show
      Runtime: go1.x
      Tracing: Active
      Events:
        CatchAll:
          Type: Api
          Properties:
            Path: /articles/{Id}
            Method: GET

Outputs:
  ArticleShowAPI:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/articles/id"

  ArticleShowFunction:
    Description: "article show function"
    Value: !GetAtt ArticleShowFunction.Arn

  ArticleShowFunctionIamRole:
    Description: "Implicit IAM Role created for Article Show function"
    Value: !GetAtt ArticleShowFunctionRole.Arn

HelloWorldxxxとなっていた箇所をArticleShowxxxに変更しています。
Resouces -> ArticleShowFunction -> Propertiesの下に lambdaで動かすものの情報を記しています。
ここでは、article-showディレクトリの下にある article-show という実行ファイルを使用すると定義しています。
また、Events -> Propertiesの下で対応するApi GatewayのURI を定義しています。

イベントの修正

/articles/:id というように、URIのパスの中にパラメータidが含まれているので、これをイベントでも表現します。
パスの中のパラメータは pathParametersで表現されます。get_event.jsonの以下の部分に Idを追加します。

    "pathParameters": {
        "proxy": "/examplepath",
        "Id": "3456"
    },

goコードの修正

idを受け取ったことがわかるように、レスポンスに含めてみましょう。
hello-worldディレクトリをarticle-showにリネームした上で、main.goの中の最後のレスポンス生成の部分を以下のように書き換えます。

  return events.APIGatewayProxyResponse{
    Body:       fmt.Sprintf("Article Hello, ID:%s, IP:%s", string(request.PathParameters["Id"]), string(ip)),
    StatusCode: 200,
  }, nil

URIのパスのパラメータは request.PathParametersで取得できます。

Makefileの修正とコンパイル

hello-worldからarticle-showに変更したので、Makefileも以下の部分を書き換えておきます。

clean: 
  rm -rf ./article-show/article-show

build:
  GOOS=linux GOARCH=amd64 go build -o article-show/article-show ./article-show

できたらmakeを実行して article-show/main.goをコンパイルします。

lambdaを動かす

以下を実行してみてください。

$ sam local invoke ArticleShowFunction --event get_event.json

2018-09-16 05:55:00 Invoking article-show (go1.x)
2018-09-16 05:55:00 Starting new HTTP connection (1): 169.254.169.254

Fetching lambci/lambda:go1.x Docker container image......
2018-09-16 05:55:04 Mounting /Users/tsukasa/dev/kaeru_go/article-show as /var/task:ro inside runtime container
START RequestId: d59e6ed3-e729-126e-1139-5feb553f7f1d Version: $LATEST
END RequestId: d59e6ed3-e729-126e-1139-5feb553f7f1d
REPORT RequestId: d59e6ed3-e729-126e-1139-5feb553f7f1d  Duration: 918.57 ms     Billed Duration: 1000 ms        Memory Size: 128 MB     Max Memory Used: 10 MB
{"statusCode":200,"headers":null,"body":"Article Hello, ID:3456, IP:10.0.0.1\n"}

最後のレスポンスのところで ID: 3456と出力されているのがわかります。get_event.jsonで定義した id が渡ってきていますね。

ローカルでApi Gatewayを起動すれば、idを自由に変更して確かめることができます。

sam local start-api

http://127.0.0.1:3000/articles/1234 にブラウザでアクセスすると、ID:1234と表示されるはずです。URLの1234の部分を自由に変更してみてください。

まとめ

とりあえずローカルでaws lambda を(Api Gatewayも)動かすことができました。samを使って、完成したコードをaws lambdaへデプロイすることもできるようです。
ローカルでDynamoDBを使ったり、このブログを開発していく上で気づいたことがあったらまた記事にしようと思います。