Edited at

DockerでRails環境構築 + CircleCIで自動テストとherokuへの自動デプロイを実行


前置き

Qiita初投稿です。Webエンジニアを目指して独学でRuby on Railsを学習しており、現在は企業提出用のポートフォリオの作成に取り組んでいます。今回は環境構築のメモとして記事を投稿させていただきました。本記事では、DockerがMacにインストールされていることと、Dockerの基本コマンドについて勉強していることを前提としています。拙い点も多いかと思われますが、有識者の方々はどうぞご指摘よろしくお願いいたします。

以下の記事を参考に作成しました。

docker-composeでRailsを開発しCircleCI 2.0でテストしてHerokuにdeployする

Quickstart: Compose and Rails

Heroku Container Registry CLI plugin

Heroku Container Registryのcontainer:push後のcontainer:releaseの対応方法

Dockerがインストールされているかの確認


terminal

$ docker -v 

Docker version 18.09.2


環境

Ruby: 2.6.2

Rails: 5.2.2.1

PostgreSQL: 11.2

CircleCI: 2.0


Docker環境におけるRailsアプリのセットアップ

まずは作業ディレクトリを作ってください。自分は ./docker/myappとしました。

最低限必要なファイルを用意します。


terminal

$ cd docker/myapp

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

Gemfileに以下を記述します。


Gemfile

source 'https://rubygems.org'

gem 'rails', '5.2.2.1'

Dockerfileに以下を記述します。


Dockerfile

FROM ruby:2.6.2

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& apt-get install -y nodejs
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client

RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

CMD ["rails", "server", "-b", "0.0.0.0"]


Rubyのimageを引っ張ってきて依存ライブラリなどをインストールしています。

その後、bundle installを行い、コンテナ起動時にサーバーが立ち上がるようになっています。 (curlコマンドの部分はBootstrap導入時のnode.jsのバージョンエラーを対策するためのものです。)




docker-compose.ymlには以下を記述します。


docker-compose.yml

version: '3'

services:
db:
image: postgres:11.2
ports:
- '5432:5432'
volumes:
- postgresql-data:/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
volumes:
postgresql-data:
driver: local


5432番ポートでアクセス可能なPostgreSQLコンテナを作っており、プロセスが終了したとしても、データが消えないようにしています。

もう一方では、3000番ポートでアクセス可能なRailsコンテナを作っており、ビルドした後に先ほど作ったmyappディレクトリをコンテナ内のmyappにマウントしています。




コンテナ内でrails newを行い、アプリのスケルトンを作成します。DBはPostgreSQLを使うようにしています。


./docker/myapp

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


/myappにRailsアプリの各種フォルダ群が生成されると思います。




続いて、開発環境、テスト環境、本番環境でのDBの接続設定をします。


./config/database.yml

development:

<<: *default
database: myapp_development
host: db
username: postgres
password:

test:
<<: *default
database: myapp_test
host: db
username: postgres
password:


DBを作成 & マイグレーションします。


terminal

$ docker-compose run web rails db:create

$ docker-compose run web rails db:migrate

docker-compose up --buildでイメージを再ビルドします。

ここまででRails環境の構築は一通り完了しました。ブラウザでlocalhost:3000にアクセスすると、"Yay! You're on Rails!"が表示されていると思います。



Dockerコンテナを本番環境(heroku)にデプロイ

まずはローカルで作成したコンテナをherokuにリリースするためのプラグインを追加します。


terminal

$ heroku plugins:install @heroku-cli/plugin-container-registry


続いて、herokuにログインし、コンテナをリリースします。

本番環境でDBのセットアップを行なった後はheroku openでアプリを起動します。


./docker/myapp

# dockerコンテナレジストリにログイン

$ heroku container:login
# appを新規作成
$ heroku create
# 作成したdockerイメージをコンテナレジストリにpush
$ heroku container:push web
# herokuのpostgresqlアドオンの無料プランを追加
$ heroku addons:create heroku-postgresql:hobby-dev
# DBのセットアップ
$ heroku run rails db:migrate
# dockerイメージをherokuにデプロイ
$ heroku container:release web
# ブラウザで確認
$ heroku open

ブラウザ上で"Yay! You're on Rails!"が表示されていればデプロイ成功です。

ここまでの内容で既にdockerでRailsアプリを開発し、本番環境にデプロイするまでできると思われます。

テストの自動化、デプロイの自動化をやるつもりのない方はここで終了です。お疲れ様でした。



CircleCIを用いてCI / CDパイプラインを構築

CI / CDって何ぞ?という方のために簡単に説明すると、GitHubのリモートリポジトリ 上にローカルリポジトリでの作業内容がpush、またはmasterブランチにmergeされた際に、自動でテストを実行(CI)して、テストが通ったら本番環境へアプリが自動的にデプロイ(CD)されるというものです。




まずはGithubで先ほど作成したmyappのリモートリポジトリを作成し、リモートのmasterブランチにpushしてください。

続いて、CircleCIにアクセスし、Githubアカウントでauthorizeしてください。

先ほど作成したリモートリポジトリが表示されるので、選択し、build projectをしてください。

これでGithubとCircleCIの連携ができました。

続いて、CircleCIにアクセスして、herokuにデプロイするための環境変数を設定します。

画面左側のサイドバーから、プロジェクト名のsettingsを開き、Environment Variablesから、Add Variableを選択します。

Name, Valueに以下の4つをそれぞれ追加してください。

Name: HEROKU_AUTH_TOKEN Value: herokuの認証用トークン

Name: HEROKU_LOGIN Value: herokuアカウントのメールアドレス

Name: HEROKU_API_KEY Value: herokuの認証用トークン

Name: HEROKU_AUTH_TOKEN Value: herokuでのアプリ名

herokuの認証用トークンは以下で参照できます。


terminal

$ heroku auth:token

Use heroku authorizations:create to generate a long-term token
# ここに表示




続いて、CircleCIの設定用ファイルと必要なシェルスクリプトを作成します。


terminal

$ mkdir .circleci

$ cd .circleci
$ touch config.yml setup_heroku.sh heroku-container-release.sh

続いて、作成したconfig.ymlに以下を記述します。


.circleci/config.yml

version: 2

jobs:
build:
machine:
image: circleci/classic:edge
steps:
- checkout
- run:
name: docker-compose build
command: docker-compose build
- run:
name: docker-compose up
command: docker-compose up -d
- run:
name: sleep for waiting launch db
command: sleep 1
- run:
name: "before_test: setup db"
command: docker-compose run web rails db:create db:migrate
- run:
name: test
command: docker-compose run web rails test #RSpecを使う場合は、bundle exec rspec
- run:
name: docker-compose down
command: docker-compose down
deploy:
machine:
image: circleci/classic:edge
steps:
- checkout
# see: https://devcenter.heroku.com/articles/container-registry-and-runtime#using-a-ci-cd-platform
- run:
name: "build docker image"
command: docker build --rm=false -t registry.heroku.com/${HEROKU_APP_NAME}/web .
- run:
name: setup heroku command
command: bash .circleci/setup_heroku.sh
- run:
name: heroku maintenance on
command: heroku maintenance:on --app ${HEROKU_APP_NAME}
- run:
# HEROKU_AUTH_TOKEN is generated by `heroku auth:token`
name: "push container to registry.heroku.com"
command: |
docker login --username=_ --password=$HEROKU_AUTH_TOKEN registry.heroku.com
docker push registry.heroku.com/${HEROKU_APP_NAME}/web
bash .circleci/heroku-container-release.sh
- run:
name: heroku db migrate
command: heroku run rails db:migrate --app ${HEROKU_APP_NAME}
- run:
name: heroku maintenance off
command: heroku maintenance:off --app ${HEROKU_APP_NAME}
workflows:
version: 2
build_and_deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: master

setup_heroku.shには以下を記述します。


setup_heroku.sh

wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz

sudo mkdir -p /usr/local/lib /usr/local/bin
sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku

cat > ~/.netrc << EOF
machine api.heroku.com
login
$HEROKU_LOGIN
password
$HEROKU_API_KEY
EOF
# machine git.heroku.com
# login $HEROKU_LOGIN
# password $HEROKU_API_KEY

# Add heroku.com to the list of known hosts
ssh-keyscan -H heroku.com >> ~/.ssh/known_hosts


heroku-container-release.shには以下を記述します。


heroku-container-release.sh

#!/bin/bash

imageId=$(docker inspect registry.heroku.com/${HEROKU_APP_NAME}/web:latest --format={{.Id}})
payload='{"updates":[{"type":"web","docker_image":"'"$imageId"'"}]}'
curl -n -X PATCH https://api.heroku.com/apps/${HEROKU_APP_NAME}/formation \
-d "$payload" \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.heroku+json; version=3.docker-releases" \
-H "Authorization: Bearer ${HEROKU_API_KEY}"


buildとdeployというCircleCI上で行われる2つのジョブを定義しています。

buildジョブでは、CircleCI上の仮想マシンで、作成したdockerイメージをビルドし、テストを実行する。といったことをやっています。

一方、deployジョブでは、同様にdockerイメージをビルドし、先ほど手動でやったようなデプロイの流れを実行する。といったことをやっています。

CircleCI上ではherokuコマンドをそのまま使うことができないので、heroku-container-release.shにそのためのスクリプトを記述しています。

また、heroku container:releaseを実行するために、heroku-container-release.shにスクリプトを記述しています。

また、workflowではbuild deployという2つのジョブをうまく制御するための設定を記述しています。

ここでは、

・buildジョブとdeployジョブが実行される順番(build → deploy)

・deployジョブを実行条件(buildジョブの完了)

・masterブランチのみデプロイを実行

という制約をかけています。

ではここまでの内容をGitHubにpushしてみましょう。

CircleCIにアクセスしてみると、先ほど定義したジョブが実行されていることが確認できるかと思います。 Successという表示がされていれば、ジョブの成功が確認できます。

2つのジョブが無事に成功しているのを確認できたら、ブラウザからアプリにアクセスして、正しくデプロイされているか確認してみてください。

お疲れ様でした。


まとめ

今回はDocker, CircleCI初心者でも比較的簡単にDocker上でRailsの開発環境を構築し、CircleCiでCI / CDをするまでの簡単な流れを説明させていただきました。

Railsをやっている方で、Dockerで開発してみたいという方、CI/CDを導入してみたいという方の助けに少しでもなれたら光栄です。 

ありがとうございました。

Twitterで普段のプログラミング学習の内容を発信していますので、よかったらフォローしてみてください。

アカウント: @kei_f_1996