CloudBuildをGitHubと連携してRailsのCIを回してみる

昨年GitHubにChecks APIというCI系サービスと連携する機能が導入され、CIの結果が分かりやすく見れるように。

スクリーンショット 2019-03-18 15.24.37.png

GCPのCIサービスであるCloudBuildもChecks APIに対応しているので今回設定した方法を共有したいと思います。

どちらかというとCI部分に苦戦したのでGithub側はあっさりめに書いてます。


Cloud Build と Github Checksの連携

とりあえずCloudBuildとGithubを連携しないことには始まらないので、まずはGithubから設定してみます。

https://github.com/apps/google-cloud-build

連携方法はこちらの記事でも紹介がありましたので、そちらに譲ることとします。

https://medium.com/google-cloud-jp/try-github-cloudbuild-integration-5149175105fb


注意点

Githubのレポジトリは複数または全部を対象にできるのに対して、GCPプロジェクトは一つしか対象できないようです。

プロジェクトが1つの場合は特に困らないですが、会社で複数のGCPプロジェクトを扱っている場合、どこか特定のプロジェクトに紐づけてしまうとそのプロジェクトでCIが回ってしまいます。

ここでは共有用のプロジェクトを作成しCIをそのプロジェクトに集約することにします。

ここまで設定が完了すると、Githubにbranchをpushする度にCloudBuildでCIが走ってくれることになります。


CI設定

CloudBuildではレポジトリ直下に


  • Dockerfile

  • cloudbuild.yaml

がある場合にのみ、CIを走らせてくれるようです。

なお両方ある場合はcloudbuild.yamlの設定が優先されるとのこと。

https://cloud.google.com/cloud-build/docs/run-builds-with-github-checks?hl=ja#preparing_a_github_repository_with_source_files

今回はDockerfileでビルドをまとめてみました。

やってから思いましたが、cloudbuild.yamlで書いた方がコマンドは柔軟に書けそうです。。(gcr.io/cloud-buildersにあるコマンドに限る)


Dockerfile

Dockerfileの場合はビルド成功 = Checks API成功ということなので、テストで落ちてほしい場合はきちんとDockerfileのビルドに失敗する(=exit code 1以上)必要があります。

今回はRailsのCIを回すということで以下のテストを実行します。


  • rubocop (静的解析)

  • RSpec (DBなどのBDDテスト)

rubocopは静的解析なのでRUNコマンドで実行するだけで問題なし。

RSpecに関してはコードだけのチェックではなく、DBやredisが必要になるためコンテナ上にDBを立ち上げます。

別途サーバーを立ててもいいですが、お金がかかってしまうので毎日のCIとしては現実的じゃなさそうです。

また今回のように共有プロジェクトを使っている場合、本来のプロジェクトとは別の場所でDBのスキーマの管理なども考えないといけないので、なるべくDockerだけで完結できる方が運用的にも楽です。

ここではmysql5.7のイメージをベースにredisとrubyを導入して、RailsのCIコマンドを記述してみます。


Dockerfile

FROM mysql:5.7.24

ARG RUBY_VERSION=2.5.3

ENV PATH=/root/.rbenv/shims:$PATH

# prepare ruby environment
RUN set -ex \
&& apt-get update \
&& apt-get install -y \
rbenv \
git \
wget \
autoconf \
bison \
build-essential \
libssl-dev \
libyaml-dev \
libreadline6-dev \
zlib1g-dev \
libncurses5-dev \h
libffi-dev \
libgdbm3 \
libgdbm-dev \
libmysqlclient-dev \
redis-server \
software-properties-common \
&& mkdir -p "$(rbenv root)"/plugins \
&& git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build \
&& mkdir /app

WORKDIR /app

RUN rbenv install $RUBY_VERSION \
&& rbenv global $RUBY_VERSION \
&& gem install bundler \
&& rbenv rehash

# custom setting for mysql
ENV MYSQL_ALLOW_EMPTY_PASSWORD true

# ここからRailsのビルド関連
COPY Gemfile Gemfile.lock /app/

RUN bundle install && rbenv rehash

COPY . /app/

# test codes without data
RUN rubocop

# test logic with data (中でRSpecを実行)
RUN sh /app/bin/ci.sh


RSpecは最後のシェルスクリプトの中で実行しています。

最後のDockerの仕組み上、プロセスをRUNで実行しっぱなしにしておけない(RUNが終わったらプロセスがなくなってる)ため、

mysqlやredisはシェル内で起動して、そこでRSpecも実行するようにします。


ci.sh

#!/bin/sh

/entrypoint.sh mysqld &
service redis-server start

sleep 10 # mysqlの立ち上がりを待つ

RAILS_ENV=test bundle exec rails db:create
RAILS_ENV=test bundle exec rails db:migrate
bundle exec rspec


各コマンドが失敗時にexit code 0以外になるように書いておけば、CIも失敗時に落ちてくれます。


CI実行

実行はGithubへのpushがトリガーになります。(正確にはcommitのpushなので、名前だけ違うbranchは対象外)

手動設定のトリガーと違い、Github Checksは連携したrepoの全てのbranchが対象になるようです。

実行中は黄色いマークがcommit hashの前について、そこからCloud Buildの詳細画面にも飛ぶことが出来ます。

成功時は緑のチェック、失敗すると赤いバツに変化します。

スクリーンショット_2019_06_13_14_16.png

スクリーンショット 2019-06-13 14.21.37.png

失敗のログはCloud Build側でログが見れます。

ここではrubocopの静的解析で落ちたことが確認できます。

Cloud_Build_-_visits-technologies-share_-_Google_Cloud_Platform.png


おわりに

というわけでGithubとCloud BuildのCI連携でした。

コードレビュー時のコーディング規約等やテスト通ってないなどの機械的な指摘は、CIに回してしまえると不要なコミュニケーションを取らずに済むのでありがたいですね。

とはいえCloud Buildもまだまだ足りないなと感じることもありますし、日々改善が加えられているので、また何かにぶつかったらQiitaに残したいと思います。