この記事は GLOBIS Advent Calendar 2022 - Qiita の 15 日目です。
Docker build を継続的に行う環境として、2022 年末の現時点では GitHub Actions (GHA)が有力な選択肢の 1 つだと思います。グロービスでも従来は AWS CodeBuild を用いてビルドすることが多かったのですが、最近は GHA を使う場合がほとんどです。
GHA 上で愚直に docker build
コマンドを実行することももちろん可能ですが、Docker 社が GHA をサポートするツールや機能をかなり打ち出しており、これを適格に利用したほうが開発体験をより向上できます。
公式の Docker Docs を読む
とりあえず公式のドキュメントを読みましょう。これさえ読めばむしろこの記事は要らないぐらいかもしれません。
2022 年 11 月頃から build: rework ci/gha docs by dvdksn · Pull Request #15898 · docker/docs などの PR によって、GHA 関連のドキュメントがさらに充実し、まとまった形で読みやすくなりました。 Introduction to GitHub Actions | Docker Documentation からの 3 ページをすべて読むことをおすすめします。
Introdoction の冒頭に書かれている通り、Docker 社では GHA で便利に使える Action を複数用意しており、これらを組み合わせることで、多くのユースケースに合わせたビルドフローを GHA 上で構築できます。基本的には Buildx を有効化し、image を push する先のコンテナレジストリへログインし、 docker build && push
を行うという流れになります。弊社では ECR をレジストリに用いているので、簡単に示すと以下のような YAML になります。
name: Docker Build and Push
on:
push:
branches:
- main
env:
ENV: dev
ECR_REGISTRY_ID: XXXXXXXXXXXX
ROLE_TO_ASSUME: arn:aws:iam::XXXXXXXXXXXX:role/github-actions-role
permissions:
id-token: write
contents: read
actions: read
jobs:
build-and-push:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Set up buildx
uses: docker/setup-buildx-action@v2
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ env.ROLE_TO_ASSUME }}
aws-region: ap-northeast-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
with:
registries: ${{ env.ECR_REGISTRY_ID }}
- name: Build and push images
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
target: ${{ env.REPOSITORY }}
push: true
GitHub 上の情報を image tag に活用する
せっかく GitHub 上でビルドしていることですし、GitHub から取得できるブランチ、commit SHA などの情報を image tag に活用したいところです。
Docker Metadata action · Actions · GitHub Marketplace を使うと、これらの情報を組み合わせた tag を簡単に生成できます。
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
name/app
tags: |
type=semver,pattern={{version}}
type=sha,format=long
type=schedule,pattern={{date 'YYYYMMDD-hhmmss'}}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
docker/metadata-action
の with.tags
に指定した内容に従ってタグが自動生成されます。少々癖もありますし、ここではすべて説明はしませんが、上記の例で言えば type=semver,pattern={{version}}
は、 git tag の push でビルドが発火した際、tag がセマンティックバージョンに従ったものになっていれば、それが image tag として使われます。 type=sha,format=long
は長い形式の commit SHA、 type=schedule
はスケジュール(cron)実行された際に、実行時刻を活用するものです。このように複数行を用いて 3 パターンを指定すれば 3 つのタグが改行区切りで steps.meta.outputs.tags
にセットされるので、あとは build-push-action
にそのまま渡せば OK です。
日付と commit SHA など、複数の要素を複合させて使いたい場合は type=raw
で実現できます。例えば type=raw,value={{date 'YYYYMMDD-hhmmss'}}-{{sha}}
とすると、日付と SHA をハイフン区切りで結びつけたタグを生成できます。
type=gha
のキャッシュを使う
Buildkit にはキャッシュのエクスポート先を指定する --export-cache type=
というオプションがあり、この中に GitHub Actions のキャッシュ API を利用する type=gha
が存在します。
moby/buildkit: concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit
注意点としては、2022 年 12 月時点ではまだ experimental の機能だという点ですが、GHA のキャッシュを手軽に扱うことができて非常に便利です。不具合が起きる可能性のリスクを織り込みつつ活用するのは手ではあるでしょう。GHA 上では、以下のような設定でこの機能を有効化できます。 mode
はデフォルトだと最終イメージのレイヤーしかキャッシュされない min
に設定されているので、すべての中間レイヤーもキャッシュしてくれる mode=max
がほぼ必須になります。
- name: Build and push images
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
type=local
のキャッシュは無限に肥大化する
experimental であることが気になるのであれば、 type=inline
などその他のキャッシュ方式も当然ながら扱えます。その場合、 mode=max
を指定可能でかつ、 GitHub Actions のキャッシュ機能と併用しやすい点で type=local
が有力な選択肢になりそうです。
注意点として type=local
を actions/cache
と組み合わせて使う場合、キャッシュが無尽蔵に肥大化してしまうという点があります。これは GitHub Actions 固有の問題では無く、Buildx 自体がそのようなつくりになっています(詳細は Local cache | Docker Documentation )。回避策として、GHA に保存したキャッシュをそのまま上書きして使うのでは無く、古いキャッシュを削除して入れ替えていくようなフローが推奨されています(cf: Pretty big cache · Issue #252 · docker/build-push-action )。
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: user/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
キャッシュを無効化できるようにしておく
ビルドの失敗など、何らかの理由によりキャッシュを無効にしてビルドし直したい場合も出てきます。GHA では 2022 年にキャッシュを削除する機能も設けられましたが、現状、あるワークフローに結びついたキャッシュを一括で削除するような UI は無く、キャッシュを手で削除して再実行するのは困難です。
docker/build-push-action
には、キャッシュを使わずにビルドする no-cache
というオプションがあるので、Workflow Dispatch からこれを無効化できるようにしておくと便利です。
name: Docker Build and Push
on:
workflow_dispatch:
inputs:
no-cache:
description: "Build docker images with no cache"
default: false
required: false
type: boolean
jobs:
build-and-push:
steps:
- name: Build and push images
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
no-cache: ${{ inputs.no-cache == true }}
# workflow_dispatchを使わなかった場合はno-cacheがNaNになるため、
# 明示的な比較が必要になります。
このように設定しておくと、Workflow Dispatch を実行する際に、チェックボックスでキャッシュの無効化を指定することができるようになります。
matrix を活用する
Docker build においてはマルチプラットフォームのビルド、マルチステージの各ステージのビルドなど、1 回のフローの中で複数のイメージビルドを行いたい場合が多々あります。GHA で複数イメージをビルドする際には matrix を活用し、並行でビルドを走らせることで全体のビルド時間を短縮できます。
matrix を使う場合、各 matrix を示す識別子を用いて、キャッシュを明確に分離する必要がある点には注意が必要です。 local cache を用いながら、非常に多くのジョブを並行させている例として、 fullstaq-ruby-docker/build-push.yml at 9deabb1f7181718cdfc3d18d52cea8eb817c8e65 · evilmartians/fullstaq-ruby-docker · GitHub などが参考になります。
おまけ:Syntax Highlight を効かせる
最後に GitHub Actions とは直接関係ありませんが、GitHub 上で Dockerfile を扱う際のおまけの tips を載せておきます。
GitHub 上のファイルは拡張子で識別されて自動的に syntax highlight されます。Dockerfile の場合はファイル名が Dockerfile
であるか、拡張子として .Dockerfile
が使われている場合に正しく highlight されますが、複数 Dockerfile を同じディレクトリに配置する際、 Dockerfile.hoge
のようなファイル名として、highlight が効かなくなってしまっている、ということもままあります。 hoge.Dockerfile
にすればもちろん解決しますが、ファイル名をソートしたときに Dockerfile.hoge
と Dockerfile.fuga
を並べておきたい、という需要も理解できます。
この解決策として Vim のモードラインを使うという手があります。モードラインとは、ファイルにコメントで Vim の設定コマンドを書いておくと、Vim で当該ファイルを開いたとき、その設定を読み込んでくれるという機能です。例えば # vim: fenc=cp932 ft=markdown
というモードラインは、文字コード(fenc, fileencoding)が cp932、ファイル形式(ft, filetype)が Markdown であることを意味します。
このモードラインで filetype
を指定すると、GitHub の Syntax Highlight に反映させることができます。
なお Vim の場合は最上行にモードラインを書くことが多く、Vim の設定にも「最上行から何行目までモードラインとして読み込むか」というものがあるのですが、GitHub で軽く実験した範囲ですと、十数行目に書いてもモードラインは有効化されたので、邪魔にならないよう最下に書くのもありかもしれません。