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

先日、Google Cloud BuildとGithubの連携が[発表](https://cloudplatform.googleblog.com/2018/07/go-fast-safely-and-easily-with-cloud-build-and-github.html)され、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上で実行しやすいよう、１イメージに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を作成しました。

```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`というイメージ名にしました。

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

コンテナ内に入ります。

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

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

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

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

### モデルのテスト

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

```shell
$ 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のコンソール](https://console.cloud.google.com/flows/enableapi?apiid=cloudbuild.googleapis.com&hl=ja&_ga=2.145606577.-422810268.1532783025)からプロジェクトを作成し、Cloud Build APIを有効にします。
サンプルとして`cloud-build-rails`というプロジェクト名にしました。

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

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

```shell
$ 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`に書かれている内容を上から順に実行していきます。
ここでは、大きく２つのステップがあります。

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

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

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

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

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

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

<img width="849" alt="Container_Registry_-_cloud-build-rails.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/7cfb9b00-97fe-6296-2a75-acf8399b6b36.png">


### Pull Request時に実行する

最後に、GithubでPull Requestを作成したときに、CIを実行するようにしてみます。
といっても難しい設定は特になく、Github appで [`Google Cloud Build`](https://github.com/apps/google-cloud-build)をインストールします。

<img width="668" alt="スクリーンショット 2018-08-05 22.51.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/2fdfbe77-9cfd-162a-b2b5-95c0d5e9c847.png">

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

<img width="773" alt="スクリーンショット 2018-08-05 22.51.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/cb405527-412f-0574-ae4c-d31bd3d3225d.png">

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

<img width="470" alt="スクリーンショット 2018-08-05 22.53.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/78536597-3078-f6c9-65ab-65f2bf051dfb.png">

<img width="537" alt="スクリーンショット 2018-08-05 22.53.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/f00d5497-962e-9b02-5924-c94ebfcd721a.png">

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

### 動作確認

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

<img width="594" alt="スクリーンショット 2018-08-06 2.37.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/2716a499-0015-e910-69cd-be103ba12e72.png">

CI実行中...

<img width="784" alt="スクリーンショット 2018-08-06 2.39.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/1d543cda-0663-e4e1-e700-3191f3f88fdb.png">

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

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

コミットのSHA（先頭７文字）は `$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がつくようになりました。

<img width="795" alt="スクリーンショット 2018-08-06 2.55.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/c5de5a18-3f42-326e-31bd-94329004a3e1.png">

<img width="849" alt="Container_Registry_-_cloud-build-rails 3.png" src="https://qiita-image-store.s3.amazonaws.com/0/92359/594ecff5-20d4-2852-9b03-f852a166129e.png">


## まとめ

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

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