【ServerlessFramework】AWS Lambda を用いたWebページやAPIの環境構築

# はじめに

APIGateway、Lambdaを使った簡単なサーバーレスアプリケーションを作りたいと思います。

# 作るもの

![lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266407/83762ef7-6075-b25a-a7fb-e0e11adcd658.png)

# 対象読者

- サーバーレスアプリケーションに触れてみたい人
- ServerlessFrameworkを使ってみたい人

# AWS マネージドサービスについて

## APIGatewayとは

> Amazon API Gateway は、あらゆる規模の REST、HTTP、WebSocket API を作成、公開、管理、モニタリング、保護するための AWS のサービスです。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/welcome.html

物理サーバや仮想サーバの場合は、httpリクエストを受付ようとすると、 *Nginx* などwebサーバを構築する必要があります。
構築後は、アクセス負荷分散・ログローテーション・死活監視など気にする点は多いはずです。

APIGatewayは、それらの気にする点を引き受けてくれるサービスになります。
利用者は、リクエスト数に応じた費用を支払います。
https://aws.amazon.com/jp/api-gateway/pricing/

AWSの他のサービスを利用して、アクセス制限（WAF）、認証（Cognite）などと連携することが容易です。

細かい仕組みになっているのですが、詳しくはクラスメソッドさんの記事がわかりやすいです。
[Amazon API Gateway は何をしてるのか](https://dev.classmethod.jp/articles/what-does-amazon-api-gateway-do/)

## Lambdaとは

> AWS Lambda はイベント発生時にお客様のコードを実行し、基盤となるコンピューティングリソースをお客様に代わって管理する、サーバーレスコンピューティングサービスです。
https://aws.amazon.com/jp/lambda/features/
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html

物理サーバや仮想サーバの場合は、プログラムを動作するアプリケーションサーバを構築する必要があります。
構築後は、アクセス負荷分散・ログローテーション・ディスク容量監視・死活監視など気にする点は多いはずです。

Lambdaは、それらの気にする点を引き受けてくれるサービスになります。
利用者は、リクエスト数と実行時間に応じた費用を支払います。
https://aws.amazon.com/jp/lambda/pricing/

開発者が管理するのは、プログラムコードのみになります。
プログラムコードをLambdaにデプロイすることで動作します。

# ServerlessFrameworkについて

> サーバーレスフレームワークオープンソースを使用すると、サーバーレスアプリケーションを開発して、AWS、Azure、GCPなどにデプロイできます。
https://www.serverless.com/framework/docs/

AWS SAMがありますが、AWS以外のクラウド環境にも対応しています。
Yamlファイルの記述に慣れが必要ですが、極力なツールだと思います。

---

# 実行環境を構築

## Docker環境を準備

Docker環境で用意しました。
Githubからクローン後は、以下2つの手順で環境は完成します。

**Github**
https://github.com/acronhub/sls-python3


- AWSのIAMアカウントのアクセスキーを環境ファイルに設定します。
  - Github上のコードでは **.env.example** を用意しているので名前を **.env** に変えて利用します。

```yaml:.env
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
AWS_DEFAULT_REGION=ap-northeast-1
SERVERLESS_VERSION=2.15.0
```

- ビルド

```bash
docker-compose build
```

- コンテナ起動

```bash
docker-compose up -d
```

## Pythonで環境を準備

- 起動中のコンテナに入る

```bash
docker-compose exec app bash
```

- サービスの作成
  - この工程でプロジェクトが作成されます。

```bash
serverless create --template aws-python3
```

- とりあえずローカルで実行
  - HelloWorldっぽいのが作られているので実行します。

```bash
serverless invoke local --function hello
```

- 以下のように返ってきます。

```bash
{
    "statusCode": 200,
    "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {}}"
}
```

# AWSにデプロイ

- HTTPリクエストで応答するように設定します。
    - APIGatewayを利用します。
    - ServerlessFrameworkはS3を使ってデプロイします。
        - 今回はバケットを指定していないのでサービス名のバケットが自動で作られます。

```bash:serverless.yaml
# Welcome to Serverless!
service: sls-python3     < 変更(デプロイしたときにLambdaの関数名になります)

provider:
  name: aws
  runtime: python3.8
  region: ap-northeast-1 < 追加

functions:
  hello:
    handler: handler.hello
    events:             < 追加
      - http:           < 追加(HTTPを指定することでAPIGatewayが作られます)
          path: /       < 追加
          method: GET   < 追加
```

- デプロイ

```bash
serverless deploy
```

- 以下のようにメッセージが表示されてくると思います。

```bash
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service sls-python3.zip file to S3 (965 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
...........................
Serverless: Stack update finished...
Service Information
service: sls-python3
stage: dev
region: ap-northeast-1
stack: sls-python3-dev
resources: 10
api keys:
  None
endpoints:
  GET - https://b02w4yeoh5.execute-api.ap-northeast-1.amazonaws.com/dev/
functions:
  hello: sls-python3-dev-hello
layers:
  None

```

# 動作確認

- エンドポイントがログに出力されているので、そこにアクセスします。

```bash
https://b02w4yeoh5.execute-api.ap-northeast-1.amazonaws.com/dev/
```

- JSON形式なので、Postmanを利用して取得しました。

```json
{
    "message": "Go Serverless v1.0! Your function executed successfully!",
    "input": {
        "resource": "/",
        "path": "/",
        "httpMethod": "GET",
        "headers": {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "CloudFront-Forwarded-Proto": "https",
            "CloudFront-Is-Desktop-Viewer": "true",
            "CloudFront-Is-Mobile-Viewer": "false",
            "CloudFront-Is-SmartTV-Viewer": "false",
            "CloudFront-Is-Tablet-Viewer": "false",
            "CloudFront-Viewer-Country": "JP",
            "Host": "b02w4yeoh5.execute-api.ap-northeast-1.amazonaws.com",
            "Postman-Token": "4b053914-cc6c-4837-aaaa-e75ac445df85",
            "User-Agent": "PostmanRuntime/7.26.8",
            "Via": "1.1 e6b20196b0d9593ce8bf37920e475b8d.cloudfront.net (CloudFront)",
            "X-Amz-Cf-Id": "feG9_lzfO_cd2a1OOgKA0wnU7f7h9Sq00mA0cyYzwhxpiD3HHi9DFw==",
            "X-Amzn-Trace-Id": "Root=1-5fd2bc3e-3cf565a8213417de68ea2c7c",
            "X-Forwarded-For": "xxx.xxx.xxx.xxx",
            "X-Forwarded-Port": "443",
            "X-Forwarded-Proto": "https"
        },
        "multiValueHeaders": {
            "Accept": [
                "*/*"
            ],
            "Accept-Encoding": [
                "gzip, deflate, br"
            ],
            "CloudFront-Forwarded-Proto": [
                "https"
            ],
            "CloudFront-Is-Desktop-Viewer": [
                "true"
            ],
            "CloudFront-Is-Mobile-Viewer": [
                "false"
            ],
            "CloudFront-Is-SmartTV-Viewer": [
                "false"
            ],
            "CloudFront-Is-Tablet-Viewer": [
                "false"
            ],
            "CloudFront-Viewer-Country": [
                "JP"
            ],
            "Host": [
                "b02w4yeoh5.execute-api.ap-northeast-1.amazonaws.com"
            ],
            "Postman-Token": [
                "4b053914-cc6c-4837-aaaa-e75ac445df85"
            ],
            "User-Agent": [
                "PostmanRuntime/7.26.8"
            ],
            "Via": [
                "1.1 e6b20196b0d9593ce8bf37920e475b8d.cloudfront.net (CloudFront)"
            ],
            "X-Amz-Cf-Id": [
                "feG9_lzfO_cd2a1OOgKA0wnU7f7h9Sq00mA0cyYzwhxpiD3HHi9DFw=="
            ],
            "X-Amzn-Trace-Id": [
                "Root=1-5fd2bc3e-3cf565a8213417de68ea2c7c"
            ],
            "X-Forwarded-For": [
                "xxx.xxx.xxx.xxx"
            ],
            "X-Forwarded-Port": [
                "443"
            ],
            "X-Forwarded-Proto": [
                "https"
            ]
        },
        "queryStringParameters": null,
        "multiValueQueryStringParameters": null,
        "pathParameters": null,
        "stageVariables": null,
        "requestContext": {
            "resourceId": "bzg9qviz6f",
            "resourcePath": "/",
            "httpMethod": "GET",
            "extendedRequestId": "XXJZxEL8NjMFStQ=",
            "requestTime": "11/Dec/2020:00:24:30 +0000",
            "path": "/dev/",
            "accountId": "XXXXXXXXXXXX",
            "protocol": "HTTP/1.1",
            "stage": "dev",
            "domainPrefix": "b02w4yeoh5",
            "requestTimeEpoch": 1607646270497,
            "requestId": "e3230533-5aa1-479b-962e-33e05cbdacab",
            "identity": {
                "cognitoIdentityPoolId": null,
                "accountId": null,
                "cognitoIdentityId": null,
                "caller": null,
                "sourceIp": "xxx.xxx.xxx.xxx",
                "principalOrgId": null,
                "accessKey": null,
                "cognitoAuthenticationType": null,
                "cognitoAuthenticationProvider": null,
                "userArn": null,
                "userAgent": "PostmanRuntime/7.26.8",
                "user": null
            },
            "domainName": "b02w4yeoh5.execute-api.ap-northeast-1.amazonaws.com",
            "apiId": "b02w4yeoh5"
        },
        "body": null,
        "isBase64Encoded": false
    }
}
```

# おわりに

今回はHelloWorldで終わりました。

実業務で利用している感覚ですが、動的なポータルサイトなどにも利用しても十分に対応できており、コストも抑えることができています。
ただ実行時間の制限など制約があるため、重ため処理にはあまり向いていません。

今後もまずサーバーレスで実現できないか考えつつガンガン利用をしていきたいと思っています。