AWS SAMとGolangでLambda関数の開発環境をつくる
AWS SAMを使う機会があったので、今回は、AWS SAMとGolangでのLambda関数の開発環境を紹介いたします。
AWS SAMテンプレート
まずはAWS SAMのテンプレートファイル(template.yml
)から作成してゆきます。
今回は、API Gatewayへのアクセスをイベントとし、シンプルにレスポンスを返すだけの関数を作成しようと思います。テンプレートは以下のようになります(Parameters
に関しては後に説明します)。
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Parameters:
ProjectName:
Type: String
Stage:
Type: String
AllowedValues:
- prod
- stg
Default: stg
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Join [ "-", [ !Ref ProjectName, !Ref Stage, HelloFunction ] ]
AutoPublishAlias: !Ref Stage
Handler: main
Runtime: go1.x
Tracing: Active
Environment:
Variables:
Stage: !Ref Stage
Events:
GetEvent:
Type: Api
Properties:
Path: /
Method: post
ここで定義したHelloFunction
をデプロイできるようにしてゆきます。
コンテナ環境
全体的にDockerによるコンテナ環境を想定しており、docker-compose.yml
は以下のようになります。
version: '3'
services:
sam-local:
build: .
command: ./start-sam.sh
ports:
- '3000:3000'
volumes:
- .:/var/opt/
- /var/run/docker.sock:/var/run/docker.sock # AWS SAM Localをdocker-composeと使うときに必要
depends_on:
- db
- go
environment:
- VOLUME=$PWD
env_file:
- .env # 環境変数をコンテナ内で読み込み
go:
command: ./gobuild.sh
build:
context: ./
dockerfile: ./Dockerfile_go
volumes:
- .:/go/src/your-project/
db:
environment:
- MYSQL_ROOT_PASSWORD=docker
- MYSQL_PASSWORD=docker
- MYSQL_USER=docker
- MYSQL_DATABASE=reportdb
build: ./docker/mysql
ports:
- "3306:3306"
基本的にはsam-local
コンテナを立ち上げてそこで動作確認、go
コンテナはビルドまでを責務として行っております。
イメージは以下のようにCLIをインストールするだけのシンプルなものです。
FROM alpine
ENV SAM_CLI_VERSION=0.3.0
PYTHONUSERBASE=/usr/local
RUN apk add --no-cache py-pip git bash &&
pip install --user aws-sam-cli==${SAM_CLI_VERSION} awscli
WORKDIR /var/opt
COPY . /var/opt/
EXPOSE 3000
SAM Localでは、ビルド後の生成物をホスト側からコンテナ側にマウントする仕組みのため、Go側では、ホスト側に生成物を持ってくるようにしています。
FROM golang:1.10.2-alpine
RUN apk add --no-cache git bash &&
go get -u github.com/golang/dep/cmd/dep &&
go get -u github.com/golang/lint/golint
WORKDIR /go/src/your-project/
COPY . /go/src/your-project/
RUN dep ensure
CMD CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o ./main ./main.go
gobuild.sh
#!/bin/sh
dep ensure # パッケージ管理はdepを使用
CGO_ENABLED=0 GOOS=linux go build -v -a -installsuffix cgo -o ./main ./main.go
start-sam.sh
#!/bin/sh
set -e
until ls -l /var/opt/main; do
>&2 echo "go build is not done. - sleeping" # Goのビルドが終わるまで待機
sleep 2
done
>&2 echo "go build is done - executing command"
env | sort
sam local start-api --docker-volume-basedir "${VOLUME}/" --host 0.0.0.0 --template template.yml # ホスト側のファイル一式をコンテナにマウントしつつ、SAM Local起動
Goアプリケーション
環境ができてきたので、Goで関数の実装を書いてゆきます。レスポンスを返すだけの関数なので、以下のようになります。
package main
import (
"os"
"log"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
log.Printf("Processing Lambda request %s
", request.RequestContext.RequestID)
return events.APIGatewayProxyResponse{
Body: "Hello " + request.Body,
Headers: map[string]string{ "x-custom-header" : "my custom header value" },
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(Handler)
}
関数の起動
準備ができたので、関数を起動してみます。
docker-compose build
docker-compose up
# (省)
sam-local_1 | 2018-06-06 08:19:51 Mounting HelloFunction at http://0.0.0.0:3000/ [POST]
sam-local_1 | 2018-06-06 08:19:51 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
sam-local_1 | 2018-06-06 08:19:51 * Running on http://0.0.0.0:3000/ (Press CTRL+C to quit)
上記のようにアプリケーションが起動して、SAM Localが動いてくれます。API Gatewayへのイベントをトリガーとしているので、以下を実行すると、Hello Paul
とレスポンスが返ってくれます。
curl -H 'Content-Type:application/json' http://localhost:3000 -X POST -d "Paul"
Hello Paul
一方、SAM Localは、SAM LocalのDockerプロセスを立ち上げて、その中でLambdaのイメージ(今回だとlambci/lambda:go1.x
)を利用して関数を実行する仕組みですので、コンテナ側では以下のようなログが確認できます。
sam-local_1 | 2018-06-08 08:55:18 Invoking main (go1.x)
sam-local_1 | 2018-06-08 08:55:18 Found credentials in environment variables.
sam-local_1 |
sam-local_1 | Fetching lambci/lambda:go1.x Docker container image......
sam-local_1 | 2018-06-08 08:55:23 Mounting /Users/prop/go/src/your-project as /var/task:ro inside runtime container
sam-local_1 | START RequestId: aa4db6b2-37ef-1628-cadc-3390123c9a16 Version: $LATEST
sam-local_1 | 2018/06/08 08:55:24 Processing Lambda request c6af9ac6-7b61-11e6-9a41-93e8deadbeef
sam-local_1 | END RequestId: aa4db6b2-37ef-1628-cadc-3390123c9a16
sam-local_1 | REPORT RequestId: aa4db6b2-37ef-1628-cadc-3390123c9a16 Duration: 8.44 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 5 MB
sam-local_1 | 2018-06-08 08:55:24 No Content-Type given. Defaulting to 'application/json'.
sam-local_1 | 2018-06-08 08:55:24 172.26.0.1 - - [08/Jun/2018 08:55:24] "POST / HTTP/1.1" 200 -
テスト
上記はアプリケーションの動作確認としてのフローですが、テストを書けば、普通にGoのユニットテストも実行できます。
package main
import (
"testing"
"github.com/aws/aws-lambda-go/events"
"github.com/stretchr/testify/assert"
)
func TestHandler(t *testing.T) {
tests := []struct {
request events.APIGatewayProxyRequest
expect string
err error
}{
{
request: events.APIGatewayProxyRequest{Body: "Paul"},
expect: "Hello Paul",
err: nil,
},
}
for _, test := range tests {
response, err := Handler(test.request)
assert.IsType(t, test.err, err)
assert.Equal(t, test.expect, response.Body)
}
}
docker-compose run go go test
PASS
ok your-project 0.016s
環境変数の読み込み
環境変数は、Gitで追わない.env
ファイルを用意し、それをアプリケーション内で読み込むようにしています。
ただし、デプロイ時に、template.yml
内で環境変数を読み込むために、Parameters
を使用しています。template.yml
内で以下のように書くことで、!Ref Stage
と読み込むことができます。
Parameters:
ProjectName:
Type: String
Stage:
Type: String
AllowedValues:
- prod
- stg
Default: stg
これでデプロイ時に、--parameter-overrides
オプションを使って環境変数を渡してやればOKです。デプロイのコマンドは以下のようになります。
sam deploy
--template-file ./packaged.yml
--stack-name $StackName
--capabilities CAPABILITY_IAM
--no-fail-on-empty-changeset
--parameter-overrides $(cat .env | tr '
' ' ')
※--parameter-overrides
で$(cat .env | tr '
を指定しているので、環境変数を予めCircleCI(CI/CDはCircleCI想定です…)で設定しておき、そこから
' ' ').env
ファイルを動的に生成する必要はあります。
Stageを環境変数として渡せば、本番用/ステージング用のLambda関数を切り分けることもできます。
まとめ
以上、AWS SAMとGolangを使用してLambda関数の開発環境を作ってみました。
以前はあまりできることが少なかった印象ですが、いまはServerlessなどとそこまで大差がなくなってきたのではないかなと思います。むしろCloudFormationで細かい設定ができる分、今後はAWS SAMを使うことが増えそうです。
参考になれば幸いです。
なお、以下のぺージでMMMのAWS Lambdaへの取り組みをご紹介しています。ぜひ合わせてご覧ください。