プルリクごとにレビュー用のサーバーレスアプリをデプロイする

この記事は[Serverless Advent Calendar 2018](https://qiita.com/advent-calendar/2018/serverless)の6日目です。

少し前までサーバーレスアプリを開発していましたが、最近転職したことで **Ruby on Rails + heroku** でのアプリ開発をしています。
なのでサーバーレスのネタは溜まっていませんが、その heroku に便利な機能があることを知り、サーバーレスでも使えそうだと思ったのでやってみます。
便利な機能というのが **[Review Apps](https://devcenter.heroku.com/articles/github-integration-review-apps)** というものです。

## Review Apps とは
Heroku の Review Apps がどういうものか簡単に説明すると、
Review Apps の設定をした Git リポジトリで GitHub にプルリクが作られると、そのプルリクのソースコードを使って **プルリクごとの環境を Heroku に構築** してくれる
というものです。

## 何が嬉しいか
デプロイされると URL エンドポイントもプルリクごとに作成されるので、動作確認したい場合はそのエンドポイントにアクセスすればよく、プルリクのブランチをローカルにプルしてきて起動して動作確認、みたいな手間が省けます。

## サーバーレスアプリ開発でも使えそう！
これがめっちゃ便利だと思ったのでサーバーレスでもやってみます。
サーバーレスってどうしてもデプロイして動作を確認したくなるときがあるのですが、ステージごとに環境を作ると、「これから dev 環境にデプロイします」みたいな不毛なやり取りが発生するし、開発者ごとに環境を作ると、じゃあレビューしてもらう間の開発はどうするのよとなってしまうし、どうしたらいいのかという悩みはありました。
プルリクごとだといい感じじゃないですか？
（実際の開発でこの方法をやっていないのでどうなるかわからないですが。）

実行時だけ課金されるサーバーレスなのでレビュー用環境ぽこぽこ立てるのよさそうじゃないですか。

そして肝は **デプロイごとに完全に分離された環境** ということです。アプリも DB も CFn スタックもです。

## 実装方法
実装方法は大体想像つきますよねー。GitHub のプルリクをフックできるCIサービスを使えばできそう。
今回は **CodeBuild** を使います。そのビルドの中でプルリクごとのソースをビルドしてデプロイできればよさそう。
デプロイには [awslabs/aws-sam-cli](https://github.com/awslabs/aws-sam-cli) を使います。

対象とするリポジトリは GitHub 上に存在する前提でいきます。
私は `sam init --runtime python3.6` をしただけのリポジトリを作ってプッシュしておきました。

今回作ったリポジトリ [ykarakita/serverless_review_apps](https://github.com/ykarakita/serverless_review_apps)

### CodeBuild の設定
CodeBuild でプロジェクトを作成します。ソースプロバイダを GitHub に指定して、対象のリポジトリを選択します。このとき GitHub 連携が必要になります。 
リポジトリを選択すると **Webhook** というチェックボックスが出てくるのでそれにチェックします。さっきやってみたところ、Codebuild のコンソールが新しいものになっていて、そこからだとチェックボックスが出てこなかったので、私はそっと古い画面に戻す設定をしました。

### GitHub の設定
CodeBuild プロジェクトが作成できたら、 GitHub リポジトリの Settings ページに行きます。 Webhooks に Codebuild の webhook が追加されているはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/99248/1f900076-6f4c-b880-7da6-2f27c86900a0.png)

Edit から、Webhook を送信するアクションを指定できます。デフォルトで Pushes と Pull requests が有効になっています。今回はプッシュしたときにビルドが走らなくていいので Pushes のチェックをはずします。

### とりあえず動かしてみる！
これで連携はできるようになったはずなのでプルリク出してみます。
`requirements.txt` の中身を消すプルリクでも出してみましょうか。

![1206-1.png](https://qiita-image-store.s3.amazonaws.com/0/99248/8b882a19-abb4-f6ff-159f-8bae77db165f.png)

おっ、動いてるっぽい
CodeBuild の方はどうだろう

![1206-2.png](https://qiita-image-store.s3.amazonaws.com/0/99248/609dbf5e-588c-a4b7-7fa6-fe378011231b.png)

おっ動いてる。

連携の動作確認できました。 🍺
ただ、これだとビルド設定ファイルがリポジトリ内に存在しないのでビルドに失敗します。

CodeBuild はデフォルトで、ディレクトリ直下の `buildspec.yml` を見に行くので作成します。
とりあえずなんの意味もないやつを置きました。

```buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - ls
  build:
    commands:
```

これを追加したプルリクを出してみます。

![1206-3.png](https://qiita-image-store.s3.amazonaws.com/0/99248/d81b03db-64f3-94c3-5304-a90791378c25.png)

おっ通りました。
もちろんプルリクを出しているブランチにプッシュするたびにビルドが走ります。


## プルリクごとにサーバーレス環境がデプロイされるようにする
サーバーレス環境がデプロイされるように `buildspec.yml` にコマンドを書いていきましょう。

プルリクごとの環境を作るので作成されるリソース名などはすべてプルリクのIDをつけていけばよさそう。
プルリクのIDは CodeBuild 実行環境の環境変数 `CODEBUILD_WEBHOOK_TRIGGER` で取得できます。
出力してみると `pr/3` みたいな形で入ってました。
それをもとにリソースにつける名前の定義をします。

最終的にはこんな感じの buildspec ができあがりました。

```codebuild.yml
version: 0.2

phases:
  install:
    commands:
      - pip install aws-sam-cli==0.6.1

  pre_build:
    commands: 
      - PR=`echo $CODEBUILD_WEBHOOK_TRIGGER | sed -e 's/\///'`
      - APP_NAME=my-review-apps-$PR
      - aws s3 ls s3://$APP_NAME 2> /dev/null || aws s3 mb s3://$APP_NAME

  build:
    commands:
      - pip install -r requirements.txt -t hello_world/build
      - cp hello_world/*.py hello_world/build/

      - sam package --template-file template.yaml --output-template-file serverless-output.yaml --s3-bucket $APP_NAME
      - sam deploy --template-file ./serverless-output.yaml --stack-name $APP_NAME --capabilities CAPABILITY_IAM
```

やっていることは、

- ビルドしたソースを置くためのS3バケット作成（作成済みであればスキップ）
- ライブラリのインストール
- sam package
    - 作成したS3バケットを指定
- sam deploy
    - 作成したS3バケットを指定
    - スタックネームを指定

これで再度プルリクを作成すると・・・

**CloudFormation のコンソール画面**
<img width="785" alt="スクリーンショット 2018-12-06 22.56.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/4a91d093-cd65-820a-8831-c94e7319df74.png">

プルリク単位のデプロイができた！
それぞれのリソースも見てみます。

**API Gateway のコンソール画面**
<img width="306" alt="スクリーンショット 2018-12-06 23.12.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/da0a7235-7213-fb3a-5f25-9a98257c05cc.png">
**Lambda のコンソール画面**
<img width="1057" alt="スクリーンショット 2018-12-06 23.13.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/51144d0d-bf18-cda9-6f65-1ebdc8ffcc8e.png">
ちゃんとプルリク単位になってますねえ。

新しいプルリクが作成されたら新しいスタックが作成されます。
<img width="787" alt="スクリーンショット 2018-12-07 0.03.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/99248/5fe9ba56-bcdc-0a44-9afb-adb546293ba6.png">
うおおおお、これは便利なのでは。

## TODO
これはジャストアイデアなのでもっといい方法があるかもしれないし、本当にこれで開発をするならもっと考慮しなくてはいけないことがあるかもしれない。
Heroku Review Apps は PR がマージなどでクローズされると環境も削除されます。今回やったやつは、自動で削除されないため別途、クローズされたら環境消すよ君を作る必要がある。

## まとめ
CodeBuild 思ったより便利。 GitHub webhook とか環境変数とか、必要なものは揃っていた。