Codebuildで安く楽する話

この記事は Progate Advent Calendar 2017 の11日目です。
今日はJenkins上のCIをAWSのCodebuildに移して幸せになった話をしようと思います。

## 前提
現在のProgateでは、コンテンツの制作にリソースの多くを注いでいるため、コードのコミット自体は多い方ではありません。
しかし、コミットの時間帯が集中したり、他のJenkinsタスクが走る時間帯とかぶると、なかなかテストが通らないことがあり、エンジニアから苦情が出る、ということが起こります。

## Jenkinsのしんどいところ

弊社でもつい最近まで、m4.xlarge上のJenkinsを使ってPull Requestにテストを走らせていました。
機械的なチェックで、linterやユニットテストなど、最低限の品質を担保する上で大いに役立っていたのですが、課題もありました。

いくつか例を挙げると

- スペック的にもうテストが足せない…
- テスト項目同士の依存関係の管理が大変
- ディスク容量との兼ね合いで、数日経つとビルド結果が消える
- Jenkinsが混むと、なかなかビルドが始まらない
- 長時間実行しているJenkinsでなぜかエラーになる
  - メモリが足りなくなったりDNSエラーで `git pull` が落ちてたり…
- etc...


## CI as a Service なソリューションは高い

これらの解決策として、一番に思いつくのは Circle CI や Travis CI などの活用です。

しかし、Pricing のページを見るとわかるように、これらのサービスは __高い！__

たとえば Circle CI だと、4人が同時にCIをまわそうとすると [1万5千円もします](https://circleci.com/pricing/)。

「問題をカネで解決して時間を買う」という考え方をするなら妥当な選択肢ではあるのですが、常時なにかしらコミットがなされている、というわけでもないので、ちょっと勿体無い感じがしますよね。


## そこでCodebuildですよ
今回、弊社のCIで「つらい」部分をまとめると次のようになりました。

- 実行環境のスペックを気にせず、必要なテストを足したい
- ただし実行時間が伸びるのはNG
  - 並列にスケールしてほしい
- 「なぜかエラーになる」というのはNG
- CI項目はリポジトリ側で管理できる
- 結果はいつでも見られる

[Codebuild](https://aws.amazon.com/jp/codebuild/) は去年の re:Invent で発表されたもので、Dog Year なこの業界においてはそこまで新しい選択肢でもないのですが、今回の弊社のユースケースにおいてはぴったりでした。

Codebuildの特徴を簡単にまとめると、以下のような感じになります。

#### 完全マネージド型
実行環境に関してほとんど気を使わなくてよくなります。

#### 従量課金
深夜などの待機時間には課金されません。

#### 実行環境はDocker
dockerのイメージ化しておけば、特殊なビルド環境でも利用できます。

今回Progateでは利用していませんが、ビルドの成果物をS3などに置くこともできます。


## Codebuild の使い方

GitHub上のリポジトリに対して、Codebuildで何か実行しようとすると、一般的には次のようになるかと思います。

1. AWSのコンソールからCodebuildにプロジェクトを作る
2. プロジェクトにリポジトリを登録する
3. リポジトリの適当なところに `buildspec.yml` を置く
4. どうにかして `aws codebuild start-build --project-name={プロジェクト名}` を叩く

`buildspec.yml` はS3に置いたりAWSのコンソールからCodebuildのプロジェクトに登録することもできますが、CI項目をリポジトリ側で管理することを考えると取りづらい選択肢でしょう。

また、Circle CI や Travis CI のように「`git push` に反応して自動でビルドする」といった機能は現在のところ提供されていないので、自前でなんとかする必要があります。


### 実装方針
今回は、Lambda で解決することにしました。

> GitHub PUSH -> Amazon SNS -> Lambda

の流れで実行して、

- `start-build` を叩く動作
- GitHub側に [Commit Status](https://developer.github.com/v3/repos/statuses/) (`pending`) をつける動作

を Lambda にやらせます。

GitHub と Amazon SNS の連携は [こちら](https://aws.amazon.com/blogs/compute/dynamic-github-actions-with-aws-lambda/) が参考になります。


また、ビルドの終了をCloudWatch Eventで拾って、そこから別の Lambda にやらせます。

> Cloudwatch Event -> Lambda

ここで、`success` または `failure` の Commit Status をつけます。


### 実行時間・並列度について

`buildspec.yml` 中に

```
build:
  commands:
    - bundle install
    - npm install
    - rspec
    - eslint
    - flow
    - ...
```

と書き連ねても良いのですが、これだと、項目が増えるごとにCI時間が長くなってしまい、あんまり幸せになれないです。


さいわい Codebuildでは、プロジェクト作成時だけでなく、`start-build` の際にも `buildspec.yml` について設定することができます。

そこで、Progateでは、各 `buildspec.yml` の実行時間が大体同じになるよう、リポジトリ上の　`buildspec/` の下にファイルを複数用意することにしました。

用意した `buildspec.yml` たちの一覧は、GitHub API を使えば次のように取得できます。

```
curl \
 -u {username}:{token} \
  'https://api.github.com/repos/{owner}/{repository}/contents/{path_to_file_or_directory}?ref={branch}'
```

あとは各 `buildspec.yml` について、`aws codebuild start-build` してやれば、無事に望みのものが手に入るはずです！

実行結果は `https://${region}.console.aws.amazon.com/codebuild/home?region=${region}#/builds/${id}/view/new` で取得できます([コンソールへのアクセス権](http://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html#console-permissions)が必要です)

### 実行時間について
`docker pull` 以後の動作時間については課金対象としてカウントされるので、`bundle install` やら `npm install` やらは `buildspec.yml` の中でやりたくないですよね。

実行環境で使用する docker イメージは指定できるので、こういった準備を事前に済ませたイメージをビルドして、ECRに入れて置くのが良さそうです。

## 移行した結果

冒頭で挙げた、「Jenkinsのしんどいところ」はほぼ解消されました。

- コンテナのプロビジョニングにかかる時間で多少前後するものの、おおむね5〜10分でテスト結果が出るようになった
- ビルド結果が消える、ディスクが溢れる、ときどきエラーになる、といった煩わしさから解放された
- CI項目を `buildspec.yml` としてコード上で管理できるようになった


## 課題
CodebuildのLimitにも[書いてある](http://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/limits.html)のですが、同時に実行できるビルドの数には上限があります。

節操なく `start-build` しまくって上限を超えてしまうと、ビルドが失敗します。

とりあえずQueueに突っ込んでよしなに処理する、みたいな機能はCodebuild側には用意されていませんので、そこも自前でやる必要があります。


## 最後に
プロダクトのフェーズが変わってコミットが増えてくるとまた状況が変わるかとは思いますが、現状では、安くて便利なビルド環境が確保できて生産性が向上しました。

コードがほとんど公開できないために味気ない記事になってしまいましたが、ご興味を持たれた方は是非試してみていただけると幸いです。