RailsアプリをDockerで作ってCircleCIで自動テストしてHerokuにデプロイした話

#はじめに

どうも、Pirikaraです。
久しぶりの投稿となりました。

今回は、Docker環境でRailsアプリケーションを開発し、
CircleCIで自動テスト、Herokuにデプロイするところまでやっていきたいと思います。
個人開発でやってみましたが、このエラーの山々......
<img width="1412" alt="スクリーンショット 2020-02-20 15.00.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/67b1897d-24fe-3fbb-1d5a-6a1b2dec992a.png">

一つ一つエラーを解決して設定ファイルを修正してを繰り返し繰り返し......
やっとまともに動くようになりました。
<img width="1418" alt="スクリーンショット 2020-02-20 15.03.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/9ed0afa4-13f1-653a-de25-87c1dd2f6051.png">

初めてやるよって方は僕の屍を踏み越えていってください。
ちなみに間違ってるよーとか改善点とかご指摘いただけると幸いです。

#環境
・Mac OS
・Ruby 2.5.3
・Rails 5.2.2
・MySQL 5.7

また、Dockerがインストールされていることを前提としています(Dockerコマンドが使用できる状態)。
インストールしていない場合は、公式サイトからアカウントを作ってログインし、DockerHubからダウンロード・インストールします。
[Dockerhub](https://hub.docker.com/editions/community/docker-ce-desktop-mac)

Herokuについても登録していない場合は登録の必要があります。
公式サイトから登録が可能です。
[Heroku](https://jp.heroku.com/)


#アプリの構成
<img width="776" alt="スクリーンショット 2020-02-20 13.02.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/1d9fea05-8d2c-b25e-2ef2-af3fa9ac72b9.png">

開発環境からDockerを導入し、RailsとMySQLのimageでコンテナを作りました。苦労していた環境構築がすぐに出来て感動。
GithubにpushするとCircleCIによる自動テストが行われて、テストをパスするとHerokuに自動でデプロイされる仕様です。
また、Herokuでは有料であればMySQLが使用できるみたいですが、僕はお金がないので本番環境のみPostgreSQLを使用します。

では、
1. Docker開発環境の構築
2. CircleCIの導入と自動テスト
3. Herokuに自動デプロイ
の順番でやっていきましょう。

## 1.Docker開発環境の構築
まず、アプリケーションのディレクトリを作成します。
ここではsample_appとします。

```terminal
sample_appディレクトリを作成
$ mkdir sample_app

sample_appディレクトリに移動
$ cd sample_app
```
ディレクトリ内に「Dockerfile」「docker-compose.yml」「Gemfile」「Gemfile.lock」の4ファイルを作成します。

```terminal
$ touch Dockerfile
$ touch docker-compose.yml
$ touch Gemfile
$ touch Gemfile.lock
```
今ディレクトリはこんな感じです。
<img width="373" alt="スクリーンショット 2020-02-20 14.16.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/3d73a810-59aa-942b-f73c-bfed189d5279.png">

では、それぞれ中身を書いていきましょう。（Gemfile.lockは空のままです）

```yml:Dockerfile
FROM ruby:2.5.3

#必要なパッケージのインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           
#作業ディレクトリの作成
RUN mkdir /sample_app  #自身のアプリディレクトリ名を設定

#作業ディレクトリをAPP_ROOTに割り当てる
ENV APP_ROOT /sample_app  #自身のアプリディレクトリ名を設定 
WORKDIR $APP_ROOT

#ローカルのGemfileを追加
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

#Gemfileのbundle installを実行
RUN bundle install
ADD . $APP_ROOT
```
```yml:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "4306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    environment:
      RAILS_ENV: development
    volumes:
      - .:/sample_app #自身のアプリディレクトリ名を設定
    ports:
      - "3000:3000"
    links:
      - db
```

```:Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.2'
```
ところどころ違いますが、
設定の内容についてはこちらの記事が詳しくて参考になったので、任せます。
[DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】](https://qiita.com/kodai_0122/items/795438d738386c2c1966)

ここまで書けたら、dockerコマンドでrails newします。

```terminal
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
あとでimageを構築する際にDockerfileにしたがってbundle installが実行されるので、ここではスキップします。

作成されたconfig/database.ymlを編集します。

```yml:database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名
```

dockerを起動します。

```terminal
imageを構築（コンテナは作成しない）
$ docker-compose build

コンテナを構築・起動
$ docker-compose up
```
データベースを作成します。

```terminal
$ docker-compose run --rm web rails db:create
```
--rmオプションをつけることで、コンテナが実行されたあと削除されます。
「rails g controller ~」や「rails console」など、docker-composeから始まるコマンドで実行していくことになりますが、
--rmをつけていないと実行するたびにコンテナが増えていってしまうので、都度削除しています。僕は。

ここまでできると、localhost:3000へアクセスしてサーバーの起動を確認することができます。おめでとう。
<img width="1249" alt="スクリーンショット 2020-02-20 14.42.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/c4373527-987e-8e0e-1d58-692f24036809.png">


## 2.CircleCIの導入と自動テスト

こちらのブログをパク・・・参考にしました。
導入まで大変わかりやすく、参考になりました。こちらを参考に導入してみてください。

[ウェブ系ウシジマくんのテックブログ
](https://breakthrough-tech.yuta-u.com/circleci/notification-to-slack/)

また、今回テストはRspecで実装しますのでRspecを導入しておいてください。

「.circleci」というディレクトリを作成し、これ以下に「config.yml」というファイルを作成します。
また、configディレクトリ以下に「database.yml.ci」というファイルを作成します。

こんな感じ。
<img width="382" alt="スクリーンショット 2020-02-20 14.56.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/6a3da6c7-0dfa-e3d1-b5d7-92abbea22482.png">

では、作成したファイルの中身を書いていきましょう。

```yml:config.yml
version: 2
jobs:
  build:
    docker:
    - image: circleci/ruby:2.5.3-node-browsers
      environment:
        - BUNDLER_VERSION: 2.0.2
        - RAILS_ENV: 'test'
    - image: circleci/mysql:5.7
      environment:
        - MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
        - MYSQL_ROOT_HOST: '127.0.0.1'

    working_directory: ~/sample_app  #自身のアプリディレクトリ名を設定  


    steps:
    - checkout

    - restore_cache:
        keys:
        - v1-dependencies-{{ checksum "Gemfile.lock" }}
        - v1-dependencies-

    - run:
        name: install dependencies
        command: |
          gem install bundler -v 2.0.2
          bundle install --jobs=4 --retry=3 --path vendor/bundle

    - save_cache:
        paths:
        - ./vendor/bundle
        key: v1-dependencies-{{ checksum "Gemfile.lock" }}

    # Database setup
    - run: mv ./config/database.yml.ci ./config/database.yml

    # Database setup
    - run:
        name: Databasesetup
        command: |
           bundle exec rake db:create
           bundle exec rake db:schema:load

    # run tests!
    - run:
        name: Run rspec
        command: |
          mkdir /tmp/test-results
          TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
            circleci tests split --split-by=timings)"

          bundle exec rspec \
            --format progress \
            --format RspecJunitFormatter \
            --out /tmp/test-results/rspec.xml \
            --format progress \
            $TEST_FILES

    # collect reports
    - store_test_results:
        path: /tmp/test-results
    - store_artifacts:
        path: /tmp/test-results
        destination: test-results
```

```yml:database.yml.ci
test:
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: 'root'
  port: 3306
  host: '127.0.0.1'
  database: sample_app_test #database.ymlのテスト環境のデータベース名を参照
```

これでGithubへpushすると、自動でテストが実行されるようになります。
プルリクエストを作ってmasterブランチへmergeしていくと思うのですが、テストに失敗するとmergeすることができません。
<img width="755" alt="スクリーンショット 2020-02-20 19.50.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/edb45ca6-7873-7dd1-25ed-66af73388b7e.png">
ほらね。

Detailのリンクからエラー箇所も教えて頂けます。
<img width="1397" alt="スクリーンショット 2020-02-20 19.54.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/2a358ea6-ed95-5230-24b1-3a24d3b21eb1.png">

テストをパスすると、Mergeボタンがアクティブになります。
<img width="763" alt="スクリーンショット 2020-02-20 20.00.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/4cccb660-3f7f-aa29-4ca7-b3de81500d59.png">



### ※ seeds.rbを読み込みたい場合
.circleci/config.ymlでseeds.rbの読み込み設定をしたところ、うまくいかなかったのでspec_helperにseeds.rbを読み込む設定をしました。

「database_cleaner」というgemを導入し、spec_helperへ処理を書いていきます。
これで、Rspec実行の際にデータベースがリフレッシュされ、seeds.fileが読み込まれます。

Githubへpushした際に実行された場合は大丈夫なのですが、ローカルでdocker-composeコマンドからrspecを実行した場合にデータベースがリセットされてしまうので注意してください。

```ruby:Gemfile
group :development, :test do
  # rspec実行時にDBをリセットする
  gem 'database_cleaner'
end
```
```ruby:spec_helper.rb
# テスト実行時にDatabaseをリセットし、seeds.rbを読み込む
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
    Rails.application.load_seed
  end
end
```

## 3.Herokuに自動デプロイ
Herokuにデプロイするため、ファイルを修正・追加します。

まずはGemfile。
開発環境ではMySQL、本番環境ではPostgeSQLを使用するので、「pg」のgemをインストールします。

```ruby:Gemfile
# 変更前
gem 'mysql2', '>= 0.4.4', '< 0.6.0'

# 変更後
gem 'mysql2', '>= 0.4.4', '< 0.6.0', groups: %w(test development), require: false
gem 'pg', '~> 0.19.0', group: :production, require: false
```
次に、database.ymlを編集していきます。
本番環境でPostgreSQLを使う設定をします。

```yml:database.yml
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5
```
そして、「heroku.yml」というファイルを新たに作成します。
作成場所はアプリ直下です。（GemfileとかDockerfileと一緒）
今回はDockerコンテナを使用して開発をしているので、コンテナごとHerokuのサーバーに持っていきます。
そのための設定ファイルです。知らんけど。

DockerコンテナをHerokuにアップする方法は公式サイトを参考にしました。

[Dockerを使用したデプロイ](https://devcenter.heroku.com/categories/deploying-with-docker)

```yml:heroku.yml
build:
  docker:
    web: Dockerfile
run:
  web: bundle exec puma -C config/puma.rb
```
アプリケーション側の設定はこれで完了です。

ではHeroku側の設定をしてデプロイしていきましょう。
ターミナルから、Herokuにログインします。

```terminal
$ heroku login

# ログインするかどうか確認されます。
# クリックするとブラウザが立ち上がり、Herokuのログイン画面になります。ログインしてください。
heroku: Press any key to open up the browser to login or q to exit: 

# ログインが完了すると、ターミナルに「ログインしたよー」って表示されます。
Opening browser to https://cli-auth.heroku.com/auth/browser/**************
Logging in... done
Logged in as *******@email.com
```

次に、Herokuにアプリケーションを作成します。

```terminal
# アプリケーション名のところには好きな名前を入れてください。それでURLが作成されます。
$ heroku create アプリケーション名

Creating app... done, ⬢ *******
https://******.herokuapp.com/ | https://git.heroku.com/*******.git
```
ちなみにこの時、Herokuのリポジトリが作成されています。
もしアプリケーションの名前を変更したいときは、Herokuの管理画面から名前を変更することに加えて、リポジトリを削除する必要があります。

```terminal
$ git remote rm heroku
```
このコマンドで消せます。
消さないとアプリ名とリポジトリ名が異なるためにデプロイできなくなります。


そして、Heroku側でPostgreSQLを使用する設定をします。

```terminal
$ heroku addons:create heroku-postgresql:hobby-dev

# PostgreSQLのデータベース作ったよーって通知がきます。
Creating heroku-postgresql:hobby-dev on ⬢ ******... free
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pg:copy
Created postgresql-transparent-70433 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
```

今回DockerコンテナをHerokuにのせるので、Heroku側でコンテナにアプリのStackをセットします。

```terminal
$ heroku stack:set container
```
この辺の話は「heroku.yml」の設定の時に出てきた公式サイトに全部載ってます。
[Dockerを使用したデプロイ](https://devcenter.heroku.com/categories/deploying-with-docker)


これでデプロイするための設定は完了しました。
masterブランチにアプリをpushしたのち、

```terminal
$ git push heroku master
```
このコマンドでHerokuのリポジトリにpushすることでデプロイが完了します。エラーが起こらなければ。



さて、ついに自動デプロイです。
circleciを通してHerokuにデプロイしていくので、config.ymlファイルにdeployの処理を追加します。

```yml:config.yml
# 省略
    - deploy:
        name: Deploy Master to Heroku
        command: |
          if [ "${CIRCLE_BRANCH}" == "master" ]; then
            git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master
          fi
```
 「HEROKU_API_KEY」と「HEROKU_APP_NAME」については、CircleCIに設定した環境変数が読まれるため、
こちらを設定していきます。

どちらもHerokuの管理画面から取得できます。

アカウント設定からHEROKU_API_KEYに当たるAPI KEYを......
<img width="1268" alt="スクリーンショット 2020-02-20 18.47.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/5ca32715-b60c-47b7-5f13-8543a562996a.png">

アプリケーションの設定からHEROKU_APP_NAMEを......
<img width="876" alt="スクリーンショット 2020-02-20 19.28.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/d9439bf0-d613-95de-967b-6bda9bf42428.png">
それぞれ取得します。

CircleCIのアプリケーション管理画面から、右上の「Project Setting」をクリックします。
<img width="1424" alt="スクリーンショット 2020-02-20 19.30.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/47431248-dfa8-4a29-8f3f-f22722a3de23.png">

Environment Variablesから「Add Variables」をクリックして、先ほど取得した値を環境変数として設定します。
これがconfig.ymlで使用されます。
<img width="1190" alt="スクリーンショット 2020-02-20 19.33.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493565/80554d53-a7c7-26bd-8329-d0c518454b41.png">

設定は以上です。
これで、

Githubへpush → CircleCiが自動でテスト → パスすればHerokuに自動でデプロイ

という風に動きます。良かったね。

##終わりに
DockerとCircleCIを利用したのは初めてでしたが、
環境構築もテスト・デプロイも簡潔になったので、非常に便利でした。

特にテストコマンドやデプロイコマンドについては「コマンド打つだけやしなぁ......」と自動化の恩恵を甘く見ていましたが、
チリも積もればなんとやらです。
いちいちコマンドを打つ煩わしさから解放され、アプリの開発スピードも段違いだったように感じます。

参考にしていただけたら幸いです。

またね。