Serverless FrameworkとCustom Docker(Go)でAPI GatewayとLambdaを作成する
概要
Serverless FrameworkとCustom Dockerを用いて、API GatewayとLambdaを作成する方法を記載します。言語はGoを使用しています。
背景
SlackからBot経由で、ユーザの入力に対する処理をする仕組みを作成しようとしていました。
イベントを受け付けるためにAPI Gatewayを用意し、処理をLambdaで実装することにしました。
このLambdaの処理内でいくつか使用したいパッケージがありましたが、処理を実行するOSの中に含まれていなかったため、Custom Docker内でインストールする必要がありました。
作成手順
- サーバーレスで雛形の作成
- Custom Dockerの作成
- AWSリソースの用意
- serverless.ymlの設定
- デプロイと動作確認
サーバーレスで雛形の作成
まずはディレクトリを作成します。
mkdir -p serverless-with-custom-docker-example && cd $_
次にserverlessをインストールします。
npm install serverless
次にaws-go-modを用いて、雛形を作成します。
sls create --template aws-go
Custom Dockerの作成
基本的には、AWSの公式ドキュメントにあるコンテナイメージを使用して Go Lambda 関数をデプロイするを見て、必要な箇所を記載します。
Dockerfileの準備
まずはDockerfileを記載します。
FROM public.ecr.aws/lambda/provided:al2
COPY bin/hello /var/runtime/bootstrap
CMD ["/var/runtime/bootstrap"]
イメージはAWSが提供しているpublic.ecr.aws/lambda/provided:al2を使用します。
バイナリは/var/runtime/bootstrap
に配置します。
これは、provided:al2
のENTRYPOINTに/lambda-entrypoint.sh
が指定され、この中で/var/runtime/bootstrap
がRUNTIME_ENTRYPOINTとして固定されているからです。
provided:al2
ののENTRYPOINTは以下のように確認できます。
$ docker inspect public.ecr.aws/lambda/provided:al2 | jq -c ".[].Config.Entrypoint"
["/lambda-entrypoint.sh"]
/lambda-entrypoint.sh
は以下のように確認できます。
$ docker run --rm --entrypoint "cat" public.ecr.aws/lambda/provided:al2 /lambda-entrypoint.sh
#!/bin/sh
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
if [ $# -ne 1 ]; then
echo "entrypoint requires the handler name to be the first argument" 1>&2
exit 142
fi
export _HANDLER="$1"
RUNTIME_ENTRYPOINT=/var/runtime/bootstrap
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT
else
exec $RUNTIME_ENTRYPOINT
fi
*/usr/local/bin/aws-lambda-rie
は、Lambda コンテナイメージをローカルでテストするためのエミュレータです。詳しく情報はLambda コンテナイメージをローカルでテストするに記載されています。
もしRUNTIME_ENTRYPOINTを変更したい場合、下記のようにentry.shを用意し、DockerfileのENTRYPOINTを変えてあげると動作します。
#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/local/bin/aws-lambda-rie "$@"
else
exec "$@"
fi
FROM public.ecr.aws/lambda/provided:al2
COPY entry.sh /
RUN chmod 755 /entry.sh
COPY bin/hello /main
ENTRYPOINT ["/entry.sh"]
CMD ["/main"]
ローカルでLambdaの動作確認
上記Dockerfileを作成したら、次はローカルでLambdaの動作確認を行います。
まずは、goの環境を用意します。
go mod init github.com/kazune-br/serverless-with-custom-docker-example
go mod tidy
次に、ソースコードとイメージをビルドします。
env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o bin/hello hello/main.go
docker build --platform linux/x86_64 -t serverless-with-custom-docker-example:latest .
準備が出来たらdockerを起動します。
docker run --rm -p 9000:8080 serverless-with-custom-docker-example:latest
下記のcurlを実行します。成功すると記載したものと同じレスポンスが返ってきます。
curl -s "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' | jq
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"X-MyCompany-Func-Reply": "hello-handler"
},
"multiValueHeaders": null,
"body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\"}"
}
AWSリソースの用意
lambdaのデプロイメントバケットとECRの用意をします。
*普段はterraformで作成していますが、この記事だけを見てさっと出来るようにaws cliから作成しています。
S3バケットの作成
下記コマンドでバケットを作成します。
aws s3 mb s3://your-bucket-name
*serverless-deployment-bucketを使用して作る方法もあります。
ECRの作成
下記コマンドでECRを作成します。
aws ecr create-repository \
--repository-name serverless-with-custom-docker-example \
--image-scanning-configuration scanOnPush=true
*docker-image-deployments-in-ecrを使用して作る方法もあります。
serverless.ymlの設定
全体像
serverless.yml
service: serverless-with-custom-docker-example
frameworkVersion: '3'
provider:
name: aws
runtime: go1.x
stage: ${opt:stage, self:custom.defaultStage}
region: "ap-northeast-1"
deploymentBucket: "your bucket name"
lambdaHashingVersion: 20201221
timeout: 29
memorySize: 128
stackTags:
env: ${opt:stage, self:custom.defaultStage}
logs:
httpApi: true
custom:
defaultStage: stg
customDomain:
certificateName: "paste your certificate name"
domainName: "paste your desired domain name"
stage: ${self:provider.stage}
createRoute53Record: true
endpointType: regional
securityPolicy: tls_1_2
apiType: http
plugins:
- serverless-domain-manager
package:
patterns:
- '!./**'
functions:
hello:
image:
uri: "paste your ecr uri"
# If you want to override command and entryPoint, you cat specify as below.
# command:
# - "/main"
# entryPoint:
# - "/entry.sh"
timeout: 29
role: ServerlessWithCustomDockerExampleRole
events:
- httpApi:
path: /hello
method: get
resources:
Resources:
ServerlessWithCustomDockerExampleRole:
Type: AWS::IAM::Role
Properties:
Path: /
RoleName: serverless-with-custom-docker-example-${self:provider.stage}-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: serverless-with-custom-docker-example-${self:provider.stage}-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource:
- "arn:aws:logs:*:*:/aws/lambda/*"
```
Custom Dockerの設定
基本的には、serverlessのblogにあるContainer Image Support for AWS Lambdaを見て、必要な箇所を記載します。
変更箇所はfunctionsディレクティブだけです。
テンプレートから作成したときは下記のように記載されています。
functions:
hello:
handler: bin/hello
events:
- httpApi:
path: /hello
method: get
handlerの部分を、imageに書き換え、uriに先程作成したecrのuriを記載するだけです。
commandやentryPointを変更したい場合は、下記のコメントアウトを外してあげると指定することが出来ます。
functions:
hello:
image:
uri: "paste your ecr uri"
# If you want to override command and entryPoint, you can specify as below.
# command:
# - "/main"
# entryPoint:
# - "/entry.sh"
events:
- httpApi:
path: /hello
method: get
デプロイと動作確認
まずはECRに作成したImageをPushします。
account_id=$(aws sts get-caller-identity | jq .Account)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${account_id}.dkr.ecr.ap-northeast-1.amazonaws.com
service_name=serverless-with-custom-docker-example
docker tag ${service_name}:latest ${account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/${service_name}:latest
docker push ${account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/${service_name}:latest
次にserverlessでAPI GatewayとLambdaを作成します。
sls deploy --stage stg
デプロイ完了後、ログにURLが表示されます。
そこにリクエストを送り、ローカルで試したときと同じ結果が得られれば完了です。
Discussion