GitHub Action 上で docker layer をキャッシュする
GitHub Action を CI やビルドツールとして使うとき,手元と動作を完結にさせたいので docker で済ませたいことがある. コンテナリポジトリにある docker image から直接 run できる場合は pull するだけで良いが, Dockerfile を別途用意する場合は image の取得に加えてビルドが必要になり,毎回時間がかかる.
例えば,circleci を使用する場合は,docker layer をキャッシュするオプションを追加するだけで良いが, GitHub Action には(現状)そういった使い勝手の良いキャッシュは無い.
代わりに任意のパス以下をキャッシュしてくれる組み込みの機能が存在している.
ただ,この actions/cache はまだまだ機能が不十分で,docker layer のキャッシュには工夫が必要だったのでこの記事はその対策を書く. 記事公開時点での対策であるため,公式なサポート等が出た場合はそれを利用することを推奨する.
actions/cache の制約
指定したパス以下をなんでもキャッシュしてくれるわけではなく,使用にあたっていくつかの制約が存在する.
- パスごとの最大サイズは 400MB
- リポジトリごとに最大のサイズが 2GB
- 2GB を超える場合は古いキャッシュから順に削除される
2GB は仕方ないとして,docker layer のキャッシュとしてディレクトリごとに 400MB は制約が結構厳しい.
対策: 複数のパス以下に分散して配置する
あまり難しいことを考えずに,docker history
コマンドから layer のハッシュ値を持ってきて,それを docker save
で書き出す.
もし 400MB を超える場合があるならば複数ファイルへ分割して配置する.
以下はサンプル.cache の複数パス指定は現段階ではサポート外であるため*1, 残念ながら冗長に必要な分だけ配置する必要がある. サンプルは2分割であるため冗長に書いているが,必要に応じて分割数を指定できるスクリプト等を用意したほうが良いかもしれない.
name: Build and Run with cache on: [push] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - uses: actions/cache@v1 name: Splitted cache 01 id: cache01 with: path: docker-cache/01 key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}-01 restore-keys: ${{ runner.os }}-docker-01 - uses: actions/cache@v1 name: Splitted cache 02 id: cache02 with: path: docker-cache/02 key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile') }}-02 restore-keys: ${{ runner.os }}-docker-02 - name: Load cached docker layers if: steps.cache01.outputs.cache-hit == 'true' run: | cat docker-cache/*/file > layer-cache-sample.tar docker load < layer-cache-sample.tar - name: Build docker image with cache if: steps.cache01.outputs.cache-hit != 'true' run: | docker build --file Dockerfile --cache-from layer-cache-sample --tag layer-cache-sample . docker save layer-cache-sample $(docker history -q layer-cache-sample | awk '!/<missing>/{print}') > layer-cache-sample.tar rm -rf docker-cache && mkdir -p docker-cache/01 docker-cache/02 split -n l/1/2 layer-cache-sample.tar > docker-cache/01/file split -n l/2/2 layer-cache-sample.tar > docker-cache/02/file - name: dokcer run run: docker run --rm layer-cache-sample:latest echo "Hello World!"
補足
docker layer のキャッシュについては以下の PR でサンプルについて議論されていて(記事執筆時点ではまだ議論中), 今回はその内容を参考にしつつ書いた.