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


はじめに

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

なぜキャッシュをするのか

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

CodeBuildのキャッシュ機能

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

  • S3キャッシュ
    • 複数のビルドホスト間で利用できるキャッシュ
    • ダウンロードするよりも構築にコストがかかる小規模な中間ビルドアーティファクトに適したオプション
    • ネットワーク経由で転送するには長い時間がかかる場合があるため、大規模なビルドアーティファクトには適していません
    • Docker レイヤーを使用する場合、これは最適なオプションではありません。
  • ローカルキャッシュ(buildを実行するホストに保存する)
    • ビルドホストのみが利用できるキャッシュをそのビルドホストにローカルに保存します
    • キャッシュはビルドホストですぐに利用できるため、この方法は大規模な中間ビルドアーティファクトに適しています

AWS公式記事「AWS CodeBuild でのキャッシュのビルド」

ローカルキャッシュはキャッシュモードが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キャッシュ利用時

ローカルキャッシュ(カスタムキャッシュモード)利用時

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

備考(インフラ構成図)


色々使用しておりますが、今回関係のある箇所は赤枠で囲われた部分になります。
CodePipeline,CodeBuild,CodeDeployを使用してECSにデプロイをしております。

最後に

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