AWS & Game Advent Calendar 2020 の 20 日目の記事になります。
agenda
- はじめに
- 利用するツール
- ローカルでの実装
- Lambdaへのdeploy
- 動作チェック
- まとめ
はじめに
2020年のre:Inventで、Lambdaがコンテナをサポートする発表がありました。
今までカスタムランタイムでLambdaを動かすには面倒なセットアップが必要でしたがこのアップデートでかなり使いやすくなるのではないでしょうか。
この記事ではコンテナを使ってミニマムの(bashを使ってイベントデータを表示させる)関数をLambdaで動作させる解説を行います。
利用するツール
- sam: Lambdaのdeploy
- docker: コンテナイメージのビルドなど
- ECR: イメージのレジストリ
- bash: 何でも良いのですが今回はbashでechoをさせます
samのインストール
samはHomebrewを使ってインストールしました
$ brew tap aws/tap
$ brew install aws-sam-cli
$ sam --version
SAM CLI, version 1.15.0
ECRのリポジトリ作成
awscliを使ってECRリポジトリを作成します。リポジトリ名はdocker-lambda-bash
とします。
$ aws ecr create-repository --repository-name docker-lambda-bash
ローカルでの実装
カスタムランタイムのチュートリアルはこちらです。
bashでLambdaを動作させるために必要なものは以下の3つです。
- カスタムランタイムのDockerイメージ
- bootstrap
- function.sh
カスタムランタイムのイメージ
こちらの公式のイメージを使って実装を進めます。
bootstrap
詳細はチュートリアルを参照してください。
簡単に説明すると、whileを回してLambdaAPIを使ってLambdaのイベントを取得します。
取得したイベントデータをレスポンスとして返却しています。
#!/bin/sh -xv
set -euo pipefail
# ハンドラ関数の読み込み
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"
while true
do
HEADERS="$(mktemp)"
# イベントの取得
EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)
# ハンドラ関数の実行
RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")
# 結果を返却
curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE"
done
function.sh
こちらも詳細はチュートリアルを参照してください。
イベントデータをechoするだけのscriptです。
function handler () {
EVENT_DATA=$1
echo "$EVENT_DATA" 1>&2;
RESPONSE="Echoing request: '$EVENT_DATA'"
echo $RESPONSE
}
ローカル環境での実行
ローカルでこの関数を実行するには、以下のDockerfileを使ってイメージをビルドします。
FROM public.ecr.aws/lambda/provided:al2
COPY bootstrap ${LAMBDA_RUNTIME_DIR}
COPY function.sh ${LAMBDA_TASK_ROOT}
CMD [ "function.handler" ]
内容は至ってシンプルで、AmazonLinux2(al2)のイメージをベースにして、bootstrap
をLambdaのランタイムDirに、function.sh
をLambdaのタスクDirに配置し、function.handler
をCMDで実行するというものです。
Lambdaの関数の指定方法は、(スクリプト名).(関数名)
です。
この例では、function.sh
という名前のスクリプトの中の、handler
という関数を実行させる必要があるので、このような指定方法となります。
dockekrfileが整ったので以下の様にイメージをビルドします。
$ docker build -t docker-lambda-bash .
ビルドしたイメージを使ってrunします。
$ docker run -p 9000:8080 docker-lambda-bash
curlで9000番portにアクセスするとリクエストデータが返却されます。
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"payload":"hello world!"}'
Echoing request: '{"payload":"hello world!"}'
Lambdaへのdeploy
Lambdaへのdeployはsamを利用します。
samの詳細な使い方は割愛しますが、大まかな手順は以下の通りです。
- ECRにログイン
- docker tagでイメージにtagging
- ECRにpush
- samでイメージを指定してdeploy(Lambda関数の作成)
ECRにログイン
ECRにログインするには以下のコマンドを実行します。
$ export aws_region=ap-northeast-1 # 環境変数にリージョンを設定
$ export aws_account_id=xxxxxxxxxx # 環境変数にAWSのアカウントIDを設定
$ aws ecr get-login-password --region $aws_region | docker login --username AWS --password-stdin $aws_account_id.dkr.ecr.$aws_region.amazonaws.com
tagging & push
$ docker tag docker-lambda $aws_account_id.dkr.ecr.$aws_region.amazonaws.com/docker-lambda-bash
$ docker push $aws_account.dkr.ecr.$aws_region.amazonaws.com/docker-lambda-bash
samでイメージを指定してdeploy
samでLambdaをdeployするにはtemplateを作成する必要があります。
環境変数aws_account_id
とaws_region
を指定する必要があるので、適宜(YOUR-AWS-ACCOUNT-ID)
と(YOUR-AWS-REGION)
を差し替えてください。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: lambda-custom-docker-sample
Globals:
Function:
Timeout: 3
Resources:
MyCustomDocker:
Type: AWS::Serverless::Function
Properties:
FunctionName: CustomDockerWithBash
ImageUri: (YOUR-AWS-ACCOUNT-ID).dkr.ecr.(YOUR-AWS-REGION).amazonaws.com/docker-lambda-bash:latest
PackageType: Image
Events:
CustomDockerEcho:
Type: Api
Properties:
Path: /echo
Method: get
Outputs:
MyCustomDockerApi:
Description: 'API Gateway endpoint URL'
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/echo/'
ImageUri
でECRにpushしたコンテナイメージを指定しています。
また、Outputs
を指定してApiGatewayを使って確認できるようにエンドポイントを作成しています。
deploy
sam deploy --guided
でdeployします。deploy時のログは以下となります。
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: lambda-custiom-docker-sample
AWS Region [us-east-1]: ap-northeast-1
Image Repository for MyCustomDocker: xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/docker-lambda-bash
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
MyCustomDocker may not have authorization defined, Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Looking for resources needed for deployment: Not found.
Creating the required resources...
Successfully created!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1b4wws4k1oxg
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Deploying with following values
===============================
Stack name : lambda-custiom-docker-sample
Region : ap-northeast-1
Confirm changeset : True
Deployment image repository :
{
"MyCustomDocker": "xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/docker-lambda-bash"
}
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1b4wws4k1oxg
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
MyCustomDocker may not have authorization defined.
Uploading to lambda-custiom-docker-sample/e1f6334b9552a49d5e9e07a9970bbae3.template 697 / 697.0 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add MyCustomDockerCustomDockerEchoPermiss AWS::Lambda::Permission N/A
ionProd
+ Add MyCustomDockerRole AWS::IAM::Role N/A
+ Add MyCustomDocker AWS::Lambda::Function N/A
+ Add ServerlessRestApiDeploymentc8d8b9cf15 AWS::ApiGateway::Deployment N/A
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A
+ Add ServerlessRestApi AWS::ApiGateway::RestApi N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxx:changeSet/samcli-deploy1608550235/9b8db82c-4197-4d15-9624-fc35f2e35bd8
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2020-12-21 20:31:50 - Waiting for stack create/update to complete
CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role MyCustomDockerRole -
CREATE_IN_PROGRESS AWS::IAM::Role MyCustomDockerRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role MyCustomDockerRole -
CREATE_IN_PROGRESS AWS::Lambda::Function MyCustomDocker -
CREATE_IN_PROGRESS AWS::Lambda::Function MyCustomDocker Resource creation Initiated
CREATE_COMPLETE AWS::Lambda::Function MyCustomDocker -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeploymentc8d8b9cf15 -
CREATE_IN_PROGRESS AWS::Lambda::Permission MyCustomDockerCustomDockerEchoPermiss -
ionProd
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeploymentc8d8b9cf15 Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Permission MyCustomDockerCustomDockerEchoPermiss Resource creation Initiated
ionProd
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeploymentc8d8b9cf15 -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission MyCustomDockerCustomDockerEchoPermiss -
ionProd
CREATE_COMPLETE AWS::CloudFormation::Stack lambda-custiom-docker-sample -
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Key MyCustomDockerApi
Description API Gateway endpoint URL
Value https://ddcq0ciwhb.execute-api.ap-northeast-1.amazonaws.com/Prod/echo/
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - lambda-custiom-docker-sample in ap-northeast-1
$
動作チェック
成功したので、Outputs
のURLにアクセスしてみます。
$ curl 'https://ddcq0ciwhb.execute-api.ap-northeast-1.amazonaws.com/Prod/echo?param1=val1¶m2=val2'
{"message": "Go Serverless v1.0! Your function executed successfully!", "input": {"resource": "/echo", "path": "/echo", "httpMethod": "GET", "headers": {"Accept": "*/*", "CloudFront-Forwarded-Proto": "https", "CloudFront-Is-Desktop-Viewer": "true", "CloudFront-Is-Mobile-Viewer": "false", "CloudFront-Is-SmartTV-Viewer": "false", "CloudFront-Is-Tablet-Viewer": "false", "CloudFront-Viewer-Country": "JP", "Host": "ddcq0ciwhb.execute-api.ap-northeast-1.amazonaws.com", "User-Agent": "curl/7.64.1", "Via": "2.0 06f6824c0d57ccd48408cb017c7bce76.cloudfront.net (CloudFront)", "X-Amz-Cf-Id": "-lVoexDRq5sBmapw-aMSE8l0ypW4IXMZK3kECxkp4qZVT2ZpLgsAZQ==", "X-Amzn-Trace-Id": "Root=1-5fe08a9d-26bf7d24044bf6127630150f", "X-Forwarded-For": "124.213.96.14, 70.132.19.137", "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https"}, "multiValueHeaders": {"Accept": ["*/*"], "CloudFront-Forwarded-Proto": ["https"], "CloudFront-Is-Desktop-Viewer": ["true"], "CloudFront-Is-Mobile-Viewer": ["false"], "CloudFront-Is-SmartTV-Viewer": ["false"], "CloudFront-Is-Tablet-Viewer": ["false"], "CloudFront-Viewer-Country": ["JP"], "Host": ["ddcq0ciwhb.execute-api.ap-northeast-1.amazonaws.com"], "User-Agent": ["curl/7.64.1"], "Via": ["2.0 06f6824c0d57ccd48408cb017c7bce76.cloudfront.net (CloudFront)"], "X-Amz-Cf-Id": ["-lVoexDRq5sBmapw-aMSE8l0ypW4IXMZK3kECxkp4qZVT2ZpLgsAZQ=="], "X-Amzn-Trace-Id": ["Root=1-5fe08a9d-26bf7d24044bf6127630150f"], "X-Forwarded-For": ["124.213.96.14, 70.132.19.137"], "X-Forwarded-Port": ["443"], "X-Forwarded-Proto": ["https"]}, "queryStringParameters": {"param1": "val1", "param2": "val2"}, "multiValueQueryStringParameters": {"param1": ["val1"], "param2": ["val2"]}, "pathParameters": null, "stageVariables": null, "requestContext": {"resourceId": "co37s7", "resourcePath": "/echo", "httpMethod": "GET", "extendedRequestId": "X5qYlFZ8tjMFcbg=", "requestTime": "21/Dec/2020:11:44:29 +0000", "path": "/Prod/echo", "accountId": "800832305859", "protocol": "HTTP/1.1", "stage": "Prod", "domainPrefix": "ddcq0ciwhb", "requestTimeEpoch": 1608551069250, "requestId": "f7c4fb6d-9621-44c3-8c60-03a0d259ce67", "identity": {"cognitoIdentityPoolId": null, "accountId": null, "cognitoIdentityId": null, "caller": null, "sourceIp": "124.213.96.14", "principalOrgId": null, "accessKey": null, "cognitoAuthenticationType": null, "cognitoAuthenticationProvider": null, "userArn": null, "userAgent": "curl/7.64.1", "user": null}, "domainName": "ddcq0ciwhb.execute-api.ap-northeast-1.amazonaws.com", "apiId": "ddcq0ciwhb"}, "body": null, "isBase64Encoded": false}}
$
Lambdaのイベントデータが表示されました。
まとめ
カスタムランタイムをdockerコンテナで動作させるサンプルを紹介しました。
このプログラム自体はあまり意味のあるものではありませんが、カスタムランタイムのチュートリアルの例と比べたらかなりシンプルになっています。
また、ローカルとクラウドで実行環境が一致するので(同じコンテナの上で動作するので)動作が保証される点も嬉しいです。
sample code