AWS CodeBuildでcomposer installしたライブラリをキャッシュする

# はじめに
Code兄弟のCodePipeline,CodeBuild,CodeDeployを利用してCI/CD環境を構築した際（ソースはBitBucket）に、
composer installしたライブラリをキャッシュする方法の記事がなく時間がかかったため作成します。

# なぜキャッシュをするのか
キャッシュをすることによりデプロイに、要する時間を短縮できるためサービスを早く提供することができるメリットがあります。
小規模なインフラ構成の場合は多少の時間しか短縮できませんが、少しでも短い方が良いと思うので是非参考にしていただければと思います。

# CodeBuildのキャッシュ機能
CodeBuildのキャッシュ機能は2通りあります

- S3キャッシュ  
  - 複数のビルドホスト間で利用できるキャッシュ
  - ダウンロードするよりも構築にコストがかかる小規模な中間ビルドアーティファクトに適したオプション
  - ネットワーク経由で転送するには長い時間がかかる場合があるため、大規模なビルドアーティファクトには適していません
  - Docker レイヤーを使用する場合、これは最適なオプションではありません。
- ローカルキャッシュ(buildを実行するホストに保存する)  
  - ビルドホストのみが利用できるキャッシュをそのビルドホストにローカルに保存します  
  - キャッシュはビルドホストですぐに利用できるため、この方法は大規模な中間ビルドアーティファクトに適しています  
 
[AWS公式記事「AWS CodeBuild でのキャッシュのビルド」](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-caching.html)

# ローカルキャッシュはキャッシュモードが3つある
キャッシュする内容を選ぶことができる、キャッシュモードが3つあります。  

- ソースキャッシュモード  
- Docker レイヤーキャッシュモード  
- カスタムキャッシュモード  
それぞれの内容は公式記事を参照してください。

今回私のケースは以下

- 小規模なアプリ  
- dockerを使用  
- ビルドホストは1つ 

せっかくなので実行の比較をしてみましょう。(composerのキャッシュの)

# ローカルキャッシュとS3キャッシュの実行時間の比較
それぞれの設定で2回実行し、2回目の実行時間を比較します。
ローカルキャッシュに関しては、`カスタムキャッシュモード`を使用してみます。

# buildspecファイルを作成する際にcacheを利用するようにする
codebuildを使用する際は、buildspecファイルを作成する必要があります。
そのファイルの記述の中で、キャッシュを利用することを記述する箇所があるため、そこにcompopserのcacheディレクトリを指定することで、
実現することができます。





## つまづいたポイント1
`composer install`をdockerファイル内で実行していると、コンテナ内にcompoesrのキャッシュファイルができるため、
キャッシュすることができなかった。
解決する方法としては  

1. ホスト側で`composer install`する。
2. ホスト側とコンテナ側で依存ライブラリのディレクトリをマウントして同期させる

今回は1の方法を取りたいと思います。

buildspecファイルの内容は以下になります。

```buildspec.yml
version: 0.2

phases:
  install:
    ## ここでphpの実行環境のバージョンを指定する
    runtime-versions:
      php: 7.3

  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)

      - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION

      - REPOSITORY_URI=********.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/restful-api

  build:
    commands:
      ## ホスト側でcomposer install
      - composer install

      - docker build -t $REPOSITORY_URI:$IMAGE_TAG .

      - docker push $REPOSITORY_URI:$IMAGE_TAG

  post_build:
    commands:
      - echo Writing image definitions file...
      - printf '[{"name":"restful-api", "imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json

artifacts:
  files:
    - imageDetail.json

 ## ここがキャッシュを指定する箇所！！！！
cache:
  paths:
    - '/root/.composer/**/*'
```  

注目していただきたいのは`cache`項目になります。

# つまづきポイント2
実際にbuild実行したところ、2回以上build実行しても、キャッシュが利用されている形跡がなく実行時間も短くなりません。
ローカルキャッシュの`カスタムキャッシュ`は先ほどのbuild.specのままではdockerでは使えないという問題がわかりました。
原因は、以下の実行時のログを見ると気付きました。

```ログ

Symlinking: /root/.composer => /codebuild/local-cache/custom/a29cac2ab2bc5a9ecb43317ff3dd8847f93bf828b0606fda90fa7db63d27cebb/root/.composer

```  
ローカルキャッシュのカスタムキャッシュの仕組みはキャッシュ対象のパスから、codebuildのキャッシュ用ディレクトリ(?)に対して、Symlinkを貼るという内容でした。
dockerではホスト・コンテナ間でのSymlinkはサポートしていないため、キャッシュが取れませんでした。(この事実を知るのに、かなり時間がかかったのに。。。)

# どうやって実現できるのかというと
大変参考になる方法を見つけたので、(参考記事「AWS CodeBuildのローカルキャッシュのCustom cacheでnode_modulesやvendorをキャッシュする
」)[https://tech.moyashidaisuke.com/entry/2019/03/19/205243]に対応方法を真似することにしました。

```buildspec_v2.yml

version: 0.2

phases:
  install:
    ## ここでphpの実行環境のバージョンを指定する
    runtime-versions:
      php: 7.3

  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)

      - IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION

      - REPOSITORY_URI=********.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/restful-api
      ## キャッシュを利用する
      - cp -r /root/.composer_cache/ /root/.composer/
  build:
    commands:
      - composer install

      - docker build -t $REPOSITORY_URI:$IMAGE_TAG .

      - docker push $REPOSITORY_URI:$IMAGE_TAG

  post_build:
    commands:
      - echo Writing image definitions file...
      - printf '[{"name":"restful-api", "imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
      ## 最新のキャッシュファイルをローカルキャッシュ用ディレクトリにコピーする
      - cp -r /root/.composer/ /root/.composer_cache/

artifacts:
  files:
    - imageDetail.json

cache:
  paths:
    - '/root/.composer/**/*'
```

やっていることは、
1. カスタムキャッシュしたローカルキャッシュ用ディレクトリをキャッシュ利用したかったディレクトリにコピーする
2. build後に最新のキャッシュをローカルキャッシュ用ディレクトリにコピーしてあげる

# 実行結果
S3キャッシュ利用時
![スクリーンショット 2020-05-17 19.17.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289963/cb700727-d3cc-9d64-ec69-2eb307939ef6.png)

ローカルキャッシュ(カスタムキャッシュモード)利用時
![スクリーンショット 2020-05-18 0.18.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289963/72e1cdc3-074c-319c-f56b-5e3c6026c531.png)

今回のケースでいうとS3キャッシュを利用する方が早く終わりました。


# 備考(インフラ構成図)
![インフラ設計図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289963/ecda4100-1a31-0d6c-2c4a-d3872384eb1a.png)
色々使用しておりますが、今回関係のある箇所は赤枠で囲われた部分になります。
CodePipeline,CodeBuild,CodeDeployを使用してECSにデプロイをしております。

# 最後に
今回のS3とカスタムキャッシュで、composerのキャッシュを利用した場合、S3の方が早かったですが、ローカルキャッシュは他にも2つのキャッシュモードがあるため、それを合わせて利用すれば早くなりそうです。(実際に試したが、Docker レイヤーキャッシュモードがキャッシュ利用できなかったので、今度調べてみようと思います。)