Rails
docker
CloudBuild

Google Cloud BuildでRSpecのテストを行う

先日、Google Cloud BuildとGithubの連携が発表され、CIの実現がより簡単になりました。

この記事では、簡単なRailsアプリケーションを用意して、GithubのPull Request時にRSpecでテストを実行し、パスした場合にContainer RegistryにPushするまでの流れをまとめてみました。

前提

以下のコマンドを使用します。

  • docker (18.06.0-ce)
  • gcloud (Google Cloud SDK 210.0.0)

RailsアプリのDocker化

まずはRailsアプリケーションをDocker環境で準備します。
普段はDocker Composeを使って、アプリケーションとDB、KVSなどをそれぞれのコンテナで準備するのですが、今回はCI上で実行しやすいよう、1イメージにRubyとMySQLを含めます。

なお、各バージョンについては以下の通りです。

bash-4.4# ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-musl]
bash-4.4# mysql -V
mysql  Ver 15.1 Distrib 10.1.32-MariaDB, for Linux (x86_64) using readline 5.1
bash-4.4# rails -v
Rails 5.2.0

※ mysqlがMariaDBになっている部分については、後ほど説明します。

Dockerfile

Railsアプリを新規作成後、以下のDockerfileを作成しました。

FROM ruby:2.5.1-alpine3.7

RUN set -ex \
        && apk update \
        && apk upgrade \
        && apk add --no-cache  \
            build-base \
            bash \
            curl \
            nodejs \
            tzdata \
            mariadb \
            mariadb-client \
            mariadb-dev \
            openrc \
        # Setup mariadb (mysql)
        && mkdir -p /run/openrc \
        && touch /run/openrc/softlevel \
        && rc-status \
        && /etc/init.d/mariadb setup \
        && gem install bundler

WORKDIR /web

COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

ADD . .
RUN chmod +x /web/rspec.sh

Rubyの公式イメージを土台にしています。
軽量化を図ってalpineにしたものの、前述したDBが同一イメージに入っているため、 943MB となりました。
この辺りはもう少し見直したいと思います…

接続先のDBですが、現在、alpineのパッケージマネージャでMySQLをインストールするとMariaDBが入ります。
MariaDBとは、MySQLから派生したオープンソースのRDBだそうで、パフォーマンスも良いらしいです。
細かい違いはあるものの、今回の動作確認では問題ないのでこのまま進めます。

rspec.sh は、以下のような内容が入っています。

rspec.sh
#!/bin/bash

# MariaDBの起動
rc-service mariadb start

rails db:create db:migrate
bundle exec rspec

MariaDBの起動後、Railsのいつものやつと、RSpecの実行コマンドです。
最終的には、CI上でこのシェルスクリプトが実行されます。

動作確認

ここまで一旦、動作確認が出来るか試してみましょう。
なにはともあれビルドします。今回はcloud-build-railsというイメージ名にしました。

$ docker build -t cloud-build-rails:v1 .

コンテナ内に入ります。

$ docker run -it -p 3000:3000 cloud-build-rails:v1 bash

MariaDBの起動、DBの準備、サーバーの起動をします。

# コンテナ内
$ rc-service mariadb start
$ rails db:create db:migrate
$ rails s

ブラウザから http://localhost:3000/ にアクセスして、Railsのオリジナルページが表示されればOKです。

モデルのテスト

本題ではないので、さくっと最小限のUserモデルを作っておきます。

$ rails g model user name:string age:integer
$ rails db:migrate

RSpecを導入して、Userモデルのテストを準備します。
ユーザが成人かどうかの簡単なテストです。

app/models/user.rb
class User < ApplicationRecord
  def adult?
    age >= 20
  end
end
spec/models/user_spec.rb
require "rails_helper"

RSpec.describe User, type: :model do
  it "成人かどうかが分かること" do
    user = User.new(name: "enta", age: 25)
    expect(user.adult?).to eq true
  end
end

問題なく通りますね。

Cloud Build

本題です。

プロジェクトの作成

まずは、GCPのコンソールからプロジェクトを作成し、Cloud Build APIを有効にします。
サンプルとしてcloud-build-railsというプロジェクト名にしました。

コマンドライン上でプロジェクトの設定

Cloudリソースへのアクセスを承認後、コマンドライン上でプロジェクトの設定を行います。
PROJECT_ID には、作成したプロジェクトのIDを指定します。

$ gcloud auth login
$ gcloud config set project PROJECT_ID

ビルドリクエストの作成

ビルドイメージの作成やテストの実行、デプロイの設定などをYAMLファイルで管理します。
今回はアプリケーション直下へ以下のファイルを準備しました。

cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/cloud-build-rails', '.']
- name: 'gcr.io/$PROJECT_ID/cloud-build-rails'
  args: ['/bin/bash', '/web/rspec.sh']

images: ['gcr.io/$PROJECT_ID/cloud-build-rails']

Cloud Buildは、stepsに書かれている内容を上から順に実行していきます。
ここでは、大きく2つのステップがあります。

1つはイメージのビルドです。
gcr.io/cloud-builders/docker は、すでにGoogleが用意している環境を使用する宣言です。
他にも git や 言語の go まで用意されており、詳しくはこちらで確認できます。

2つ目に実行しているのは、RSpecのテストです。
序盤で準備したシェルスクリプトを、先程ビルドした環境内で行います。

テストが終わると、最後に imagesにある、ビルドしたイメージがContainer RegistryにPushされます。

次のコマンドで動作確認をしてみます。

$ gcloud builds submit . --config=cloudbuild.yaml

STATUSSUCCESS になれば、Container Registryにイメージがあるはずです。

Container_Registry_-_cloud-build-rails.png

Pull Request時に実行する

最後に、GithubでPull Requestを作成したときに、CIを実行するようにしてみます。
といっても難しい設定は特になく、Github appで Google Cloud Buildをインストールします。

スクリーンショット 2018-08-05 22.51.24.png

All repositories にすると、他のリポジトリでも特別な設定が必要なく、Cloud buildが使えるようになります。

スクリーンショット 2018-08-05 22.51.51.png

Cloud Buildへのアクセスが許可されると、GCPのコンソール上でプロジェクトの作成or選択が必要になるので、既存のプロジェクトを選択 -> cloud-build-rails(作成したプロジェクト)を選択します。

スクリーンショット 2018-08-05 22.53.26.png

スクリーンショット 2018-08-05 22.53.48.png

同意して、接続すれば完了です。

動作確認

試しに、テストの内容を変えて、PRを作成してみます。

スクリーンショット 2018-08-06 2.37.53.png

CI実行中...

スクリーンショット 2018-08-06 2.39.11.png

ちゃんと止まってくれました。

それでは、テストを通すためにコードの修正と、ビルドしたイメージタグが現在は latest なので、コミットのSHAにするよう修正してみます。

コミットのSHA(先頭7文字)は $SHORT_SHA で取得が可能です。

cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/$PROJECT_ID/cloud-build-rails:$SHORT_SHA', '.']
- name: 'gcr.io/$PROJECT_ID/cloud-build-rails:$SHORT_SHA'
  args: ['/bin/bash', '/web/rspec.sh']

images: ['gcr.io/$PROJECT_ID/cloud-build-rails:$SHORT_SHA']

追加コミットをすると、テストが通り、Pushされたイメージのタグには、最新のSHAがつくようになりました。

スクリーンショット 2018-08-06 2.55.53.png

Container_Registry_-_cloud-build-rails 3.png

まとめ

既存のプロジェクトが既にDocker化されていれば、Cloud Build上でのビルド/テストなどは、比較的短時間で行えそうだと感触でした。秘匿情報の取り扱いもKMS(Cloud Key Management Service)が使えますし、ビルドトリガーを使用して指定ブランチへのPush時にGAEへデプロイする、といったことも簡単にできそうなので、もう少しいじってみたいと思います。

気になる料金ですが、CIの実行時間1分あたり、$0.003となっているものの、1日120分までは無料です。
現在使用しているCIサービスに不満を抱えている方は、試してみてはいかがでしょうか。