既存のRails6アプリをDocker化しつつCircleCIでシステムスペックも実行できる環境を作る

以前にも似たような[記事](https://qiita.com/kenzoukenzou104809/items/b9e716204e0cd0cea447)を書いたのですが、Rails6でCircleCIもかませたいとなると、色々と修正が必要だったので新しい記事として書きます。また今回の記事のサンプルプロジェクトは[Github](https://github.com/kenzoukenzou/docker_rails6)で公開しています。

## 環境
- Ruby 2.6.5
- Rails 6.0.2.1

## やりたいこと
- Dockerを使って開発環境構築〜CircleCIでテスト実行
- システムスペックもちゃんと動くようにしたい

## Dockerfile

既存アプリケーションのルートディレクトリに`Dockerfile`を作成します。基本的には[公式ドキュメント](https://docs.docker.com/compose/rails/)なのですが、以下の２点追記が必要です。

- yarn, chromium-driverインストールを追記
- gem install bundlerを追記

```Dockerfile
FROM ruby:2.6.3
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn chromium-driver # yarnとchromium-driverを追記
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN gem install bundler # 追記
RUN bundle install
COPY . /myapp
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0"]
```

## docker-compose.yml

こちらも[公式](https://docs.docker.com/compose/rails/)とほぼ変わりありません。変更箇所としてはデバッグができるように、`stdin_oepn`と`tty`の設定を追記し、標準入出力をアタッチしていることと、システムスペックを実行できるようSeleniumのイメージも取得していることです。

```yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
      - chrome
    stdin_open: true
    tty: true
  chrome:
    image: selenium/standalone-chrome:3.141.59-dubnium
    ports:
      - 4444:4444
```

加えて`server.pid`が存在することでサーバーを立ち上げられない」という事象を避けるため、`entrypoint.sh`を追加します。これは公式ドキュメントと同じものです。

```shell

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

## docker-compose build

```shell
$ docker-compose build
```

既存のアプリケーションだとファイルが色々増えてしまっているので、コンテナが重くなりビルドに異常な時間がかかる場合があります。`.dockerignore`を使用して、必要のないファイルは含めないようにすると、ビルドできると思います。

- 参考：[Sample dockerignore for a Rails app](https://gist.github.com/neckhair/ace5d1679dd896b71403fda4bc217b9e)

## database.ymlを修正 & マイグレーション実行

Railsアプリケーションではデフォルトで`localhost`でDBが起動していることを期待していますが、Docker環境で利用するために`DB`コンテナの情報を記述する必要があります。

```yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test
```

```sh
$ docker-compose up
$ docker-compose run web rake db:create 
```

## システムスペック環境整備

```ruby:Gemfile
group :test do
  gem 'capybara', '>=2.15'
  gem 'selenium-webdriver'
  gem 'webdriver'
  gem 'rspec-rails', '~>3.8' #追加
end
```

```ruby:rails_helper.rb
require 'capybara/rspec'
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

RSpec.configure do |config|
  # 省略
  # Setup to run system spec
  config.before(:each, type: :system) do
    driven_by :selenium_chrome
  end

  config.before(:each, type: :system, js: true) do
    driven_by :selenium_chrome
  end
end
```

```ruby:spec/support/capybara.rb
Capybara.default_driver    = :selenium_chrome
Capybara.javascript_driver = :selenium_chrome

Capybara.register_driver :selenium_chrome do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-gpu')
  options.add_argument('--window-size=1400,1400')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end
```

## CircleCI設定ファイル
続いてCircleCIの設定までやってしまいます。
上記の設定で`database.yml`も書き換えているので、CIの設定ファイルも修正が必要になります。


- `DATABASE_URL`を設定しないと`could not translate host name "db" to addres`で弾かれる
- `bundle exec bin/webpack`の実行しないと、`Webpacker can't find application...`のエラーで弾かれる

```yml:.circleci/config.yml
version: 2
jobs:
  build:
    working_directory: ~/my-app
    docker:
      - image: circleci/ruby:2.6.3-node-browsers
        environment:
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          DATABASE_URL: postgres://postgres:password@localhost:5432/myapp_test
          RAILS_ENV: test
      - image: circleci/postgres:11-alpine
    steps:
      - checkout
      - restore_cache:
          keys:
            - my-app-bundle-v1-{{ checksum "Gemfile.lock" }}
            - my-app-bundle-v1-
      - run:
          name: Bundlerをinstall
          command: |
            gem update --system
            gem install bundler
      - run:
          name: Bundle Install
          command: bundle check || bundle install
      - save_cache:
          key: my-app-bundle-v1-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - restore_cache:
          keys:
            - rails-demo-yarn-{{ checksum "yarn.lock" }}
            - rails-demo-yarn-
      - run:
          name: Yarnをinstall
          command: yarn install --cache-folder ~/.cache/yarn
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://127.0.0.1:5432 -timeout 1m
      - run:
          name: Database setup
          command: bin/rails db:create db:schema:load --trace
      - run: bundle exec bin/webpack
      - run:
          name: execute rspec
          command: bundle exec rspec
      - store_test_results:
          path: /tmp/test-results
```

CircleCIはローカルでも実行できるので、便利ですよね。

```shell
# タイポチェック
$ circleci config validate .circleci/config.yml

# ローカルでジョブを実行
$ circleci local execute --job build
```

## 参考
- [Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)
- [CircleCI config for Rails 6 + Ruby 2.6 + Postgres + Redis](https://gist.github.com/nashirox/e26a13889da1ac69d525909981d2c390)
- [CircleCIのローカルCLI使用](https://circleci.com/docs/ja/2.0/local-cli/)
- [Rails on Dockerにて、Headless ChromeでSystem Testをやってみた。](https://qiita.com/at-946/items/e96eaf3f91a39d180eb3)