AWS SAM - Black Belt 

# 背景

下記記事で Step Functions の構築には SAM がかなり便利ということを実感したので、もう少し深堀りして学習すべく、Black Belt の資料を見てみる。

https://dev.classmethod.jp/articles/aws-sam-step-functions/

https://www.slideshare.net/AmazonWebServicesJapan/20190814-aws-black-belt-online-seminar-aws-serverless-application-model-165314501#82

# 読んでみる

## AWS SAM とは

### Serverless な Application
... 何らかの「イベント」に応じて Lambda 関数を起動し処理を行う性質を持つ

### サーバーレスを取り巻く主なコンポーネント

- 外部 I/F
    - REST API: Amazon API Gateway
    - GraphQL: AWS AppSync
- ストリーム
    - 流入データ: Amazon Kinesis Data Streams
- 処理ロジック
    - Pythonなど: AWS Lambda
- データ保持/管理
    - NoSQL: Amazon DynamoDB
    - Queue: Amazon SQS
    - File: Amazon S3

### AWS Serverless Application Model (SAM)

- イベント駆動の性質を持つサーバーレス　アプリケーションのデプロイに特化した、AWS CloudFormation の拡張機能
- CloudFormation 同様に YAML もしくは JSON 形式で、 SAM テンプレートを記述する
- サーバーレス　アプリケーションの開発を支援する AWS SAM コマンドライン　インターフェイス (CLI) が提供されている
- GitHub 上でオープンソースで開発が行われている

### SAM テンプレートの特徴

1. SAM を利用すると、**より簡潔に**サーバーレスなアプリケーションを定義できる
1. CloudFormation でデプロイする際に、**本来の CloudFormation の文法に変換**される
1. **より直感的にわかりやすく**サーバーレスなアプリケーションを定義できる

### 変換の仕組み

- もともと、 CloudFormation には **Transform というセクションを定義**でき、CloudFormationテンプレートの **変換方式 (マクロ) を用意**できる
- **'AWS::Serverless-2016-10-31'** というマクロが、 **あらかじめ CloudFormation 側で用意**されている
- *AWS::Serverless というプレフィックスの Type のリソース*を、 CloudFormation の本来の文法に変換する
- 自身で定義した CloudFormation マクロとの共存も可能

### 通常の CloudFormation テンプレートとの統合

SAM は CloudFormation の拡張なので、**通常の CloudFormation の文法と共存**できる

## AWS SAM の機能と文法

### SAM テンプレート - ヘッダー

用意する SAM テンプレートのバージョンや、デプロイする内容について説明を書く。

- AWSTemplateFormatVersion
- Transform
- Description

### SAM テンプレート - リソース

Resources の配下に、実際にデプロイするリソースの詳細を定義していく。
Type に指定する値によって、どのようなリソースを配置数rのかが決まる。

Type に指定可能な値は、次の 5 種類

- *AWS::Serverless::Function*
    - AWS Lambda をデプロイ
- *AWS::Serverless::Api*
    - Amazon API Gateway をデプロイ
- *AWS::Serverless::SimpleTable*
    - Amazon DynamoDB のテーブルをデプロイ
- *AWS::Serverless::LayerVersion*
    - Lambda Layer をデプロイ
- *AWS::Serverless::Application*
    - Serverless Application Repository に存在するアプリケーションをデプロイ

### AWS::ServerlessFunction リソースタイプ

**AWS Lambda 関数をデプロイ**すると同時に、 Lambda を起動するイベントを設定する。
イベントの種類によっては、**イベント発生元となるリソースも同時にデプロイ**される。

*Properties の中にこの 2 種類を記載することが基本*

- AWS Lambda の実装や設定値
- Lambda を起動するイベント

### AWS::ServerlessFunction で紹介するプロパティ

- Handler, Runtime プロパティ
- CodeUri, InlineCode プロパティ
- Events プロパティ
- AutoPublishAlias プロパティ
- DeploymentPreference プロパティ
- Policies プロパティ

#### Handler, Runtime プロパティ

Lambda 関数で利用するランタイムと、実行する関数の位置 (ハンドラ) を指定する。

#### CodeUri, InlineCode プロパティ

**Lambda 関数の実装内容**は CodeUri もしくは InlineCode プロパティに指定する。
そのため、CodeUri か InlineCode プロパティいずれかの指定が必須となる。

- *CodeUri の場合*
    - S3 に事前にアップロードした ZIP ファイルの URI を指定する
    - (ZIP ファイルに関数の実装を固めておく)
- *InlineCode の場合*
    - テンプレートの中に直接コードを書く

#### Events プロパティ

**Lambda 関数が実行されるトリガー** を Events プロパティ配下に指定する (複数可)。
Type プロパティに AWS のどのサービスをイベントソースにするかを指定する。

対応する AWS のサービス

- S3
- Amazon API Gateway
- Amazon DynamoDB
- SNS
- SQS
- Amazon Kinesis Data Stremas
- Amazon CloudWatch Events (スケジュール)
- Amazon CloudWatch Events (イベント)
- Amazon CloudWatch Logs
- AWS IoT ルール
- Amazon Alexa スキル

#### AutoPublishAlias プロパティ

AutoPublishAlias を指定すると、 SAM が Lambda 関数の更新をバージョン管理する。
Lambda 関数にデプロイする内容が変化すると、新しいバージョンを発行し、指定したエイリアスを最新版に変更する。

#### DeploymentPreference プロパティ

DeploymentPreference と AutoPublishAlias を指定すると、 **AWS CodeDeploy と自動で連携**する。
最新のコードを一部だけデプロイし、**段階的にデプロイを完了させる**こと (カナリアリリース) が可能。

DeploymentPreference の Type に指定できる値は以下の通り。
指定した内容により、どのように最新バージョンがリリースされるか動作が変わる。

- Canary10Percent30Minutes
- Canary10Percent5Minutes
- Canary10Percent10Minutes
- Canary10Percent15Minutes
- AllAtOnce
- Linear10percentEvery10Minutes
- Linear10percentEvery1Minutes
- Linear10percentEvery2Minutes
- Linear10percentEvery3Minutes

#### Policies プロパティ

よく利用されるポリシーについて**簡単に設定**できるよう、あらかじめ用意されているポリシーがある。

### AWS::Serverless::Api リソースタイプ

**API Gateway をデプロイ**する。
AWS::serverless::Function の **API イベントタイプと組み合わせて使う** 場合が多く、**より詳細な API Gateway の設定を行いたい場合に利用**する。

### AWS::Serverless::Api で紹介するプロパティ

- StageName プロパティ
- CanarySetting プロパティ
- DefinitionUri, DefinitionBody プロパティ
- Auth プロパティ

#### StageName プロパティ

API Gateway の API のデプロイ先となる**ステージ名は必須、かつ 1 つのみ指定**ができる。
そのため、 dev, test, prod といった**複数ステージをデプロイすることはできない**。

#### CanarySetting プロパティ

CanarySetting プロパティを利用すると、 **API 定義をアップデートした際に、一部のトラフィックのみを最新版 (Canary) に流す**ことができる。

#### DefinitionUri, DefinitionBody プロパティ

API Gateway の **API 定義を独自に設定する場合**、 DefinitionUri もしくは DefinitionBody プロパティに指定する。
DefinitionUri か DefinitionBody いずれかのみ指定可能。

- *DefinitionUri の場合*
    - S3 に事前にアップロードした API 定義を指定する
- *Definition Body の場合*
    - テンプレートの中に直接 API 定義を書く

#### Auth プロパティ

API Gateway のオーソライザーを作成し認可方式が設定できる。
Auth プロパティを使う場合、 DefinitionUri による API 定義は使えないことに注意。

### リソースタイプ - AWS::Serverless::SimpleTable

**DynamoDB のテーブルをデプロイ** するリソースタイプ。
CloudFormation の AWS::DynamoDB::Table タイプと同等だが、より簡潔に書くことができる。

### リソースタイプ - AWS::Serverless::LayerVersion

Lambda レイヤーをデプロイするリソースタイプ。

### リソースタイプ - AWS::Serverless::Application

Serverless Application Repository や特定の SAM テンプレートから、アプリケーションをデプロイする。
実際のアプリケーションのデプロイは Nested Stack の中で行われる。

### SAM テンプレート - グローバルセクション

各リソースタイプに適用するプロパティのデフォルト値を定義することができる。

- AWS::Severless::Function
- AWS::Severless::Api
- AWS::Severless::SimpleTable

の 3 種類のリソースが対応。

明示的にプロパティを指定しない場合のデフォルト値を決めることができる。

## AWS SAM Command Line Interface

### AWS SAM Command Line Interface (CLI)

- 開発者のローカル環境で Lambda や API のエンドポイントを起動し、実行をテストすることができる
    - Lambda をローカル実行するにあたり Docker コンテナを利用するため、 Docker のインストールが事前に必要
- SAM を利用したサーバーレス アプリケーションの雛形を作ることができる
- SAM テンプレートの文法が正しいか検証することが可能
- アプリケーションのビルド、パッケージング、デプロイを行うことができる
- 現在はベータ版としての提供

### 雛形の生成 (sam init)

sam init コマンドで雛形を作成。
--runtime オプションに実装する言語を指定できる。

実行すると、Lambda へデプロイするコードや、単体テストのコード、さらに関数をデプロイするための SAM テンプレートなどが生成される。

```bash:bash
$ sam init
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

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

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Multi-step workflow
        3 - Serverless API
        4 - Scheduled task
        5 - Standalone function
        6 - Data processing
        7 - Infrastructure event management
        8 - Machine Learning
Template: 1

 Use the most popular runtime and package type? (Nodejs and zip) [y/N]: N

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

What package type would you like to use?
        1 - Zip
        2 - Image
Package type: 1

Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.

Project name [sam-app]:

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .

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


    Commands you can use next
    =========================
    [*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
```

### SAM テンプレートの検証 (sam validate)

sam validate コマンドで、 SAM テンプレートが正しく CloudFormation の文法に変換できるか検証できる。

SAM テンプレートの変換に失敗した場合、エラーが出力される。

```bash:bash
// 正しい場合
$ sam validate --template template.yaml
2022-01-08 23:37:06 Loading policies from IAM...
2022-01-08 23:37:17 Finished loading policies from IAM.
/Users/glaciermelt/workspace/sam-black-belt/sam-app/template.yaml is a valid SAM Template
2022-01-08 23:37:18 There are unpublished metrics. Please make sure you call publish after you record all metrics.
2022-01-08 23:37:18 There are unpublished metrics. Please make sure you call publish after you record all metrics.

// エラーがある場合
$ sam validate --template template.yaml
2022-01-08 23:38:26 Loading policies from IAM...
2022-01-08 23:38:36 Finished loading policies from IAM.
Template provided at '.../sam-app/template.yaml' was invalid SAM Template.
Error: [InvalidResourceException('HelloWorldFunction', 'property Hoge not defined for resource of type AWS::Serverless::Function')] ('HelloWorldFunction', 'property Hoge not defined for resource of type AWS::Serverless::Function')
2022-01-08 23:38:37 There are unpublished metrics. Please make sure you call publish after you record all metrics.
2022-01-08 23:38:37 There are unpublished metrics. Please make sure you call publish after you record all metrics.
```

### Lambda 関数のビルド (sam build)

sam build コマンドでアプリケーションをビルドできる。
ビルドされたアプリケーションは .aws-sam/build ディレクトリ内に作成される。
--use-container オプションをつけた場合、 Lambda の動作環境と同等のコンテナ内でビルドすることができるため、ネイティブ モジュールのビルドに役立つ。

```bash:bash
$ sam build --template template.yaml
Building codeuri: /.../sam-app/hello_world runtime: python3.9 metadata: {} architecture: x86_64 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
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
[*] Deploy: sam deploy --guided
```

### Lambda 関数のローカル実行 (sam local invoke)

sam local invoke コマンドで、 Lambda ランタイムが動作する Docker コンテナの内部で、ビルドした Lambda 関数を動かすことができる。
--event オプションに、 Lambda 関数に入力として渡す JSON ファイル名を指定する。

```bash:bash
$ sam local invoke --event events/event.json
Invoking app.lambda_handler (python3.9)
Image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.9
Building image........................................................................................................................
......................................................................................................................................
......................................................................................................................................
........................................................................
Skip pulling image and use local one: public.ecr.aws/sam/emulation-python3.9:rapid-1.37.0-x86_64.

Mounting /.../sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}END RequestId: 4972c8dd-8af5-4e18-a823-3a19db486f39
REPORT RequestId: 4972c8dd-8af5-4e18-a823-3a19db486f39  Init Duration: 0.90 ms  Duration: 156.00 ms     Billed Duration: 156 ms Memory Size: 128 MB     Max Memory Used: 128 MB
```

### テスト用イベントの生成 (sam local generate-event)

sam local generate-event コマンドで、Lambda 関数の入力に使うテスト用 JSON データを、サービスごとに出力できる。

### ローカルエンドポイントの作成 (sam local start-lambda)

sam local start-lambda コマンドで、 AWS CLI や SDK など外部から、*ローカルの Lambda 関数を実行するエンドポイント*を立ち上げることができる。

ユースケースとしては、*外部ツールと連携してテストを行いたい場合*などに利用する。

### API エンドポイントの作成 (sam local start-api)

sam local start-api コマンドで、 API Gateway をイベントソースにして起動する Lambda 関数を試すことができる。
SAM テンプレートで定義している内容をもとに、関数へアクセスするパスが生成される。

### パッケージングとデプロイ (sam package / sam deploy)

sam package コマンドで、アプリケーションを ZIP 形式にパッケージング。
S3 にアプリケーションをアップロードする。
(aws cloudformation package コマンドと同様の動作)

sam deploy コマンドで CloudFormation へデプロイ (aws cloudformation deploy コマンドと同様の動作)。

### デプロイした関数のログ確認 (sam logs)

sam logs コマンドを使うと、 AWS にデプロイした　Lambda 関数のログを出力することができる。

--stack-name オプションに、デプロイした CloudFormation のスタック名を指定。

*--name オプションの指定*には SAM テンプレートの中で定義した *Lambda 関数の論理 ID* を指定する。

### 完成したアプリケーションの保管 (sam publish)

SAM テンプレートに **Metadata というセクション**を入れる。
Author に作者、Description にアプリケーションの説明、 Name にアプリケーション名を指定しておく。

sam publish コマンドで **Serverless Application Repository へアプリケーションを発行**する。

--region に発行先のリージョンを、 -template にパッケージング後の SAM テンプレートを、 --semantic-version に発行するアプリケーションのバージョンを指定する。

Serverless Application Repository にアプリケーションが保管され、 Lambda のコンソールや CLI から再びデプロイできるようになる。

### SAM CLI のインストール

SAM CLI は Linux, Mac, Windows に対応。

- 事前に AWS Command Line Interface (AWS CLI) のインストールが必要
- テストのために Lambda 関数をローカルで実行する場合、事前に Docker のインストールが必要
- SAM CLI は最新版のインストールを推奨
    - SAM の最新の文法への対応などが入るため

## その他考慮事項

### SAM と CI/CD パイプライン

- Source: CodeCommit
- Build: CodeBuild
- Deploy CloudFormation

AWS CodeStar を使うことで、 CI/CD パイプラインを自動作成可能。

### SAM のベストプラクティス

- *一つのサーバーレス アプリケーションを構築するリソースの中で、ライフサイクルが同じものは、同じテンプレートにまとめる*
    - ライフサイクルが同じ = リソースが更新・削除されるタイミングが同じであること
- *Lambda と、その呼び出し元となるリソース (S3 や API Gateway 等) は、同じテンプレートにまとめる*
    - AWS SAM では、既存の API Gateway や S3 をトリガーとして Lambda 関数を呼び出すことをサポートしていない
- *Dev / Test / Prod といった環境は、 CloudFormation スタックで分離する*
    - AWS SAM では複数の API Gateway のステージを利用することができない
    - ただし、テンプレートとしては共通化し、パラメータで環境を切り替えられるように
- *Lambda の実装内容や API Gateway の API 定義を更新する際はカナリア リリース (段階的デプロイ) を活用する*
    - テストや CI/CD の仕組みが完全でも見落としは有り得る。万が一に備え、商用環境への適用は段階的に行い、エラーが発生しないか監視する
- *汎用的に利用するサーバーレス アプリケーションは、 Serverless Application Repository を使い、他の AWS アカウントへ共有する*
    - アプリケーションとしてパッケージングされた資材を、あらかじめ対象者に対してそのまま配布できるので、責任分界点が明確化できる
- *AWS SAM CLI を使い、ローカル環境でビルド、デバッグ、 SAM テンプレート検証を行う*
    - Lambda を再現した Docker コンテナが立ち上がるので、本番同等の環境で検証ができる