CodeBuildで(docker|docker-compose)のビルドをキャッシュ使用して実行する


# 2019/3/4追記

CodeBuildが公式でDockerのレイヤーキャッシュに対応しました :tada: 
[docker buildを高速化！CodeBuildのローカルキャッシュ機能を試してみる](https://dev.classmethod.jp/cloud/aws/codebuild-local-cache/)
以降はこちらの方法でキャッシュ有効にするのが良いと思います。

※上記方法は `--cache-from` を指定しているとキャッシュ効かないみたいなので、気をつけて下さい。

# はじめに

CodeBuildはコンテナレイヤーのキャッシュには対応していないため、
コンテナベースのCI/CDをCodeBuild上で構築している場合は
毎回フルスクラッチでビルドし直す必要があり、とても時間がかかってしまいます。

このビルド時間を少しでも軽減するテクニックとして、ビルド済みのECRのコンテナイメージを
pullして`--cache-from`で使用するというものがあるので紹介します。

# 前提

ビルド環境に入っているdockerが`--cache-from`をサポートしている必要があります。
公式イメージを使用してビルドするなら `Docker 17.09` 以上を選択すればOKです。
また、オリジナルイメージを使用してビルドするならこちらの記事が参考になります。
[AWS CodeBuild カスタムDockerイメージを使ってビルドする](https://qiita.com/tiibun/items/f0045011c86efca254fc)

# docker buildでキャッシュを使う

buildspecを以下のように書きます。ポイントは2つ、

 - buildする前にECRにログインしてコンテナイメージを引っ張ってくる
 - buildの引数に--cache-fromを付けて、引っ張ってきたイメージを使用する

だけです。

```yml:buildspec.yml

version: 0.2
phases:
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email) 
      - docker pull $REPOSITORY_URL:$TAG_NAME || true
  build:
    commands:
      # --cache-fromをつけることで、爆速になる
      - docker build --cache-from $REPOSITORY_URL:$TAG_NAME -t $REPOSITORY_URL:$TAG_NAME .
      # 作ったあとはこのように呼び出してよしなにいろいろやる
      - docker run --rm $REPOSITORY_URL:$TAG_NAME npm run test
      - docker run --rm $REPOSITORY_URL:$TAG_NAME npm run lint
  post_build:
    commands:
      # deployならpushもいつもどおりやる
      - docker push $REPOSITORY_URL:$TAG_NAME
```

念のためですが…

- `$REPOSITORY_URL`や`$TAG_NAME`は環境変数です。実際のURLは  
  `123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/hoge/fuga:develop`  
  みたいな感じになりますかね。
- タグは適宜付けて下さい



また、公式イメージ以外を使ってビルドする場合はinstallフェーズにDockerデーモンを立ち上げて
利用可能になるまで待機するコマンドを足す必要があります。
**公式イメージをベースに自分で作ったコンテナを使用する場合もこれが必要なので注意**


```yml
version: 0.2
phases:
  install:
    commands:
      - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 --storage-driver=overlay&
      - timeout -t 15 sh -c "until docker info; do echo .; sleep 1; done"

  pre_build:
   .
   .
   .
```

# docker-compose buildでキャッシュを使う

基本的にdocker buildと変わりませんが、コマンドラインの引数として`--cache-from`を渡すのではなく、
docker-compose.ymlファイルに`cache_from`項目を足して指定します。
**docker-composeのversionは `3．2` 以上を指定しないと対応していないので注意して下さい。**

ちなみに、`cache_from`は複数指定できるみたいです。その場合は最初にHITした
イメージがキャッシュとして使われ、HITしなかった場合は自前でビルドされます。

```yml:buildspec.yml

version: 0.2
phases:
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email) 
      - docker pull $REPOSITORY_URL:$TAG_NAME || true
  build:
    commands:
      docker-compose build test
      docker-compose run test
```

```yml:docker-compose
version: '3.2' # 3.2以上じゃないと対応していないので注意！！！
services:
.
.
.
  test:
    build:
      context: .
      # これ！！
      cache_from: 
        - $REPOSITORY_URL:$TAG_NAME
    command: npm run test
```

# 終わりに

キャッシュと言いつつ、厳密にCodeBuildを走らせる環境にローカルキャッシュされたものを
使っているのではなく、結局ECRからpullしてるので、pullして取ってくる部分のオーバーヘッドが
あるということは意識しないといけません。

適切に設計されたdockerコンテナイメージであれば大抵速くなると思いますが、
レイヤー構成的に半分くらいしかキャッシュが効かなくて結局半分くらい自前で
ビルドする必要があるようなイメージや、データ量だけ多くて馬鹿みたいにデカい
コンテナイメージ等はむしろpullする部分のオーバーヘッド分で逆に遅くなることもあります。

逆に、データ量は少ないけどCPUをぶん回す必要があるようなコンテナは
劇的なスピードアップを見込めると思います。

なので、キャッシュを使用する価値があるかどうかは実際に試してみて判断して下さい。