AWS SAM を用いて Hello World アプリケーションをデプロイしてみる


AWS SAM を使ってみたいので、API Gateway 経由で Hello World メッセージを表示する Lambda を呼び出すアプリケーションを SAM でデプロイしてみます。

# 前提条件

- AWS アカウントの作成
- Docker のインストール
- AWS SAM CLI のインストール
  - "sam --version" と叩いてバージョンが表示されれば OK

# サンプルアプリケーションのダウンロード

sam コマンド経由でサンプルとしてダウンロードできるアプリケーションがあるので、ダウンロードしていきます。
まずは下記のコマンドを入力します。

```
$ sam init
```

途中でテンプレートソース、パッケージタイム、ランタイムを選択していきます。
プロジェクト名は任意の名前で問題ありません。
ここでは「hello-sam」としています。

```
$ sam init
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: 9

Project name [sam-app]: hello-sam
```

ここまで入力したら、自動で GitHub からテンプレートがローカルにダウンロードされてきます。
最後にテンプレートを選択します。

```
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)
Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: hello-sam
    Runtime: python3.7
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .

    Next steps can be found in the README file at ./hello-sam/README.md
```

ここまで表示されれば最初の準備は OK です。

# アプリケーションの構築

プロジェクトディレクトリに移動して中身を確認してみましょう。

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

この中の **template.yaml＊＊ というファイルが重要です。
AWS SAM では **template.yaml\*\* という設定ファイルを作成し、AWS SAM リソースを定義します。

template.yaml の中身はこんな感じです。

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hello-sam

  Sample SAM Template for hello-sam

# 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:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      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

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

```

AWS SAM は中で CloudFormation が動作して、コードとして書いた設定ファイルをもとに AWS リソースを作成してくれます。
template.yaml 内の **Resources** の欄で今回つくる Lambda、そして API Gateway を定義しています。

ではこの template.yaml をもとにビルドしましょう。

```
$ sam build
...
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
```

Build Succeeded と表示されればビルドは完了です。

# ローカルでテスト

このままデプロイすることもできますが、デプロイする前に事前にローカルで実行し、ローカルで API を叩いてテストすることもできます。
Docker をインストールしておく必要はありますが、コマンド一つでローカル環境で実行できるので便利かつ楽です。

通常はクラウドにデプロイしてはじめて確認できる API Gateway ですが、ローカルでテストするためには **sam local start-api** コマンドを実行します。

```
$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
```

start-api コマンドによってローカルでテストするためのエンドポイントを起動することができます（中身としては Docker コンテナが動いています）。

さて、ローカルでテストするための URL が表示されているので、curl コマンドでアクセスしてみます。
※別なターミナルを立ち上げて実行します。

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

JSON 形式で Hello World のレスポンスが返ってきました。
この結果は API Gateway としてクラウドでデプロイした結果と同じ結果となります。

# AWS へデプロイ

無事にローカルでテストすることができましたので、実際にデプロイしてみましょう。
基本的には **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 [ap-northeast-1]:
	#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
	Confirm changes before deploy [y/N]: N
	#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]: n
```

内部で CloudFormation が動き、デプロイが完了すると次のように出力されます。
（一部情報はマスクしています）

```
CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------
Outputs
--------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole
Description         Implicit IAM Role created for Hello World function
Value               arn:aws:iam::xxxxxxxxxxx:role/sam-app-HelloWorldFunctionRole-XXXXXXXXXX

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

Key                 HelloWorldFunction
Description         Hello World Lambda Function ARN
Value               arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:sam-app-
HelloWorldFunction-DL88K6
--------------------------------------------------------------------------------------------------

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

curl コマンドで API を叩いて試すには、Key が **HelloWorldApi** の欄に表示されている Value の URL にアクセスします。

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

ローカルでテストした時と同じレスポンスが返ってきていることが分かります。