Go で実装した Lambda 関数をコンテナイメージとしてデプロイする
2021-01-22昨年の re:Invent で、Lambda のデプロイパッケージとしてコンテナイメージがサポートされるようになったと発表がありました。今回は、 Go で実装した Lambda 関数をコンテナイメージとしてデプロイしてみます。
目次
概要
S3 バケット名リストを取得して返却する Lambda 関数を Go で実装し、コンテナイメージにして ECR に Push、そのイメージを Lambda にデプロイします。 サンプルコードは GitHub に置いているので、こっちを見てもらえばだいたい分かると思います。
やってみる
手順としては下記の通りです。
- Go で実装
- Dockerfile 作成・ビルド
- ローカルで実行
- ECR Repository を作成・Push
- ECR のイメージから Lambda 関数を作成
今回はマネジメントコンソールを使わずに AWS CLI で諸々操作していきます。
手順については下記の公式ドキュメントを参考にしています。
Deploy Go Lambda functions with container images - AWS Lambda
1. Go で実装
まずは Go で Lmabda 関数の処理を実装します。SDK は v1 です。
package main
import (
"errors"
"fmt"
"os"
"github.com/aws/aws-sdk-go/aws"
runtime "github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
type Response struct {
Message string `json:"message"`
BucketList []string `json:"bucket_list"`
}
func listBuckets(c *s3.S3) ([]string, error) {
out, err := c.ListBuckets(&s3.ListBucketsInput{})
if err != nil {
return nil, err
}
var list []string
for _, b := range out.Buckets {
list = append(list, aws.StringValue(b.Name))
}
return list, nil
}
func handleRequest() (Response, error) {
if os.Getenv("AWS_DEFAULT_REGION") == "" {
return Response{}, errors.New("'AWS_DEFAULT_REGION' is required.")
}
region := os.Getenv("AWS_DEFAULT_REGION")
s3sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String(region),
}))
s3client := s3.New(s3sess)
buckets, err := listBuckets(s3client)
if err != nil {
return Response{Message: fmt.Sprintf("An error occurred: %s", err.Error())}, nil
}
return Response{
Message: "Success!",
BucketList: buckets,
}, nil
}
func main() {
runtime.Start(handleRequest)
}
Go で Lambda 関数を実装する場合、 handleRequest()
を main()
内で runtime.Start()
に渡す形にします。1
handleRequest()
では引数としてコンテキストとリクエストを受け取りますが、今回は特に何か受け取ることはないので引数なしにしてます。
2. Dockerfile 作成・ビルド
Dockerfile は下記のような内容で作成します。
FROM public.ecr.aws/lambda/provided:al2 as build
RUN yum install -y golang
RUN go env -w GOPROXY=direct
ADD go.mod go.sum ./
RUN go mod download
ADD . .
RUN go build -o /main
FROM public.ecr.aws/lambda/provided:al2
COPY --from=build /main /main
COPY entry.sh /
RUN chmod 755 /entry.sh
ENTRYPOINT [ "/entry.sh" ]
ENTRYPOINT
として指定している entry.sh
は下記です。
#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
exec /usr/local/bin/aws-lambda-rie "$@"
else
exec "$@"
fi
環境変数 AWS_LAMBDA_RUNTIME_API
が設定されていない場合、つまりローカル実行時には RIE (Runtime Interface Emulator) を使って main
を実行します。
上記の Dockerfile でビルドします。
$ docker build -t go-lambda-sample .
ちなみに、 provided:al2 ではなく alpine をベースイメージとする場合は、下記の内容になります。
FROM alpine as build
RUN apk add go git
RUN go env -w GOPROXY=direct
ADD go.mod go.sum ./
RUN go mod download
ADD . .
RUN go build -o /main
FROM alpine
COPY --from=build /main /main
ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
COPY entry.sh /
RUN chmod 755 /entry.sh
ENTRYPOINT [ "/entry.sh" ]
3. ローカルで実行
下記のコマンドでローカル実行します。
$ docker run \
--rm \
-p 9000:8080 \
-e AWS_DEFAULT_REGION="ap-northeast-1" \
-e AWS_ACCESS_KEY_ID="************" \
-e AWS_SECRET_ACCESS_KEY="************" \
go-lambda-sample-ed-1:latest /main
今回のサンプルコード内では Amazon S3 にアクセスしているので、必要な Role を持った Credential が必要になります。なので、環境変数 AWS_ACCESS_KEY_ID
と AWS_SECRET_ACCESS_KEY
に値を設定して実行しています。また、 AWS_DEFAULT_REGION
も必要なのでこれも設定してます。
Lambda で実行する場合、これらは不要です。権限については Lambda にアタッチする IAM Role で、 デフォルトリージョンについては Lambda 関数を作成したリージョンが設定されます。
上記コマンドを実行し、ターミナルの別セッションで invoke 用のエンドポイントにアクセスすると、実装した Lambda 関数を実行できます。
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' -o out.json
## レスポンス確認
$ cat out.json | jq .
{
"message": "Success!",
"bucket_list": [
...
]
}
また、 docker run
したターミナルでは標準出力に Lambda 実行時のログが出力されます。
START RequestId: d0ac5bfb-10f6-4941-b7b4-dbd653eb9f2a Version: $LATEST
END RequestId: d0ac5bfb-10f6-4941-b7b4-dbd653eb9f2a
REPORT RequestId: d0ac5bfb-10f6-4941-b7b4-dbd653eb9f2a Init Duration: 0.38 ms Duration: 515.25 ms Billed Duration: 600 ms Memory Size: 3008 MB Max Memory Used: 3008 MB
START RequestId: 8267a9ea-d234-4c04-a730-70049eefbdd8 Version: $LATEST
END RequestId: 8267a9ea-d234-4c04-a730-70049eefbdd8
REPORT RequestId: 8267a9ea-d234-4c04-a730-70049eefbdd8 Duration: 283.04 ms Billed Duration: 300 msMemory Size: 3008 MB Max Memory Used: 3008 MB
ちなみに、ベースイメージに alpine を使用して RIE をイメージに含めなかった場合は、ローカルマシンに RIE をインストールして docker run
時にエントリーポイントとしてローカルの RIE を指定することでも実行可能です。各自で RIE をインストールする必要はありますが、イメージサイズを小さくしたい場合はこの方法が良さそうです。
RIE を含めない場合の Dockerfile は下記のような内容で。
FROM alpine as build
RUN apk add go git
RUN go env -w GOPROXY=direct
ADD go.mod go.sum ./
RUN go mod download
ADD . .
RUN go build -o /main
FROM alpine
COPY --from=build /main /main
ENTRYPOINT [ "/main" ]
## aws-lambda-rie のインストールとセットアップ
$ mkdir -p ~/.aws-lambda-rie \
&& curl -Lo ~/.aws-lambda-rie/aws-lambda-rie \
https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie \
&& chmod +x ~/.aws-lambda-rie/aws-lambda-rie
## ローカル実行
$ docker run \
--rm \
--entrypoint /aws-lambda/aws-lambda-rie \
-v ~/.aws-lambda-rie:/aws-lambda \
-p 9000:8080 \
-e AWS_DEFAULT_REGION="ap-northeast-1" \
-e AWS_ACCESS_KEY_ID="************" \
-e AWS_SECRET_ACCESS_KEY="************" \
go-lambda-sample:latest /main
4. ECR Repository を作成・Push
まずは ECR のリポジトリを作成します。
$ aws ecr create-repository \
--repository-name go-lambda-sample \
--region ap-northeast-1
続いて、ビルドしたイメージにタグを追加して、 ECR にログイン、 Push します。
$ AWS_ACCOUNT_ID=$( \
aws sts get-caller-identity \
--query 'Account' \
--output text ) \
## タグ追加
$ docker tag go-lambda-sample:latest "${AWS_ACCOUNT_ID}".dkr.ecr.ap-northeast-1.amazonaws.com/go-lambda-sample:latest
## ECR にログイン
$ aws ecr get-login-password --region ap-northeast-1 \
| docker login \
--username AWS \
--password-stdin "${AWS_ACCOUNT_ID}".dkr.ecr.ap-northeast-1.amazonaws.com
## Push
$ docker push "${AWS_ACCOUNT_ID}".dkr.ecr.ap-northeast-1.amazonaws.com/go-lambda-sample:latest
5. ECR のイメージから Lambda 関数を作成
$ IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/go-lambda-sample:latest"
$ aws lambda create-function \
--function-name "go-lambda-sample" \
--package-type "Image" \
--code "ImageUri=${IMAGE_URI}" \
--timeout 30 \
--role "<iam-role-arn>" \
--region ap-northeast-1
<iam-role-arn>
にはあらかじめ作成した Role の ARN を指定します。今回のサンプルであれば CloudWatchLogs に加えて S3 への Read 権限があれば十分です。
lambda create-function
コマンドには --runtime
オプションがありますが、コンテナイメージから作成する場合は指定は不要です。指定した場合、下記のエラーになります。
An error occurred (InvalidParameterValueException) when calling the CreateFunction operation: The Runtime parameter is not supported for functions created with container images.
まとめ
Go で実装した Lambda 関数をコンテナイメージとしてデプロイしてみた話でした。
関数をコンテナイメージで用意できて、 RIE を使ってローカルで Lambda 関数として実行できるが良さげです。今回はただローカルで実行しただけなので、他のサービス (DynamoDB とか) と合わせたローカルでのテストとかはやり方考えたいと思います。
-
“github.com/aws/aws-lambda-go/lambda” にエイリアスを付けているのは、 “github.com/aws/aws-sdk-go/service/lambda” が存在するからです。まあ、今回は使ってないのでエイリアス付けなくてもいいんですが… ↩︎
comments powered by Disqus