SAM CLIを使ってLambda向けのコンテナイメージをビルド&デプロイしてみた #reinvent

SAM CLIでコンテナイメージのビルド&デプロイが可能です!!
2020.12.02

CX事業本部@大阪の岩田です。

以下のブログでお伝えしたようにLambdaのパッケージフォーマットとしてコンテナイメージがサポートされるようになりました。

Lambdaの周辺ツールである、SAM CLIが既にコンテナイメージのパッケージに対応していたので、早速SAM CLIを使ってコンテナイメージ作成〜デプロイまで試してみました。

やってみる

SAM CLIのバージョンアップ

まずSAM CLIを最新化しておきましょう。

$pip install --upgrade aws-sam-cli
$sam --version
SAM CLI, version 1.13.1

現時点では1.13.1が最新版でした。

プロジェクトの作成

準備ができたのでsam initしてプロジェクトを初期化します。

$sam init

対話形式で色々と聞かれますが、パッケージ形式に関する選択肢が増えていることが分かります。What package type would you like to use?の質問に対してImageを意味する2を入力します。ベースとして使用するイメージはamazon/python3.8-baseを指定しました。このAWS公式イメージにはRICが内包されており、ランタイムAPIとのやりとりをRICにお任せすることができます。

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1
What package type would you like to use?
	1 - Zip (artifact is a zip uploaded to S3)
	2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2

Which base image would you like to use?
	1 - amazon/nodejs12.x-base
	2 - amazon/nodejs10.x-base
	3 - amazon/python3.8-base
	4 - amazon/python3.7-base
	5 - amazon/python3.6-base
	6 - amazon/python2.7-base
	7 - amazon/ruby2.7-base
	8 - amazon/ruby2.5-base
	9 - amazon/go1.x-base
	10 - amazon/java11-base
	11 - amazon/java8.al2-base
	12 - amazon/java8-base
	13 - amazon/dotnetcore3.1-base
	14 - amazon/dotnetcore2.1-base
Base image: 3

Project name [sam-app]: sam-app-with-container-image

Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app-with-container-image
    Base Image: amazon/python3.8-base
    Dependency Manager: pip
    Output Directory: .

    Next steps can be found in the README file at ./sam-app-with-container-image/README.md

初期化できたらディレクトリを移動します。

$cd sam-app-with-container-image/

ディレクトリの中身を確認してみましょう。

$tree
.
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py

4 directories, 12 files

hello_worldディレクトリ配下にDockerfileが作成されているところが分かります。

Dockerfileの中身は以下の通りでした

FROM public.ecr.aws/lambda/python:3.8

COPY app.py requirements.txt ./

RUN python3.8 -m pip install -r requirements.txt -t .

# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambda_handler"]

CMDにLambdaのhandlerとなるファイルと関数が指定されています。

自動生成されたSAMテンプレートを確認してみましょう。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  python3.8

  Sample SAM Template for sam-app-with-container-image

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      # ImageConfig:
        # Uncomment this to override command here from the Dockerfile
        # Command: ["app.lambda_handler"]
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.8-v1

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

HelloWorldFunctionに設定されているプロパティPackageTypeMetadataに注目です。それぞれ以下の意味を持ちます。

  • PackageType
    • パッケージ形式を指定するプロパティで、ZipもしくはImageが指定可能です
  • Metadata sam build実行時の振る舞いを制御するためのプロパティで、以下のプロパティが指定可能です
    • Dockerfile
      • Dockerfileのファイル名を指定するプロパティです
    • DockerContext
      • Dockerfileの場所を指定するプロパティで、指定されたディレクトリがビルドコンテキストとなります
    • DockerTag
      • ビルドしたイメージに付与するタグを指定するプロパティ
    • DockerBuildArgs
      • ビルド時の--build-argを指定するプロパティです

コンテナイメージのビルドとPUSH

続いてコンテナイメージをビルドします。今回は自動生成されたテンプレートから何も変更せずにビルドしてみます。

$sam build

Building codeuri: . runtime: None metadata: {'Dockerfile': 'Dockerfile', 'DockerContext': './hello_world', 'DockerTag': 'python3.8-v1'} functions: ['HelloWorldFunction']
Building image for HelloWorldFunction function
Setting DockerBuildArgs: {} for HelloWorldFunction function
Step 1/4 : FROM public.ecr.aws/lambda/python:3.8
 ---> c5a0f0b52673
Step 2/4 : COPY app.py requirements.txt ./
 ---> eeb7dd5543ff
Step 3/4 : RUN python3.8 -m pip install -r requirements.txt -t .
 ---> Running in 494d064dbb2a
Collecting requests
  Downloading requests-2.25.0-py2.py3-none-any.whl (61 kB)
Collecting certifi>=2017.4.17
  Downloading certifi-2020.11.8-py2.py3-none-any.whl (155 kB)
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.2-py2.py3-none-any.whl (136 kB)
Collecting idna<3,>=2.5
  Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
Collecting chardet<4,>=3.0.2
  Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Installing collected packages: certifi, urllib3, idna, chardet, requests
Successfully installed certifi-2020.11.8 chardet-3.0.4 idna-2.10 requests-2.25.0 urllib3-1.26.2
WARNING: You are using pip version 20.2.1; however, version 20.3 is available.
You should consider upgrading via the '/var/lang/bin/python3.8 -m pip install --upgrade pip' command.
 ---> d05bfcc30f1e
Step 4/4 : CMD ["app.lambda_handler"]
 ---> Running in 7370c01ca4aa
 ---> 942107bcba50
Successfully built 942107bcba50
Successfully tagged helloworldfunction:python3.8-v1

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

sam buildを実行するとdocker buildまで実行され、コンテナイメージがビルドされていることが分かります。

続いてビルドしたコンテナイメージをECRにPUSHします。このあたりの作業はsam buildsam deployで自動作成されないので、自分で対応する必要があります。

※2020/12/7修正 リポジトリの作成は自分で対応する必要がありますが、イメージのPUSHはsam deployでまとめて実行することも可能でした。
$aws ecr create-repository --repository-name helloworldfunction --image-tag-mutability MUTABLE --image-scanning-configuration scanOnPush=true

{
    "repository": {
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:<AWSアカウントID>:repository/helloworldfunction",
        "registryId": "<AWSアカウントID>",
        "repositoryName": "helloworldfunction",
        "repositoryUri": "<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction",
        "createdAt": 1606889269.0,
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": true
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}

リポジトリが作成できたらログインします。

$aws ecr get-login-password  | docker login --username AWS --password-stdin <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com

Login Succeeded

ローカルのイメージにタグを付与してECRにPUSHします

$docker tag helloworldfunction:python3.8-v1 <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction:python3.8-v1
$docker push <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction:python3.8-v1

The push refers to repository [<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction]
b2fd08fab701: Pushed
b3766120b913: Pushed
9b79aa2a550c: Pushed
8089fc755039: Pushed
120614c3628c: Pushed
2c1b97dd87b9: Pushed
57f5bfde13ee: Pushed
af6d16f2417e: Pushed
python3.8-v1: digest: sha256:7c3abe75461f5564951442ceaa267b8c6b282ae41da3f0276f21cdd12e4fe1b4 size: 1999

これで準備完了です

SAM CLIでデプロイする

準備ができたのでデプロイします。対話形式でコンテナイメージのリポジトリを聞かれるので、先程作成したECRのリポジトリのURIを入力します。

$ 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-pkgfmt-container-test
	AWS Region [us-east-1]: ap-northeast-1
	Image Repository []: <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction
	Images that will be pushed:
	  helloworldfunction:python3.8-v1 to <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction:helloworldfunction-942107bcba50-python3.8-v1

	#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
	HelloWorldFunction 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-69i44xrx71t3
		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
The push refers to repository [<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction]
b2fd08fab701: Layer already exists
b3766120b913: Layer already exists
9b79aa2a550c: Layer already exists
8089fc755039: Layer already exists
120614c3628c: Layer already exists
2c1b97dd87b9: Layer already exists
57f5bfde13ee: Layer already exists
af6d16f2417e: Layer already exists
helloworldfunction-942107bcba50-python3.8-v1: digest: sha256:7c3abe75461f5564951442ceaa267b8c6b282ae41da3f0276f21cdd12e4fe1b4 size: 1999


	Deploying with following values
	===============================
	Stack name                   : lambda-pkgfmt-container-test
	Region                       : ap-northeast-1
	Confirm changeset            : True
	Deployment image repository  : <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/helloworldfunction
	Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-69i44xrx71t3
	Capabilities                 : ["CAPABILITY_IAM"]
	Parameter overrides          : {}
	Signing Profiles           : {}

Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to lambda-pkgfmt-container-test/614deef9037e9199c68bda82754d59e7.template  1195 / 1195.0  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                                       LogicalResourceId                                               ResourceType                                                    Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                                           HelloWorldFunctionHelloWorldPermissionProd                      AWS::Lambda::Permission                                         N/A
+ Add                                                           HelloWorldFunctionRole                                          AWS::IAM::Role                                                  N/A
+ Add                                                           HelloWorldFunction                                              AWS::Lambda::Function                                           N/A
+ Add                                                           ServerlessRestApiDeployment47fc2d5f9d                           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:<AWSアカウントID>:changeSet/samcli-deploy1606891016/7f41b30b-38ac-4c48-b65f-b0c76dc19d39


Previewing CloudFormation changeset before deployment
======================================================	
Deploy this changeset? [y/N]: y

2020-12-02 15:37:23 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus                                                  ResourceType                                                    LogicalResourceId                                               ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS                                              AWS::IAM::Role                                                  HelloWorldFunctionRole                                          -
CREATE_IN_PROGRESS                                              AWS::IAM::Role                                                  HelloWorldFunctionRole                                          Resource creation Initiated
CREATE_COMPLETE                                                 AWS::IAM::Role                                                  HelloWorldFunctionRole                                          -
CREATE_IN_PROGRESS                                              AWS::Lambda::Function                                           HelloWorldFunction                                              -
CREATE_IN_PROGRESS                                              AWS::Lambda::Function                                           HelloWorldFunction                                              Resource creation Initiated
CREATE_COMPLETE                                                 AWS::Lambda::Function                                           HelloWorldFunction                                              -
CREATE_IN_PROGRESS                                              AWS::ApiGateway::RestApi                                        ServerlessRestApi                                               Resource creation Initiated
CREATE_IN_PROGRESS                                              AWS::ApiGateway::RestApi                                        ServerlessRestApi                                               -
CREATE_COMPLETE                                                 AWS::ApiGateway::RestApi                                        ServerlessRestApi                                               -
CREATE_IN_PROGRESS                                              AWS::Lambda::Permission                                         HelloWorldFunctionHelloWorldPermissionProd                      Resource creation Initiated
CREATE_IN_PROGRESS                                              AWS::ApiGateway::Deployment                                     ServerlessRestApiDeployment47fc2d5f9d                           -
CREATE_IN_PROGRESS                                              AWS::Lambda::Permission                                         HelloWorldFunctionHelloWorldPermissionProd                      -
CREATE_IN_PROGRESS                                              AWS::ApiGateway::Deployment                                     ServerlessRestApiDeployment47fc2d5f9d                           Resource creation Initiated
CREATE_COMPLETE                                                 AWS::ApiGateway::Deployment                                     ServerlessRestApiDeployment47fc2d5f9d                           -
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                                         HelloWorldFunctionHelloWorldPermissionProd                      -
CREATE_COMPLETE                                                 AWS::CloudFormation::Stack                                      lambda-pkgfmt-container-test                                    -
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::<AWSアカウントID>:role/lambda-pkgfmt-container-tes-HelloWorldFunctionRole-1PXZX592JZIIW

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:ap-northeast-1:<AWSアカウントID>:function:lambda-pkgfmt-container-test-HelloWorldFunction-3JMCGF5J66UP
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - lambda-pkgfmt-container-test in ap-northeast-1

しばらく待つとデプロイ完了です。

デプロイされたLambdaを確認してみましょう。

パッケージ形式:イメージ でデプロイされていることが分かります。

最後に以下のcurlコマンドを実行しAPI GW経由でLambdaを実行してみます。

$curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world"}

無事にレスポンスが返ってきました。

まとめ

SAM CLIを使ったコンテナイメージの作成とデプロイを試してみました。ツールが対応しているので、とても簡単ですね。コンテナイメージをLambdaにデプロイするためのCI/CDパイプラインの整備なんかもやりやすそうです。SAMユーザーの方はLambdaのパッケージ形式として従来のZIPだけではなく、コンテナイメージという選択肢も検討してみてはいかがでしょうか?