Cloud Build を使ってみた

https://cloud.google.com/cloud-build/?hl=ja

コンテナイメージを作る用のサービスらしいが、他にも色々できる。

機能

  • トリガーには GitHub の場合はブランチかタグを正規表現で指定できる
  • 特定のファイルに変更がある場合のみトリガーしたり、変更があっても無視するファイルを指定したりできる。
    ドキュメントファイルのみの更新などに便利そう
  • Dockerfile を使ったビルド、または cloudbuild.yaml ファイルを使ったビルドを行える。
    docker build 以外のことをしたい場合はおそらく cloudbuild.yaml 一択になる

せっかくなので今回は cloudbuild.yaml を使ってみる。

幸いにも AppEngine にデプロイするときの内部での Cloud Build のログが残っているのでそれを参考に作ってみる
リポジトリのディレクトリ構成は以下のようになっている

- docker/          開発環境構築用のDockerfileなどを管理
- src/             プロジェクトのソースコード本体
- cloudbuild.yaml  [NEW]

cloudbuild.yaml はこんな感じにしてみた。

_から始まる変数はトリガー作成時に変数として作成した値。

詳しい内容はリンク先を参照 https://cloud.google.com/cloud-build/docs/build-config?hl=ja

steps:
  - name: "gcr.io/cloud-builders/docker:latest"
    args: [
      "build", "--network=cloudbuild",
      "--build-arg", "env=$_RAILS_ENV",
      "--build-arg", "master_key=$_MASTER_KEY",
      "-t", "asia.gcr.io/$PROJECT_ID/build/$BRANCH_NAME:latest",
      "."
    ]
    dir: src
images: ["asia.gcr.io/$PROJECT_ID/build/$BRANCH_NAME"]

Dockerfile は AppEngine の CodeBuild 時に生成される Dockerfile を確認して作成した。ほぼコピペしてコメント消したくらい。

実際には app.yaml によって生成される Dockerfile は異なるので参考程度に。

############
# STEP1
############
FROM gcr.io/gcp-runtimes/ruby/ubuntu16:latest AS augmented-base
ARG ruby_version="2.5.1"

COPY --from=gcr.io/gcp-runtimes/ruby/ubuntu16/prebuilt/ruby-2.5.1:latest \
  /opt/rbenv/versions/${ruby_version} \
  /opt/rbenv/versions/${ruby_version}

RUN rbenv global ${ruby_version} \
  && rbenv rehash \
  && (bundle version > /dev/null 2>&1 \
  || gem install bundler --version ${DEFAULT_BUNDLER_VERSION}) \
  && rbenv rehash

ENV TZ="Asia/Tokyo"

############
# STEP2
############
FROM augmented-base AS app-build

COPY --from=gcr.io/gcp-runtimes/ruby/ubuntu16/build-tools:latest /opt/ /opt/

ENV PATH /opt/bin:/opt/google-cloud-sdk/bin:/opt/yarn/bin:${PATH}

RUN ln -s /opt /build_tools \
  && ln -s /opt/bin/cloud_sql_proxy /opt/cloud_sql_proxy \
  && ln -s /opt/bin/access_cloud_sql /opt/access_cloud_sql

COPY . /app/

ARG BUILD_CLOUDSQL_INSTANCES="xxxxxxxxxxxxxxxx"
RUN mkdir /cloudsql

RUN bundle install --deployment --without="development test" && rbenv rehash

# ベースのイメージは RAILS_ENV=productionなので、ステージング環境にも対応させる
# CloudBuildのトリガーでmasterブランチの場合はproduction、stagingブランチの場合はstagingを渡すようにトリガーを2つ作成してある
ARG env="production"
ARG master_key

# AppEngineでRailsをデプロイするときは assets:precompile をやってくれていなかったので追加した。
# yarnをデフォルトで入れているならやってくれてもいいのに…
RUN RAILS_ENV=${env} RAILS_MASTER_KEY=${master_key} bundle exec rails assets:precompile

############
# STEP3
############
FROM augmented-base

# cloudbuild.yamlから渡した値で環境変数を上書きする。
ARG env="production"
ARG master_key
ENV RAILS_ENV $env
ENV RAILS_MASTER_KEY $master_key

COPY --from=app-build /app/ /app/

CMD exec bundle exec rails server -p $PORT -e $RAILS_ENV

まとめ

なんとなくやっていた App Engine でのデプロイの仕組みがわかった。
Cloud Build → Container Registry (本体はCloud Storageに) → イメージを GCE上にデプロイ

(実際にはもっと複雑だけど。ロードバランサーとか。)

今回使わなかったけど便利な機能

基本的には順番に実行されていくが、 waitFor で順番を調整できる

https://cloud.google.com/cloud-build/docs/configuring-builds/configure-build-step-order?hl=ja

以下の場合は foo のビルドが終わってから bar をビルドする

steps:
- name: foo
- name: bar

以下の場合は A と B が同時にビルドされ、両方が終わってから baz のビルドが始まる

steps:
- name: foo
  id: A
- name: bar
  id: B
  waitFor: ['-']
- name: baz

以下の場合は A のビルドが終わってから B と C のビルドが同時に始まる

steps:
- name: foo
  id: A
- name: bar
  id: B
  waitFor:
  - A
- name: baz
  id: C
  waitFor:
  - A