Help us understand the problem. What is going on with this article?

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

はじめに

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

今回は、Docker環境でRailsアプリケーションを開発し、
CircleCIで自動テスト、Herokuにデプロイするところまでやっていきたいと思います。
個人開発でやってみましたが、このエラーの山々......
スクリーンショット 2020-02-20 15.00.43.png

一つ一つエラーを解決して設定ファイルを修正してを繰り返し繰り返し......
やっとまともに動くようになりました。
スクリーンショット 2020-02-20 15.03.12.png

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

環境

・Mac OS
・Ruby 2.5.3
・Rails 5.2.2
・MySQL 5.7

また、Dockerがインストールされていることを前提としています(Dockerコマンドが使用できる状態)。
インストールしていない場合は、公式サイトからアカウントを作ってログインし、DockerHubからダウンロード・インストールします。
Dockerhub

Herokuについても登録していない場合は登録の必要があります。
公式サイトから登録が可能です。
Heroku

アプリの構成

スクリーンショット 2020-02-20 13.02.15.png

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

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

1.Docker開発環境の構築

まず、アプリケーションのディレクトリを作成します。
ここではsample_appとします。

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

sample_appディレクトリに移動
$ cd sample_app

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

$ touch Dockerfile
$ touch docker-compose.yml
$ touch Gemfile
$ touch Gemfile.lock

今ディレクトリはこんな感じです。
スクリーンショット 2020-02-20 14.16.06.png

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

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
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対応】

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

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

あとでimageを構築する際にDockerfileにしたがってbundle installが実行されるので、ここではスキップします。

作成されたconfig/database.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を起動します。

imageを構築(コンテナは作成しない)
$ docker-compose build

コンテナを構築・起動
$ docker-compose up

データベースを作成します。

$ docker-compose run --rm web rails db:create

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

ここまでできると、localhost:3000へアクセスしてサーバーの起動を確認することができます。おめでとう。
スクリーンショット 2020-02-20 14.42.11.png

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

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

ウェブ系ウシジマくんのテックブログ

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

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

こんな感じ。
スクリーンショット 2020-02-20 14.56.46.png

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

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
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することができません。
スクリーンショット 2020-02-20 19.50.23.png
ほらね。

Detailのリンクからエラー箇所も教えて頂けます。
スクリーンショット 2020-02-20 19.54.06.png

テストをパスすると、Mergeボタンがアクティブになります。
スクリーンショット 2020-02-20 20.00.18.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を実行した場合にデータベースがリセットされてしまうので注意してください。

Gemfile
group :development, :test do
  # rspec実行時にDBをリセットする
  gem 'database_cleaner'
end
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をインストールします。

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を使う設定をします。

database.yml
production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

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

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

Dockerを使用したデプロイ

heroku.yml
build:
  docker:
    web: Dockerfile
run:
  web: bundle exec puma -C config/puma.rb

アプリケーション側の設定はこれで完了です。

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

$ 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にアプリケーションを作成します。

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

Creating app... done, ⬢ *******
https://******.herokuapp.com/ | https://git.heroku.com/*******.git

ちなみにこの時、Herokuのリポジトリが作成されています。
もしアプリケーションの名前を変更したいときは、Herokuの管理画面から名前を変更することに加えて、リポジトリを削除する必要があります。

$ git remote rm heroku

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

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

$ 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をセットします。

$ heroku stack:set container

この辺の話は「heroku.yml」の設定の時に出てきた公式サイトに全部載ってます。
Dockerを使用したデプロイ

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

$ git push heroku master

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

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

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を......
スクリーンショット 2020-02-20 18.47.58.png

アプリケーションの設定からHEROKU_APP_NAMEを......
スクリーンショット 2020-02-20 19.28.44.png
それぞれ取得します。

CircleCIのアプリケーション管理画面から、右上の「Project Setting」をクリックします。
スクリーンショット 2020-02-20 19.30.15.png

Environment Variablesから「Add Variables」をクリックして、先ほど取得した値を環境変数として設定します。
これがconfig.ymlで使用されます。
スクリーンショット 2020-02-20 19.33.39.png

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

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

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

終わりに

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

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

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

またね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした