どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

【AWS】SAMの使い方とチュートリアルの解説

f:id:predora005:20210418113643p:plain AWS SAMの使い方について、チュートリアルの実行手順を解説しつつ紹介します。紹介する手順は、Amazon Linux2上で試したものです。

SAMServerless Application Modelの略です。サーバレスと言えば「Lambda」が思い浮かびますが、Lambda含め様々なリソースとの連携も出来るのがSAMです。Cloud Formationをサーバレスに拡張したサービスと言ったところです。

[1] SAMのインストール

下記手順を参考にインストールします。

Installing the AWS SAM CLI - AWS Serverless Application Model

[1-1] Dockerのインストール

まずは、Dockerをインストールします。以下は、Amazon Linux2でのインストール手順です。

$ sudo yum update -y
$ sudo amazon-linux-extras install docker

インストール後、Dockerを起動します。また、ec2-userでもDockerコマンドが起動できるようにするために、ec2-userをdockerグループに追加します。

$ sudo service docker start
$ sudo usermod -a -G docker ec2-user

一度ログアウトし再ログインしたのち、ec2-userでdockerコマンドが使用できることを確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

[1-2] AWS SAM CLI のインストール

インストーラをダウンロードします。

curl -OL https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip

ハッシュ値を生成し、表示されたハッシュ値リリースノートの値と比較します。一致していれば問題ありません。4/15当時はv1.22.0で56d5d3701e6c4e7565dccff3300af072d476c0e7e36299f47565229efec0e139でした。

$ sha256sum aws-sam-cli-linux-x86_64.zip
[ハッシュ値] aws-sam-cli-linux-x86_64.zip

zipを解凍しインストールします。

$ unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
$ sudo ./sam-installation/install

無事インストールが完了していれば、以下のようにバージョンが表示されます。

$ sam --version
SAM CLI, version 1.22.0

[2] 認証情報の設定

aws configureで認証情報を設定します。

$ aws configure
AWS Access Key ID [None]: {アクセスキー}
AWS Secret Access Key [None]: {シークレットアクセスキー}
Default region name [None]: ap-northeast-1
Default output format [None]: json

[3] Hello Worldチュートリアル

以下のチュートリアルで使用方法を確認します。

Tutorial: Deploying a Hello World application - AWS Serverless Application Model

[3-1] プロジェクトの作成

対話式のコマンドになっています。次のように選択しました。

  • テンプレートソース: - AWS Quick Start Templates
  • パッケージタイプ: 1 - Zip (artifact is a zip uploaded to S3)
  • ランタイム: 2 - python3.8
  • Project name [sam-app]: (空欄)
  • テンプレート: 1 - Hello World Example
$ sam init

    SAM CLI now collects telemetry to better understand customer needs.

    You can OPT OUT and disable telemetry collection by setting the
    environment variable SAM_CLI_TELEMETRY=0 in your shell.
    Thanks for your help!

    Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

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: 1

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

Project name [sam-app]: 

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

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
    5 - Elastic File System Sample App
Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
       
$

[3-2] ファイル構成

treeで確認すると以下のディレクトリ・ファイルが作成されています。

$ tree sam-app/
sam-app/
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── template.yaml
└── tests
    ├── __init__.py
    ├── integration
    │   ├── __init__.py
    │   └── test_api_gateway.py
    ├── requirements.txt
    └── unit
        ├── __init__.py
        └── test_handler.py

5 directories, 13 files

sam-app直下に3つのディレクトリが作成されています。

  • 「hello_world」Lambda関数のコード。
  • 「events」呼び出しイベントの定義。
  • 「tests」ユニットテストのコード。

重要なファイルは、チュートリアルに記載の通りですが下記ファイルです。

  • template.yamlAWSリソースを定義するテンプレートファイル。
  • 「hello_world/app.py」Lambda関数のエントリーポイント。
  • 「hello_world/requirements.txt」Pythonの依存関係。ビルド時に参照される。

[3-3] ビルド

sam buildでビルドします。

$ cd sam-app/
$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']

Build Failed
Error: PythonPipBuilder:Validation - Binary validation failed for python, searched for python in following locations  : ['/usr/bin/python'] which did not satisfy constraints for runtime: python3.8. Do you have python for runtime: python3.8 on your PATH?

python 3.8が無いと怒られたのでインストールします。

$ sudo amazon-linux-extras install python3.8

再度ビルドすると成功しました。

$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

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
    

[3-4] デプロイ

[3-4-1] デプロイ内容の設定

sam deployでデプロイします。--guidedを付与すると対話形式で設定できます。

$ sam deploy --guided

Configuring SAM deploy
======================

    Looking for config file [samconfig.toml] :  Not found

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]: 
    AWS Region [us-east-1]: ap-northeast-1
    #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...

"Shows ..."以降の対話内容を日本語訳すると次の通りです。

#デプロイされるリソースの変更が表示され、デプロイを開始するには「Y」が必要です。
デプロイ前に変更を確認する [y/N]: 
#SAMは、テンプレートのリソースに接続するためのロールを作成する権限が必要です。
SAM CLIのIAMロール作成を許可する [Y/n]: 
HelloWorldFunctionに権限が定義されていない可能性がありますが、これは大丈夫ですか?[Y/N]: 
引数を設定ファイルに保存する [Y/n]: 
SAM設定ファイル [samconfig.toml]: 
SAMの設定環境 [default]:

少し待つと、以下のメッセージが表示されます。

   Successfully created!

        Managed S3 bucket: {アップロード先のS3バケット}
        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

Uploading to sam-app/{zipファイル名}  600353 / 600353  (100.00%)

    Deploying with following values
    ===============================
    Stack name                   : sam-app
    Region                       : ap-northeast-1
    Confirm changeset            : True
    Deployment s3 bucket         : {アップロード先のS3バケット}
    Capabilities                 : ["CAPABILITY_IAM"]
    Parameter overrides          : {}
    Signing Profiles             : {}

Initiating deployment
=====================
Uploading to sam-app/{SAMのテンプレート名}.template  1090 / 1090  (100.00%)

このとき、CloudFormatinで新しいスタックが作られ、S3バケットが生成されます。S3バケットの中には、SAMのテンプレートファイルとzipファイルが作られています。zipの中身はhello_world以下の__init__.py, app.py,requirements.txt`です。

マネージメントコンソールで見るとaws-sam-cli-managed-default」というスタックが作成されています。

f:id:predora005:20210417193433p:plain

S3バケットの中身を確認すると、メッセージに表示されていたテンプレートファイルとzipファイル(拡張子はzipになっていない)が作成されています。

f:id:predora005:20210417193531p:plain

[3-4-2] デプロイ実行

内容に問題がなければ、デプロイ前にデプロイ内容が表示されます。

また、デプロイはCloud Formationを使用して行われます。"CloudFormation stack changeset"の項で、作成されるリソースを確認できます。

Waiting for changeset to be created..

CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------
Operation                       LogicalResourceId               ResourceType                    Replacement                   
-----------------------------------------------------------------------------------------------------------------------------
+ Add                           HelloWorldFunctionHelloWorldP   AWS::Lambda::Permission         N/A                           
                                ermissionProd                                                                                 
+ Add                           HelloWorldFunctionRole          AWS::IAM::Role                  N/A                           
+ Add                           HelloWorldFunction              AWS::Lambda::Function           N/A                           
+ Add                           ServerlessRestApiDeployment47   AWS::ApiGateway::Deployment     N/A                           
                                fc2d5f9d                                                                                      
+ Add                           ServerlessRestApiProdStage      AWS::ApiGateway::Stage          N/A                           
+ Add                           ServerlessRestApi               AWS::ApiGateway::RestApi        N/A                           
-----------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:{アカウントID}:changeSet/{Change Setのリソース名}


Previewing CloudFormation changeset before deployment
======================================================

「Changeset」はCloud Formationでスタックを作成・変更する事前に、リソースがどう変わるのかを確認できる機能です。マネージメントコンソールからも確認できます。

f:id:predora005:20210417194518p:plain

f:id:predora005:20210417194522p:plain

yを入力するとデプロイが実行され、"sam-app"という名称のCloud Formationスタックが作成されます。

Deploy this changeset? [y/N]: y

2021-04-15 13:29:48 - Waiting for stack create/update to complete

CloudFormation events from changeset
-----------------------------------------------------------------------------------------------------------------------------
ResourceStatus                  ResourceType                    LogicalResourceId               ResourceStatusReason          
-----------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS              AWS::IAM::Role                  HelloWorldFunctionRole          Resource creation Initiated   
CREATE_IN_PROGRESS              AWS::IAM::Role                  HelloWorldFunctionRole          -                             
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               -                             
CREATE_IN_PROGRESS              AWS::ApiGateway::RestApi        ServerlessRestApi               Resource creation Initiated   
CREATE_COMPLETE                 AWS::ApiGateway::RestApi        ServerlessRestApi               -                             
CREATE_IN_PROGRESS              AWS::Lambda::Permission         HelloWorldFunctionHelloWorldP   -                             
                                                                ermissionProd                                                 
CREATE_IN_PROGRESS              AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   -                             
                                                                fc2d5f9d                                                      
CREATE_IN_PROGRESS              AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   Resource creation Initiated   
                                                                fc2d5f9d                                                      
CREATE_IN_PROGRESS              AWS::Lambda::Permission         HelloWorldFunctionHelloWorldP   Resource creation Initiated   
                                                                ermissionProd                                                 
CREATE_COMPLETE                 AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   -                             
                                                                fc2d5f9d                                                      
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         HelloWorldFunctionHelloWorldP   -                             
                                                                ermissionProd                                                 
CREATE_COMPLETE                 AWS::CloudFormation::Stack      sam-app                         -                             
-----------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                        
--------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                                                  
Description         Implicit IAM Role created for Hello World function                                                         
Value               arn:aws:iam::{IAMロールのリソース名}               

Key                 HelloWorldApi                                                                                              
Description         API Gateway endpoint URL for Prod stage for Hello World function                                           
Value               https://{自動で生成されるリクエストID}.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/                                    

Key                 HelloWorldFunction                                                                                         
Description         Hello World Lambda Function ARN                                                                            
Value               arn:aws:lambda:{Lambda関数のリソース名}              
--------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-app in ap-northeast-1

[4] 動作確認

作成されたAPIcurlでリクエストを送信すると{"message": "hello world"}が返ってきます。

$ curl https://{自動で生成されるリクエストID}.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world"}

[5] ローカルでの動作確認

[5-1] APIをローカルでホスト

sam local start-apiAPIをローカルでホストします。

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
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
2021-04-15 13:57:07  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

別のコマンドウィンドウを開いてcurlでローカルホストにアクセスします。すると、グローバルのAPIにアクセスした時と同様に{"message": "hello world"}が返ってきます。

$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

ホスト側にもリクエストの結果が出力されています。

Invoking app.lambda_handler (python3.8)
Image was not found.
Building image................................................................................................................................
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.22.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
END RequestId:  {リクエストID}
REPORT RequestId: {リクエストID}   Init Duration: 0.33 ms    Duration: 171.38 ms   Billed Duration: 200 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
No Content-Type given. Defaulting to 'application/json'.
2021-04-15 13:58:32 127.0.0.1 - - [15/Apr/2021 13:58:32] "GET /hello HTTP/1.1" 200 -

[5-2] Lambda関数を直接呼び出す

ローカルでホストした場合と同様の結果が得られます。

$ sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambda_handler (python3.8)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.22.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: {リクエストID} Version: $LATEST
END RequestId: {リクエストID}
REPORT RequestId: {リクエストID}   Init Duration: 2.10 ms    Duration: 67.14 ms    Billed Duration: 100 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

[6] 削除

スタックを削除します。{スタック名}はデフォルトのままであれば"sam-app"です。

$ aws cloudformation delete-stack --stack-name {スタック名} --region {リージョン名}

aws-sam-cli-managed-default」スタックを削除する際は、まずS3バケットの中身を空にします。空にしないとバケットが削除できず、スタック削除の途中でエラーになるからです。

このバケットはバージョンニングされているので、AWS CLIを使用する場合はバージョンの削除が必要です。単なるオブジェクトの削除だけだと、パッと見は削除されていてもオブジェクトは残っています。

jqコマンドを使用するシェルスクリプトを作成・実行して削除します。コマンドは下記記事を参考にさせていただきました。{バケット名}はご自分のものに置き換えてください。

バージョニングが有効なS3バケットをAWS CLIで空にする手順(オブジェクト1000個以下) - のぴぴのメモ

#!/bin/bash

BUCKET_NAME= {バケット名}

# jqインストール
sudo yum -y install jq

# バケット内のバージョン情報を表示
# aws s3api list-object-versions --bucket ${BUCKET_NAME}

# バケット内の全オブジェクト削除
aws s3 rm s3://${BUCKET_NAME}/ --recursive

# DeleteMarkersの削除
aws s3api list-object-versions --bucket ${BUCKET_NAME} \
        | jq -r -c '.["DeleteMarkers"][] | [.Key,.VersionId]' \
        | while read line
do
        key=`echo $line | jq -r .[0]`
        versionid=`echo $line | jq -r .[1]`
        aws s3api delete-object --bucket ${BUCKET_NAME} \
               --key ${key} --version-id ${versionid}
done

# Versionsの削除
aws s3api list-object-versions --bucket ${BUCKET_NAME} \
        | jq -r -c '.["Versions"][] | [.Key,.VersionId]' \
        | while read line
do
        key=`echo $line | jq -r .[0]`
        versionid=`echo $line | jq -r .[1]`
        aws s3api delete-object --bucket ${BUCKET_NAME} \
               --key ${key} --version-id ${versionid}
done

# バケットの削除
aws s3 rb s3://${BUCKET_NAME} --force

バケットを完全に削除したら、スタックを削除します。

$ aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default --region {リージョン名}

終わりに

SAMは初見だとなかなかに難しく、チュートリアルを行うだけでも不明点で立ち止まることが何度もありました。この記事が少しでも、SAMを使おうと思っている方の手助けになれば幸いです。

出典

補足

[補足1] template.yamlの中身

sam initで作成されるtemplate.yamlの中身について。コメント部を割愛すると以下の内容です。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

# 説明
Description: >

# Lambda関数の設定。以下はタイムアウト時間=3秒を設定。
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function

    # Lambda関数のソース格納場所とランタイム
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8

      # Lambda関数を起動するイベントの設定。以下はAPIイベント。
      # (注) API, HTTP APIイベントを設定すると、デプロイ時にAPIが自動で作られる。詳細は下記URL参照。
      # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            Path: /hello
            Method: get

# スタックのプロパティに表示する内容
Outputs:
  # 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/"
  # Lambda関数
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  # IAMロール
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

[補足2] ビルド時に生成されるもの

プロジェクト直下に.aws-samディレクトリが作成されます。requirements.txtで指定したライブラリも含め、デプロイ時に必要なものがbuildディレクトリ化に出力されています。

$ tree -da
.
├── .aws-sam
│   └── build
│       └── HelloWorldFunction
│           ├── certifi
│           ├── certifi-2020.12.5.dist-info
│           ├── chardet
│           │   ├── cli
│           │   └── metadata
│           ├── chardet-4.0.0.dist-info
│           ├── idna
│           ├── idna-2.10.dist-info
│           ├── requests
│           ├── requests-2.25.1.dist-info
│           ├── urllib3
│           │   ├── contrib
│           │   │   └── _securetransport
│           │   ├── packages
│           │   │   ├── backports
│           │   │   └── ssl_match_hostname
│           │   └── util
│           └── urllib3-1.26.4.dist-info
├── events
├── hello_world
└── tests
    ├── integration
    └── unit

26 directories