AWS SAMでLambdaとAPI Gatewayをデプロイしてみた(Python)

# はじめに

今までシンプルにLambdaを触ってきましたが、AWS SAMを使ってみたいと思ったので試しました。
`sam init`で作られるREADME.mdに従っています。

# 環境

* Windows 10
* Python 3.6

# 各種インストール

## AWS CLI

コマンドでAWSの各種サービス等が使えるようになります。
インストール後、`aws configure`で認証情報を設定しておきます。

* https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/installing.html
* https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html

## Docker

Dockerをインストールします。インストール済みの場合は次へ！

* https://www.docker.com/

## AWS SAM

```bash
$ pip install aws-sam-cli
```

* https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/sam-cli-requirements.html

# 使ってみる

## サンプル作成

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

なお、runtime部分を変えると他言語でもできます。（以下`sam init --help`より）

```bash
 -r, --runtime [python3.6|python2.7|python|nodejs6.10|nodejs8.10|nodejs4.3|nodejs|dotnetcore2.0|dotnetcore1.0|dotnetcore|dotnet|go1.x|go|java8|java]
```

## 必要ライブラリのインストール

buildフォルダ以下に必要なファイル等を集めます。

```bash
$ pip install -r requirements.txt -t hello_world/build/
$ cp hello_world/*.py hello_world/build/
```

## ローカルテスト

まずはAPIを起動します。

```bash
$ sam local start-api
```

次にエンドポイントにアクセスしましょう。

```
$ curl http://127.0.0.1:3000/hello
StatusCode        : 200
StatusDescription : OK
Content           : {"message": "hello world", "location": "xxx.xxx.xxx.xxx"}
RawContent        : HTTP/1.0 200 OK
                    Content-Length: 57
                    Content-Type: application/json
                    Date: Sat, 11 Aug 2018 12:29:28 GMT
                    Server: Werkzeug/0.14.1 Python/3.6.3

                    {"message": "hello world", "location": "xxx.xxx.xxx.xx...
Forms             : {}
Headers           : {[Content-Length, 57], [Content-Type, application/json], [Date, Sat, 11 Aug 2018 12:29:28 GMT], [Se
                    rver, Werkzeug/0.14.1 Python/3.6.3]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 57
```

動きました！！

実行時に`docker.errors.APIError: 500 Server Error: Internal Server Error ("b'Drive has not been shared'")`というエラーが出た場合は、下記を参考にDockerの共有設定を行います。

* http://ryoichi0102.hatenablog.com/entry/2017/01/26/134138

## パッケージ化

コード類を格納するS3バケットを作成します。すでにある場合は次へ！

```bash
$ aws s3 mb s3://gnk263-lambda-bucket
```

続いてS3バケットにパッケージ化します。

```bash
$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket gnk263-lambda-bucket
Uploading to 1729e204b620d03419b6ec4a99b8fac9  1047173 / 1047173.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file D:\xxx\yyy\packaged.yaml --stack-name <YOUR STACK NAME>
```

## デプロイ

次にデプロイします。1分ほど待ちます。WebブラウザでCloudFormationにアクセスすると動いているのが分かります。

```bash
$ sam deploy --template-file packaged.yaml --stack-name sam-sample --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - sam-sample
```

## API Gatewayのエンドポイントを取得

```bash
$ aws cloudformation describe-stacks --stack-name sam-sample --query 'Stacks[].Outputs'
[
    [
        {
            "Description": "Implicit IAM Role created for Hello World function",
            "OutputKey": "HelloWorldFunctionIamRole",
            "OutputValue": "arn:aws:iam::yyyyyyy:role/sam-sample-HelloWorldFunctionRole-1RAI3BIXXK7RS"
        },
        {
            "Description": "API Gateway endpoint URL for Prod stage for Hello World function",
            "OutputKey": "HelloWorldApi",
            "OutputValue": "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
        },
        {
            "Description": "Hello World Lambda Function ARN",
            "OutputKey": "HelloWorldFunction",
            "OutputValue": "arn:aws:lambda:ap-northeast-1:yyyyyyyy:function:sam-sample-HelloWorldFunction-10SCXHEFU9F2H"
        }
    ]
]
```

## 動作確認

取得したエンドポイントにアクセスしてみます。

```
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
StatusCode        : 200
StatusDescription : OK
Content           : {"message": "hello world", "location": "xxx.xxx.xxx.xxx"}
RawContent        : HTTP/1.1 200 OK
                    Connection: keep-alive
                    x-amzn-RequestId: c6a52235-9d77-11e8-9c19-e5ad43c51296
                    x-amz-apigw-id: Ldwi6G_UNjMFdTA=
                    X-Amzn-Trace-Id: Root=1-5b6efadf-18477a72e6ee65fc3d0b2342;Sampled=0
                    ...
Forms             : {}
Headers           : {[Connection, keep-alive], [x-amzn-RequestId, c6a52235-9d77-11e8-9c19-e5ad43c51296], [x-amz-apigw-i
                    d, Ldwi6G_UNjMFdTA=], [X-Amzn-Trace-Id, Root=1-5b6efadf-18477a72e6ee65fc3d0b2342;Sampled=0]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 56
```

成功しました！

## Webブラウザで様子を見てみる

### s3

トップ

![](https://qiita-image-store.s3.amazonaws.com/0/203323/8e1a3305-ff96-4386-fb03-ce9d85c4adcf.png)

バケットの中

![](https://qiita-image-store.s3.amazonaws.com/0/203323/06ed292b-f676-24ed-4d62-10eac470ca27.png)

### API Gateway

トップ

![](https://qiita-image-store.s3.amazonaws.com/0/203323/cdd8bf23-ac9b-009b-de92-4abb727cf137.png)

詳細

![](https://qiita-image-store.s3.amazonaws.com/0/203323/fbe1595c-2f69-73ee-3cd3-ec40907d97f1.png)

### Lambda

トップ

![](https://qiita-image-store.s3.amazonaws.com/0/203323/299b7b6e-8758-09cc-571d-332a44e9ce38.png)

### CloudFormation

![](https://qiita-image-store.s3.amazonaws.com/0/203323/c4e934c2-9219-0843-3462-e09be83f8059.png)

# 参考

* https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/test-sam-cli.html
* https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/serverless_app.html
* https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/serverless-deploy-wt.html
* https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md