サーバーレスなWebAPIの開発環境をすばやく作る

# はじめに

- AWSのApiGateway + LambdaでサーバーレスなwebAPIを簡易に作ってみよう！という要件があったりなかったりするかと思いますが、AWS上のサービスを利用していることもあり、ローカルでの開発環境を作るのが結構面倒だったりするので、そのあたりまとめてみました。
    - 以下の記事はLambda上で動作させる言語をpython3.7としています。

# 何が大変か？

- **ローカルでAPIが動作する環境を作るのが大変**
    - 普通にwebサーバ上で動作するアプリケーションであればローカルでNginxなどのwebサーバを動かすなりすれば問題ありませんが、ApiGateway + Lambdaの場合はAWS上でないと動作させることが出来ず不便です。
    - ローカルで動作させる場合は、APIのリクエストを処理するモックみたいなものを独自で実装するか、なにかしらのFWを利用するか？。。面倒ですね。。
- **デプロイが大変**
    - 普通にやると、AWSコンソール上でもろもろ設定した上で、必要なモジュールをZIPファイルにアーカイブしてS3にアップロードする必要があり、かなり面倒です。。
    - ローカルの実行環境がmacとかだったりすると、利用しているライブラリにネイティブライブラリの依存関係があったりするとコンパイル環境の違いから実行時にエラーになったりします。。面倒です。。

# どうするか？

- そこで、以下のような構成でローカルの開発環境を作ることにしました。
    - 実行環境
        - [Docker](https://www.docker.com/)
    - サーバレス環境用のFW
        - [Serverless Framework](https://serverless.com/)
    - APIの動作をエミュレート
        - [Serverless Offline](https://github.com/dherault/serverless-offline)(Serverless Frameworkプラグイン)

## Docker

- Lambda上での実行環境の違いを吸収します。
    - Lambdaと同じ実行環境のイメージがDockerHubにあるので、こちらを利用します。
        - [lambci/lambda
](https://hub.docker.com/r/lambci/lambda/)
- Dockerfileは以下のようになります。
    - Serverless Frameworkを利用するのでインストールしています。

```Dockerfile
FROM lambci/lambda:build-python3.7
ADD . /code
WORKDIR /code

RUN yum install -y gcc-c++ make
RUN curl -sL https://rpm.nodesource.com/setup_10.x | bash -
RUN yum install -y nodejs
RUN npm install -g serverless
RUN npm install
RUN pip install -r requirements.txt

CMD /bin/bash
```
- docker-compose.ymlファイルは以下のようになります。
    - Serverless Offlineを実行するようにしていて、3000番ポートをホストマシンとマッピングしています。
    - Dockerイメージ内からデプロイ操作も行うのでAWS関連の環境変数も設定しておきます。

```docker-compose.yml
services:
  workspace:
    build: .
    command: sls offline -o 0.0.0.0 --printOutput
    volumes:
      - .:/code
    tty: true
    ports:
      - "3000:3000"
    environment:
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
```

## Serverless Framework

- AWSへのデプロイ操作やAWS側の設定などこれで吸収しちゃいます。
    - サーバレス系のFWはいくつかありますが、今の所一番使いやすいかと思います。
- serverless.ymlファイルは以下のようになります。
    - eventsにhttpを設定してApiGatewayと連携するようにしています。
    - Serverless OfflineとServerless Python Requirementsをプラグインとして利用するように設定しています。

```serverless.yml
service: api-gateway-lambda-dev-env
provider:
  name: aws
  runtime: python3.7
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  memorySize: 512
functions:
  sample-handler:
    handler: handler.run
    events:
      - http:
          method: get
          path: sample-handler
          integration: lambda
          response:
            headers:
              Content-Type: "'application/json'"
            template: $input.path('$.body')
plugins:
  - serverless-offline
  - serverless-python-requirements
custom:
  defaultStage: dev
```

## Serverless Offline

- ApiGateway + Lambdaの処理をエミュレートしてくれるServerless Frameworkのプラグインになります。
- プラグインインストール後に `sls offline --help` でコマンドの詳細を確認出来ます。
    - ホストマシンとコンテナ間で3000番ポートをマッピングしているので `0.0.0.0` でLISTENしてホストマシンの127.0.0.1でアクセス出来るようにします(デフォルトで3000番ポートになりますがportオプションで変更することも出来ます)
    - 実際にはdocker-compose.yml内のcommandで指定してコンテナ起動時に動作するようにしておきます。
        - `sls offline -o 0.0.0.0`

## その他

- 環境変数系は.envファイルで定義しておくことで、環境にあわせた設定が可能です。
    - docker-compose.ymlで環境変数を定義しておいて、それをserverless.ymlからも参照するようになります。

# 開発の流れ

- まずはコンテナを起動させます。コンテナ起動時にserverless-offlineが実行されるので http://127.0.0.1 でアクセスしてAPIの動作を確認することが出来ます。
    - 事前に `sls create` してLambda-functionのテンプレートが作られている前提です。

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

- docker logsでアクセスの状況を確認します。

```bash
$ docker logs -f <docker-image>
```

- この状態で適宜Lambdaのfunctionを開発していきます。
- 開発が出来たらServerless Frameworkを使ってAWSにデプロイします。

```bash
$ docker exec <docker-image> sls deploy -v --stage dev
```

- 上記の設定をしたサンプルコードをこちらに置いてあるので、参考まで。
    - [api-gateway-lambda-dev-env](https://github.com/myoshioka/api-gateway-lambda-dev-env)


# まとめ

- Serverless Framework＋Serverless Offlineを利用することで、AWS環境をローカルでエミュレートしつつ、構成の設定やデプロイ作業も効率化することが出来ます。
- さらに、これらをコンテナ内で動かすことで実行環境による違いをなくし、開発環境をスピーディーに構築することが出来ました。
- ApiGateway + LambdaでサーバーレスなAPIが簡易に作れるのは嬉しいですが、実際にはローカルでの開発環境を構築するのが大変だったりするので、参考になればど思います。