Cloud BuildでMulti-stageのDockerfileをキャッシュを効かせて適用するにはちょっとした工夫が必要です。
Multi-stage buildsに関しては以下が参考になります。
Docker Multi-Stage BuildsでRailsのイメージを軽量化してみる
通常、dockerをローカルでビルドする場合は、Multi-stageでもあまり気にする事はなく、Multi-stageのメリットの一つであるdockerのキャッシュをうまく利用してビルドを高速化することができます。
しかし、Cloud Buildでは単純なビルドの仕方ではdockerのキャッシュを活用する事ができません。
Dockerfile
便宜上、このDockerfileを利用する前提で話を進めます。(先ほど共有した記事の中で使っているものと一緒です)
細かい処理は気にしないでください。
Fromが二つ入っていてMulti-stageになっている事だけがポイントです。
FROM ruby:2.3.2-alpine as builder
RUN apk --update add --virtual build-dependencies \
build-base \
curl-dev \
mysql-dev \
linux-headers
RUN gem install bundler
WORKDIR /tmp
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
ENV BUNDLE_JOBS=4
RUN bundle install
RUN apk del build-dependencies
FROM ruby:2.3.2-alpine
ENV LANG ja_JP.UTF-8
RUN apk --update add \
bash \
nodejs \
mariadb-dev \
tzdata \
&& rm /usr/lib/libmysqld*
RUN gem install bundler
WORKDIR /tmp
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
COPY --from=builder /usr/local/bundle /usr/local/bundle
ENV APP_HOME /myapp
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
COPY . $APP_HOME
キャッシュが効かない例
Cloud Buildのカスタムビルドで以下のような単純にマニュフェストを用意します。
steps:
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA'
images:
- 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA'
これではキャッシュが効かず毎回1からビルドされます。
キャッシュを効かせる書き方
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'gcr.io/$PROJECT_ID/builder:latest']
waitFor: ['-']
id: 'pull-builder'
- name: 'gcr.io/cloud-builders/docker'
args: ['pull', 'gcr.io/$PROJECT_ID/$REPO_NAME:latest']
waitFor: ['-']
id: 'pull-app'
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '--cache-from'
- 'gcr.io/$PROJECT_ID/builder:latest'
- '-t'
- 'gcr.io/$PROJECT_ID/builder:latest'
- '--target'
- 'builder'
- '.'
waitFor: ['pull-builder']
id: 'build-builder'
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '--cache-from'
- 'gcr.io/$PROJECT_ID/builder:latest'
- '--cache-from'
- 'gcr.io/$PROJECT_ID/$REPO_NAME:latest'
- '-t'
- 'gcr.io/$PROJECT_ID/$REPO_NAME:latest'
- '.'
waitFor: ['build-builder']
id: 'build-app'
- name: 'gcr.io/cloud-builders/docker'
args: ["push", "gcr.io/$PROJECT_ID/builder:latest"]
waitFor: ['build-builder']
- name: 'gcr.io/cloud-builders/docker'
args: ["push", "gcr.io/$PROJECT_ID/$REPO_NAME:latest"]
waitFor: ['build-app']
ポイントは4つ
- ステージ毎にステップを分ける
- ステージ毎にイメージをビルドしてpushする
-
--cache-from
で明示的に参照するキャッシュを指定する -
id
とwaitFor
で可能な限り並列化
これでアプリケーションのソースの更新だけの場合など、キャッシュが有効になり高速にビルドされるようになります。
注意点
- 各ステージ毎にイメージをpull, pushしてるのでその分の時間がかかります。
- この例の
pull-builder
のイメージをpullしてくるところは初回はイメージが存在しないのでビルドに失敗します。初回は、コメントアウトしておき、二回目以降からpullするようにします。
ステージを分けすぎると、オーバーヘッドがでかくなるので必要であればステージを統合するなどする必要があります。
さいごに
キャッシュやMulti-stageなど、dockerのエコシステムは素晴らしい設計がされていると思います。
dockerのビルドツールなどでもそれをうまく活用していきたいですね。