AWS SAM で Hello World する

# 目的

AWS Lambda を使ってサーバーレスアプリケーションを開発するために、まずは AWS SAM で Hello World してみます。

具体的には、Lambda のテストを実行して、ローカルで動かして、デプロイまで行います。

# 前提知識

前提となる知識について、簡単に書いておきます。

## AWS Lambda とは

いわゆる FaaS(Function as a Service) というやつです。関数を書いて置いてあげるだけで、サーバーを意識することなく実行してくれます。

AWS Lambda は AWS のサービスですが、Google がやっている Google Cloud Functions や Microsoft の Azure Functions などの FaaS もあります。

## AWS SAM とは

AWS Lambda は AWSマネジメントコンソール からポチポチ作成できます。コードも管理画面から書けるのですが、それだと Git 管理ができません。そこで、 Serverless Framework や AWS SAM などのツールを使うと、ローカルでコードを書き、コマンド一発でデプロイまでできるようになります。Serverless Famework は AWS 専用ではなく、他のクラウドサービスにも対応しています。前職ではこちらを使っていました。

今回は、AWS が公式で出している AWS SAM を使ってみようと思います。

ちなみに [AWS サーバーレスアプリケーションモデル \(AWS SAM\) の使用 \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/serverless_app.html) によると、

> AWS サーバーレスアプリケーションモデル (AWS SAM) はサーバーレスアプリケーションを定義するモデルです。

と書いてあります。

## AWS SAM CLI とは

元々は AWS SAM Local と呼ばていたやつです。ローカルで Lambda 関数を実行して動作確認ができます。
コードを修正する度にデプロイして動作確認をするのは大変なので、重宝します。

# この記事でやること

- AWS SAM CLI で Hello, World する
- テストを実行する
- AWS にデプロイして動かす

# 環境

- macOS High Sierra 10.13.6
- AWS SAM CLI 
- Node.js 8.10
- yarn 1.9.4

# Hello World までの手順

## AWS SAM CLI をインストールする

[Installing the AWS SAM CLI \- AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) によると

> SAM CLI を最も簡単にインストールするには pip を使用します。

とのことです。

pip は Python のパッケージマネージャです。Python がインストールされていて pip が使えれば良いと思いますが、一応 Python の最新安定版を入れます。
[Python Release Python 3\.7\.0 \| Python\.org](https://www.python.org/downloads/release/python-370/) によると 3.7.0 みたいです。

※ 私は Python のバージョン切り替えに pyenv を使っています。Ruby の rbenv のように使えるので、Rubyist にはオススメです。

```bash
# pyenv の upgrade
$ pyenv --version
pyenv 1.2.5
$ brew upgrade pyenv
$ pyenv --version
pyenv 1.2.7

# Python 3.7.0 のインストール
$ pyenv install -l | grep 3.7.0 # インストールできるバージョンの確認
  3.7.0
  miniconda-3.7.0
  miniconda3-3.7.0
$ pyenv install 3.7.0
```

`pyenv install 3.7.0` でエラーになった

```bash
$ pyenv install 3.7.0
python-build: use openssl from homebrew
python-build: use readline from homebrew
Downloading Python-3.7.0.tar.xz...
-> https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz
Installing Python-3.7.0...
python-build: use readline from homebrew

BUILD FAILED (OS X 10.13.6 using python-build 20180424)

Inspect or clean up the working tree at /var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578
Results logged to /var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578.log

Last 10 log lines:
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__main__.py", line 5, in <module>
    sys.exit(ensurepip._main())
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 204, in _main
    default_pip=args.default_pip,
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 117, in _bootstrap
    return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths)
  File "/private/var/folders/cy/nk1z019j2kgc4stnccn5kqhw0000gn/T/python-build.20180915211215.60578/Python-3.7.0/Lib/ensurepip/__init__.py", line 27, in _run_pip
    import pip._internal
zipimport.ZipImportError: can't decompress data; zlib not available
make: *** [install] Error 1
```

`zipimport.ZipImportError: can't decompress data; zlib not available` が怪しいのでググってみる。

[Common build problems · pyenv/pyenv Wiki · GitHub](https://github.com/pyenv/pyenv/wiki/Common-build-problems#build-failed-error-the-python-zlib-extension-was-not-compiled-missing-the-zlib) によると

> try reinstalling XCode command line tools for your OS (especially if you just upgraded your OS)

のために `xcode-select --install` を実行してくれとのこと。
たしかに、最近 OS アップデートした気がする。

```bash
$ xcode-select --install
```

改めて

```bash
$ pyenv install 3.7.0
$ pyenv rehash
$ pyenv versions
  system
  3.6.3
* 3.7.0 (set by /Users/yuichiro/.pyenv/version)
$ python --version
Python 3.7.0
```

無事 Python 3.7.0 が入った

- 参考
    - https://github.com/pyenv/pyenv

AWS SAM CLI をインストールします。

```bash
$ pip install aws-sam-cli
$ sam --version
SAM CLI, version 0.6.0
```

- 参考
    - [SAM CLI のインストール \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/sam-cli-requirements.html)

## sam init

```bash
$ sam init --runtime nodejs8.10
[+] Initializing project structure...
[SUCCESS] - Read sam-app/README.md for further instructions on how to proceed
[*] Project initialization is now complete
```

`Read sam-app/README.md for further instructions on how to proceed` と書いてあるので、以降は README を読んで進めてみる。

- 参考
    - [AWS サーバーレスアプリケーションモデル \(AWS SAM\) の使用 \- AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/serverless_app.html#serv-app-sam-init)

## テストを実行してみる

```bash
$ tree sam-app/
sam-app/
├── README.md
├── hello_world
│   ├── app.js
│   ├── package.json
│   └── tests
│       └── unit
│           └── test_handler.js
└── template.yaml
```

依存関係管理に npm だけでなく yarn も使えるらしいので、今回は yarn を使う。
一応、upgrade してから `yarn install` する。

```bash
$ yarn --version
1.7.0
$ brew upgrade yarn
$ yarn --version
1.9.4
$ cd hello_world/
$ yarn install
```

テスト実行

```bash
$ yarn run test
yarn run v1.9.4
$ mocha tests/unit/


  Tests index
    1) verifies successful response


  0 passing (7ms)
  1 failing

  1) Tests index
       verifies successful response:
     TypeError: app.lambda_handler is not a function
      at Context.it (tests/unit/test_handler.js:11:34)



error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

落ちた・・・。

```diff
$ git diff
diff --git a/hello_world/tests/unit/test_handler.js b/hello_world/tests/unit/test_handler.js
index 363c192..cf4dcd2 100644
--- a/hello_world/tests/unit/test_handler.js
+++ b/hello_world/tests/unit/test_handler.js
@@ -8,7 +8,7 @@ var event, context;

 describe('Tests index', function () {
     it('verifies successful response', async () => {
-        const result = await app.lambda_handler(event, context, (err, result) => {
+        const result = await app.lambdaHandler(event, context, (err, result) => {
             expect(result).to.be.an('object');
             expect(result.statusCode).to.equal(200);
             expect(result.body).to.be.an('string');
@@ -21,4 +21,3 @@ describe('Tests index', function () {

         });
     });
 });
```

なんと関数名が間違っていたので、修正。

x `lambda_handler`
o `lambdaHandler`

```
$ yarn run test
yarn run v1.9.4
$ mocha tests/unit/


  Tests index
    ✓ verifies successful response (523ms)



  1 passing (530ms)

✨  Done in 0.93s.
```

通った！

## ローカルで API Gateway 経由で Lambda 関数を呼び出す

```bash
# プロジェクトルートで
$ sam local start-api
Error: Running AWS SAM projects locally requires Docker. Have you got it installed?
```

おっと、 Docker が必要だった。インストールはしているので、起動してあげる。

```bash
$ sam local start-api
2018-09-15 23:16:55 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-09-15 23:16:55 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
2018-09-15 23:16:55  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
```

ブラウザから `http://127.0.0.1:3000/hello` にアクセスする。

```shell
2018-09-15 23:17:30 Invoking app.lambdaHandler (nodejs8.10)

Fetching lambci/lambda:nodejs8.10 Docker container image...............................
```

最初は時間かかるのかな？
ブラウザに以下が表示されたので、OK!!

```json
{"message":"hello world","location":"xxx.xxx.xxx.xxx"}
```

定義はここでされているみたい。

```yaml:template.yaml
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.lambdaHandler
            Runtime: nodejs8.10
            Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
                Variables:
                    PARAM1: VALUE
            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
```

## デプロイして動作確認

Zip化された Lambda 関数を置くための S3 パケットが必要なので、予め用意しておく。

※ デプロイするには、AWS CLI の設定が必要です。今回は割愛させていただきますが、以下をご参照ください。
[AWS CLI をインストールする \- Qiita](https://qiita.com/mokuo/items/0484feefbaddb70a9b06)

```bash
# プロジェクトルートで
$ sam package \
    --template-file template.yaml \
    --output-template-file packaged.yaml \
    --s3-bucket YOUR_S3_BUCKET_NAME
$ sam deploy \
    --template-file packaged.yaml \
    --stack-name sam-app \
    --capabilities CAPABILITY_IAM
```

以下を実行すると、API Gateway のエンドポイントがわかる

```bash
$ aws cloudformation describe-stacks \
    --stack-name sam-app \
    --query 'Stacks[].Outputs'
[
    [
        {
            "OutputKey": "HelloWorldFunctionIamRole",
            "OutputValue": "xxx",
            "Description": "Implicit IAM Role created for Hello World function"
        },
        {
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://xxx.execute-api.xxx.amazonaws.com/Prod/hello/",
            "Description": "API Gateway endpoint URL for Prod stage for Hello World function"
        },
        {
            "OutputKey": "HelloWorldFunction",
            "OutputValue": "xxx",
            "Description": "Hello World Lambda Function ARN"
        }
    ]
]
```

`https://xxx.execute-api.xxx.amazonaws.com/Prod/hello/` にブラウザからアクセスすると、以下が表示されたのでOK!!

```json
{"message":"hello world","location":"xxx.xxx.xxx.xxx"}
```

# 終わりに

前職で初めて AWS Lambda を触ったとき、正直訳がわからなかったです。
その上 Serverless Framework とか・・・
は？って感じでした。

この記事は初心者向けとは言えないかもしれませんが、少しでもお役に立てれば幸いです。