SAM Accelerateだと！？SAM AccelerateをCloud9を一から構築して試す

#はじめに
皆さん、こちらの記事はご覧になりましたか？私も記事が出た当日に見てはいたのですが、同僚にも「めっちゃ速いよ！」と言われたのですが、スルーしてました。　

https://aws.amazon.com/jp/about-aws/whats-new/2021/10/aws-sam-accelerate-test-code-changes-cloud/

でも、百聞は一見に如かずなんですよね。

[re:Invent 2021](https://virtual.reinvent.awsevents.com/)のLeadership SessionであるAccelerating your serverless journey with AWS LambdaでSAM AccelerateとStep Functionsのデモが披露されていたのですが、それを見て、

「あ、すぐ使いたい」と思って、使ってみたのです。

ということで、今日は、[こちらのブログ](https://aws.amazon.com/jp/blogs/compute/accelerating-serverless-development-with-aws-sam-accelerate/
)で詳しく紹介されていますが、自分で試したことをまとめたいと思います

**なお、執筆時点(2021/12/06)で当機能はPublic Previewです。**

#サマリ
- SAM AccelerateはLambda関数のコードの変更時に迅速なデプロイが可能
- `sam build -c`は依存関係の変更がない場合のビルドが高速化されローカル開発で重宝しそう。
- `sam logs` も素敵。


#環境
タイトルにもある通り、Cloud9 を新規に立ち上げて整備してみます。なお、構築したのは、2021/12/05ですので、最新とは異なる可能性があります。

## Cloud9の起動と初期セットアップ
### Cloud9の起動

まずは、Cloud9を用意しました。
<details><summary>起動手順</summary><div>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/6908b078-108a-7a8b-c5d5-4abd4afb56a9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/04e72762-112d-aa3d-f1b0-f5d1c77398d4.png)
</div></details>
はい、出来上がり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/b6bf1cd7-f64b-aad4-7b0d-bed250637fdf.png)

### ディスク拡張
次に初期サイズではディスクが足りなくなる可能性があることから、念のため、EBSを30GBに拡張します。
<details><summary>手順</summary><div>

[こちらの手順](https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/move-environment.html)にあるスクリプトをコピーして、Cloud9のエディターでresize.shとして作成します。続いて以下のコマンドを実行すれば、数秒で拡張が完了。

```shell:Disk拡張
$ sh resize.sh 30
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    19  100    19    0     0   1583      0 --:--:-- --:--:-- --:--:--  1583
{
    "VolumeModification": {
        "TargetSize": 30, 
        "OriginalMultiAttachEnabled": false, 
        "TargetVolumeType": "gp2", 
        "ModificationState": "modifying", 
        "TargetMultiAttachEnabled": false, 
        "VolumeId": "vol-064045755a07c2231", 
        "TargetIops": 100, 
        "StartTime": "2021-12-04T05:35:49.000Z", 
        "Progress": 0, 
        "OriginalVolumeType": "gp2", 
        "OriginalIops": 100, 
        "OriginalSize": 10
    }
}
CHANGED: partition=1 start=4096 old: size=20967391 end=20971487 new: size=62910431 end=62914527
meta-data=/dev/xvda1             isize=512    agcount=6, agsize=524159 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1 spinodes=0
data     =                       bsize=4096   blocks=2620923, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal               bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
data blocks changed from 2620923 to 7863803
```
</div></details>
docker コンテナを利用したビルド等を行うと、ディスクサイズを拡張しておくほうが安心ですね。まずは、これで当分大丈夫。


### AWS CLI/AWS SAMのアップデート
そこに、[AWS CLIの最新版の導入手順](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-linux.html#cliv2-linux-upgrade)と、[AWS SAM]()の最新版の導入手順を見ながら、環境を用意します。

<details><summary>手順</summary><div>

```sh:現時点
$ aws --version
aws-cli/1.19.112 Python/2.7.18 Linux/4.14.252-195.483.amzn2.x86_64 botocore/1.20.112

$ sam --version
SAM CLI, version 1.33.0
# 結構新しい
```
SAMはまあまあ新しいですが、AWS CLIは超古いですね。これは確実にUpdateが必要です。

```sh

$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 43.3M  100 43.3M    0     0   101M      0 --:--:-- --:--:-- --:--:--  101M

$ unzip awscliv2.zip
# (省略）

$sudo ./aws/install --update
You can now run: /usr/local/bin/aws --version


$ curl -L  https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip --output aws-sam-cli-linux-x86_64.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   155  100   155    0     0   3655      0 --:--:-- --:--:-- --:--:--  3690
100   665  100   665    0     0  13878      0 --:--:-- --:--:-- --:--:-- 13878
100 50.7M  100 50.7M    0     0  53.2M      0 --:--:-- --:--:-- --:--:-- 63.3M

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

$sudo ./sam-installation/install --update
You can now run: /usr/local/bin/sam --version
```
ということで、アップデートとできてるか、ターミナルを立ち上げて確認

</div></details>

```bash
$ aws --version;sam --version
aws-cli/2.4.5 Python/3.8.8 Linux/4.14.252-195.483.amzn2.x86_64 exe/x86_64.amzn.2 prompt/off
SAM CLI, version 1.36.0
```

はい、これで、現時点での最新化が完了。
## Pyenvの導入とPython 3.9.9を導入

ついでに、pyenvを導入して、python を3.9.9にしておきます。
<details><summary>手順</summary><div>

```bash:現状
$ python -V
Python 3.7.10
```
### Pyenvの導入

```bash:pyenvの導入と設定
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
$ ~/.pyenv/bin/pyenv --version
pyenv 2.2.2-1-gf2925393

$ vi ~/.bashrc
 # PATHを通す & pythonの実行パスの差し替えられるように設定。以下に行を追加
 # export PATH="$HOME/.pyenv/bin:$PATH"
 # eval "$(pyenv init -)"


$ cat ~/.bashrc | grep pyenv
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"

$ pyenv --version
pyenv 2.2.2-1-gf2925393

```

```bash:Python3.9.9の導入
$ pyenv install -l | grep 3.9.
(省略)
3.9.9
(省略)
$ pyenv install 3.9.9
Downloading Python-3.9.9.tar.xz...
-> https://www.python.org/ftp/python/3.9.9/Python-3.9.9.tar.xz
Installing Python-3.9.9...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
Installed Python-3.9.9 to /home/ec2-user/.pyenv/versions/3.9.9

#（警告が出ているので追加で導入）

$ sudo yum -y install bzip2

$ pyenv versions
* system (set by /home/ec2-user/.pyenv/version)
  3.9.9

$ pyenv global 3.9.9

$ python -V
Python 3.7.10
```
Pythonのインストールは、5分くらいかかりますかね。そして、まだ、設定は反映されていません。原因は、

```
which python
alias python=python3
     /usr/bin/python3
```
ですね。Alias 設定がされており、どうやら `/etc/profile.d/python3_default.sh ` で指定されているようなのです。
とりあえず、以下の一行を `  ~/.bash_profile` をに追加し変更を適用



```
export PATH="$HOME/.pyenv/shims:$PATH"
```
</div></details>

```
$ source ~/.bash_profile
$ python -V
Python 3.9.9
```

ということで、ここまでだいぶ長くなりましたが、環境設定ができたので、ここからようやく本題ですね。

# 構築するアプリケーション
シンプルにAmazon API GatewayとAWS Lambdaを連携させてみたいと思います。
SAMでLambda 関数を構築するとEvent属性にAPI Gatewayを指定することで簡単に連携ができますが、今回は個別に作りたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535037/72a9210c-60d8-3975-f76f-d0715386e30b.png)

# SAMを利用して構築

## 初期化

まずは、`sam init` 。今回はhello-worldを指定しています。生成されるTemplateは後でごっそり書き換えます。
<details><summary>sam init</summary><div>

```sh
$ sam init --name sam-accelerate-demo --app-template hello-world --runtime python3.9 --architecture x86_64 -p Zip

        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

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

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

    Next application steps can be found in the README file at ./sam-accelerate-demo/README.md
    

    Commands you can use next
    =========================
    [*] Create pipeline: cd sam-accelerate-demo && sam pipeline init --bootstrap
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
    
```
</div></details>

初期化完了しました。treeコマンドで現在の環境をお見せします。ただ、その前に、treeをインストール
<details><summary>tree</summary><div>

```
sudo yum -y install tree
```
初期状態から、ダウンロードしたものも含めて色々ありますね。
その中で、` sam-accelerate-demo` ディレクトリができています。
```
$ tree
.
├── aws
│   ├── dist
│   ├── install
│   ├── README.md
│   └── THIRD_PARTY_LICENSES
├── awscliv2.zip
├── aws-sam-cli-linux-x86_64.zip
├── README.md
├── resize.sh
├── sam-accelerate-demo
│   ├── events
│   ├── hello_world
│   ├── __init__.py
│   ├── README.md
│   ├── template.yaml
│   └── tests
└── sam-installation
    ├── aws-sam-cli-src
    ├── dist
    ├── install
    └── THIRD-PARTY-LICENSES
```

</div><details>

ということで、ここから最初の状態を目指して仮に作ってみます。

## アプリ構築開始
- HTTP APIを定義。パス(Route/Integration)は別ファイルで定義。（OpenAPIで定義) 
- API GatewayがLambdaを呼び出す際の権限をIAM Roleで指定（Lambdaのリソースベースポリシーでも設定可能）
- Lambda 関数は分散トレーシング（X-Ray)を有効化
- OutputsでAPIアクセスするためのURLを表示

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

  Sample SAM Template for sam-accelerate-demo
Globals:
  Function:
    Timeout: 3
    Tracing: Active
Resources:
  HttpAPI:
    DependsOn: APIIAMRole
    Type: AWS::Serverless::HttpApi
    Properties:
      DefinitionBody:
        Fn::Transform:
          Name: AWS::Include
          Parameters:
            Location:
              openapi.yaml
      FailOnWarnings: true

  #API Gateway Invoke Lambda Role
  APIIAMRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - apigateway.amazonaws.com
            Action: 
              - sts:AssumeRole
      Path: "/"        
      Description: "Role for API Gateway to invoke Lambda Functions"
      Policies: 
        - PolicyName: AllowAllActions
          PolicyDocument:
            Version: 2012-10-17
            Statement: 
            - Effect: Allow
              Action: lambda:InvokeFunction
              Resource: !GetAtt ProducerFunction.Arn      

  ProducerFunction:
    Type: AWS::Serverless::Function 
    Properties:
      FunctionName: !Sub ${AWS::StackName}-ProducerFunction
      CodeUri: ProducerFunction/
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64

Outputs:
  HttpApiUrl:
    Description: URL of your API endpoint
    Value:
      Fn::Sub: 'https://${HttpAPI}.execute-api.${AWS::Region}.${AWS::URLSuffix}/'


```
Open API定義はこんな感じです。Any で{proxy+}で受け取る定義にしています。特に深い意味はありません。

```yaml:openapi.yaml
openapi: "3.0.1"
servers:
  - url: ""
    x-amazon-apigateway-endpoint-configuration:
      disableExecuteApiEndpoint: false
info:
  title:
     Fn::Sub:
      ${AWS::StackName}-api
  version: "1.0"
components:
  x-amazon-apigateway-integrations:
    lambda-sample:
        credentials: 
          Fn::GetAtt:
            APIIAMRole.Arn
        payloadFormatVersion: "2.0"
        type : aws_proxy
        connectionType: INTERNET
        httpMethod : POST
        uri: 
          Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${ProducerFunction}/invocations
paths:
  /{proxy+}:
    x-amazon-apigateway-any-method:
      parameters:
      - name: "proxy+"
        in: "path"
        description: "Generated path parameter for proxy+"
        required: true
        schema:
          type: "string"
      x-amazon-apigateway-integration:
          $ref : "#/components/x-amazon-apigateway-integrations/lambda-sample"

```

## ビルド・デプロイ
さて、テンプレートを書いたら、いつも使うコマンドは、`sam build`
これ、毎回時間がかかるんですよね。今回は関数が1つなのでよいのですが、複数あるとさらに時間がかかる。結構ストレスな彼。
今回は、`sam build`　に **-c**オプションをつけて実行してみますね。

### ビルドをキャッシュで高速化
以下のコマンドで時間計測をしながらビルドをします。初回はキャッシュがない状態、その後、template.yamlを修正してキャッシュがある状態でビルドします。

` time sam build --use-container -c `

<script id="asciicast-ZjgJKwjqvr4dGet9d8iT4qwfz" src="https://asciinema.org/a/ZjgJKwjqvr4dGet9d8iT4qwfz.js" async></script>

2回目以降の結果は以下の通りです。今回は関数が１つだけなので、大した影響はないように見えるかもしれませんが、関数が更けてくると影響が大きくなりますね。

```sh
$ time sam build --use-container -c
Starting Build use cache
Starting Build inside a container
Valid cache found, copying previously built resources from function build definition of fa140cb2-3521-449f-981a-51fb482a48d0

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
        

real    0m1.138s
user    0m0.645s
sys     0m0.044s
```
たったの1秒で終わってます。ということで、依存関係等の変更がなければ、この**-cオプション**とても開発のストレスを軽減する優れもの。

でも、今日は、これはメインディッシュではありません。

`buildコマンド`の最後に、`sam sync --stack-name {stack-name} --watch`が表示（紹介）されています。これが今日の目玉なのです。

ただ、その目玉に行く前に`sam sync --stack-name {stack-name} `を実行したいと思います。以下のムービーは、短縮版で作成しているため実際には、120秒程度かかっています。


<script id="asciicast-mQX7sLTgkWX7wDoP0WCSdCwX3" src="https://asciinema.org/a/mQX7sLTgkWX7wDoP0WCSdCwX3.js" async></script>

作られたスタックを見てみます。

```
$ aws cloudformation list-stacks
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:012345678901:stack/sam-accelerate-demo-AwsSamAutoDependencyLayerNestedStack-I0U4XRI8KM5M/2d561a90-54ee-11ec-bbdb-06a7faf1c72f",
            "StackName": "sam-accelerate-demo-AwsSamAutoDependencyLayerNestedStack-I0U4XRI8KM5M",
            "TemplateDescription": "AWS SAM CLI Nested Stack for Auto Dependency Layer Creation",
            "CreationTime": "2021-12-04T10:37:28.234000+00:00",
            "StackStatus": "CREATE_COMPLETE",
            "ParentId": "arn:aws:cloudformation:ap-northeast-1:012345678901:stack/sam-accelerate-demo/275eda00-54ee-11ec-ab89-0ab26497c855",
            "RootId": "arn:aws:cloudformation:ap-northeast-1:012345678901:stack/sam-accelerate-demo/275eda00-54ee-11ec-ab89-0ab26497c855",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        },
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:012345678901:stack/sam-accelerate-demo/275eda00-54ee-11ec-ab89-0ab26497c855",
            "StackName": "sam-accelerate-demo",
            "TemplateDescription": "sam-accelerate-demo\nSample SAM Template for sam-accelerate-demo\n",
            "CreationTime": "2021-12-04T10:37:18.221000+00:00",
            "StackStatus": "CREATE_COMPLETE",
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        
]
}
```
どうやら、Lambda 関数が依存するライブラリ等をLaterで実装しているようです。この影響もあるのかもしれませんが、**sam syncを最初に実行するときは、時間が長く感じます。**
これは、`--watch`をつけても同じです。ただ、`--watch`をつけるとその後のデプロイが高速に行われ、特にLambda関数の開発がしやすくなります。


それでは、今度は、最初から`sam sync`に`--watch`つけてスタックを作成したいと思います。
実行後、最後に `Infra sync completed.` が表示されると、そこから待機してディレクトリ内の変更を監視するようになるのです。

なお、スタック作成後に`sam sync`に`--watchを実行するとローカルの環境とスタックを同期するための更新で時間が少しがかかりますが、その後は同じ動きとなります。

動画で紹介している手順はこちらです。

1. sam syncを実行しスタックを作成
1. スタック作成後、OutputsでHTTP APIのURLを表示
1. Curlでアクセスしメッセージを受け取る。
1. sam logでLambdaやAPI Gatewayのログを受け取る
1. Lambdaのソースコードを修正し反映
1. Curlでアクセスし更新されたメッセージを受け取る。
1. Lambdaのコードの反映や、Stackが更新されていないことを確認する。

<iframe width="800" height="500" src="https://www.youtube.com/embed/MDX8Gd3K9dQ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


# 終わりに

CICDパイプラインでUnit Testをローカル実行して動作確認するのが通常のプロセスかとは思いますが、Lambdaのコード変更をローカルで行い、いち早くクラウドに反映させて動作検証する場合には非常に高速ですし、sam logもなかなか便利なツールだと思います。複数のAWSサービスのログを一元的に確認でき、自身でポーリングしてチェックする必要がないところがお気に入りポイントですね。

さて、今回は、一からCloud9を立ち上げてSAM Accelerateを試したのですが、自身の環境の問題(IAM Roleを利用してアクセスしたものの、設定をしていない)等もあり、エラーがでることや、自動検知されない変更等がありました。また別途記事にしたいと思います。