チームで共有するための『Rails 6 x MySQL 8』Docker環境構築手順

インフラ

こんにちは。仁科(@nishina555)です。

今回はRails 6とMySQL 8を組み合わせたWebアプリケーションのDocker環境を構築する手順について紹介します。

Rails 6からwebpackerが標準でインストールされるようになったり、MySQL 8からユーザー認証の方式が変わったりと環境構築でつまる部分がいろいろとあったため参考になればと思います。

複数人でもスムーズに開発ができるようにするためリモートリポジトリからcloneしてきたらdocker-compose upするだけでアプリケーションが立ち上がるという環境をゴールにします。

各種バージョンは以下の通りです。

バージョン一覧
  • Ruby on Rails: 6.0.3.2
  • Ruby: 2.7.1
  • MySQL: 8.0.21

実行環境はDocker Desktop for Mac(バージョン 2.3.0.4)を利用しています。

なお、今回紹介する手順をもとに作成したサンプルアプリケーションはnishina555/gihyosd2020-dockerに公開しております。

Railsアプリケーションの準備

ディレクトリの作成・移動をします。
今回作成するRailsアプリケーション名はsample_appとします。

$ mkdir sample_app && cd $_

rubyイメージを利用してローカル環境にGemfileを作成します。
-vはバインドマウント(ホストとコンテナのディレクトリの同期)のオプションです。

ホストのカレントディレクトリとコンテナのワークディレクトリを同期させることで、コンテナ上で作成されるファイルをホストに配置します。

$ docker run --rm -v `pwd`:/sample_app -w /sample_app ruby:2.7.1 bundle init

作成されたGemfileの『gem “rails”』をアンコメントし、railsのバージョンを指定します。

Gemfile

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

- # gem "rails"
+ gem "rails", '~> 6.0.3.2'

rails newを実行するDocker環境を構築するためDockerfileを作成します。

Rails 6ではアプリケーションを作成する際にrails webpacker:installも実行されるのでyarnのインストールを忘れずにしましょう。
今回はnpmを利用してyarnをインストールします。

Dockerfile

FROM ruby:2.7.1

RUN apt-get update -qq && \
    apt-get install -y nodejs \
                       npm && \
    npm install -g yarn

# 作業ディレクトリを/sample_appに指定
WORKDIR /sample_app

# ローカルのGemfileをDokcerにコピー
COPY Gemfile* /sample_app/

# /sample_appディレクトリ上でbundle install
RUN bundle install

Dockerfileをビルドして作成されたイメージを利用してコンテナを起動し、コンテナ上でrails newをします。

$ docker build -t sample_app .

$ docker run --rm -v `pwd`:/sample_app sample_app rails new . –skip-bundle --database=mysql

docker-compose.ymlを作成します。

今回は説明を簡略化するため、rootユーザーでMySQLに接続しています。
一般ユーザーで接続をする場合はMySQLのイメージに対して以下の環境変数を設定する必要があります。

環境変数 内容
MYSQL_USER ユーザー名
MYSQL_PASSWORD ユーザーパスワード

【Docker】Railsで理解する、複数のMySQLデータベースに一般ユーザーで接続するための環境構築手順

データベースの情報はmysql_dataという名前付きボリュームを作成して永続化します。

docker-compose.yml

version: '3'
services:
  web: # Ruby on Railsが起動するコンテナ
    build: .
    ports:
      - '3000:3000' # localhostの3000ポートでアクセスできるようにする
    volumes:
      - .:/sample_app # アプリケーションファイルの同期
    depends_on:
      - db
    command: ["rails", "server", "-b", "0.0.0.0"]
  db: # MySQLが起動するコンテナ
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql # データの永続化
    command: --default-authentication-plugin=mysql_native_password # 認証方式を8系以前のものにする。
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'
volumes:
  mysql_data: # データボリュームの登録

Dockerのデータを永続化!Data Volume(データボリューム)の理解から始める環境構築入門

上記のdocker-compose.ymlの補足説明をします。

MySQLのDockerイメージではMYSQL_DATABASEに設定された名前のデータベースを作成してくれます。
Ruby on Railsのdevelopment環境では[アプリケーション名]_developmentというデータベースを利用するため、sample_app_developmentMYSQL_DATABASEに登録しています。
これでrails db:createを実行しなくてもdevelopment環境のデータベースを用意できます。

MySQL 8からは認証方式がmysql_native_passwordからcaching_sha2_passwordに変更されました。
MySQL 8標準のcaching_sha2_passwordの認証方式だとデータベースへ接続できず、Railsアプリケーション起動時に以下のようなエラーメッセージが表示されてしまいます。

Mysql2::Error::ConnectionError

Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory

そこで、--default-authentication-plugin=mysql_native_passwordで以前のmysql_native_passwordの認証方式を利用するようにしています。

次にRuby on Railsのデータベース接続設定を行います。

config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: pass # (今回はrootなので)MYSQL_ROOT_PASSWORDと一致させる
  host: db # データベースのコンテナ名を設定する

Railsアプリケーションの起動確認

Ruby on Rails 6とMySQL 8を組み合わせたDocker環境ができあがったので起動をしてみます。

$ docker-compose up

localhost:3000にアクセスして以下の画面が表示されればOKです。

データベースのデータの永続化についても確認をしてみます。
例としてscaffoldeventに関する機能を作成します。

$ docker-compose exec web rails g scaffold event title:string
$ docker-compose exec web rails db:migrate

localhost:3000/events/newにアクセスし、例として『サンプルイベント』というレコードを登録してみます。

コンテナを削除・起動してもデータが残っていればOKです。

$ docker-compose down
$ docker-compose up

$ docker-compose exec web rails c
> Event.last.title
=> "サンプルイベント"

テスト環境用のデータベースの作成

MYSQL_DATABASEを利用してdevelopment環境のデータベースを作成することでdb:createを実行することなくRailsアプリケーションを起動できるようにしました。

しかし、テストコードで利用するtest環境のデータベース(`[アプリケーション名]_test`)が作られていないためこれだけでは開発環境としては不十分です。

MySQLのDockerイメージでは/docker-entrypoint-initdb.dにスクリプト(.sql.sh.sql.gz)を配置しておくとコンテナ起動時に実行してくれるという機能があります。1
この機能を活用してtest環境用のデータベースを作成します。

スクリプトはアルファベット順で実行されるため、スクリプト間に依存関係がある場合はファイル名も意識してつけるようにしておきましょう。

$ mkdir docker-entrypoint-initdb.d && cd $_
$ vim 00_create.sql

00_create.sql

-- test環境用のデータベースを作成する
CREATE DATABASE sample_app_test;

なお、一般ユーザーのデータベースアクセス権はMYSQL_DATABASEに設定したデータベースのみとなっています。
ですので、一般ユーザーでデータベースに接続する場合はデータベースの作成だけでなく、以下のようなアクセス権の付与も実行する必要があります。

01_grant.sql

-- webappという名前の一般ユーザーを利用する場合
GRANT ALL ON `sample_app_test`.* TO webapp@'%';

【Docker】Railsで理解する、複数のMySQLデータベースに一般ユーザーで接続するための環境構築手順

作成したスクリプトをコンテナ上で読み取れるようにするためバインドマウントを追加します。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
    command: ["rails", "server", "-b", "0.0.0.0"]
  db:
    image: mysql:8.0.21
    volumes:
      - mysql_data:/var/lib/mysql
+     - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'
volumes:
  mysql_data:

データベースが自動作成されることを確認

dbコンテナを削除・起動し、自動でデータベースが作られるか確認をしてみます。
[アプリケーション名]_development[アプリケーション名]_testのデータベースが存在していればOKです。

# データベースの永続化情報も削除して0からデータベースを作成する
$ docker-compose down --volumes

$ docker-compose up

$ docker-compose exec db mysql -uroot -ppass -D sample_app_development

mysql> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sample_app_development |
| sample_app_test        | ← test用のdbが作成されている
| sys                    |
+------------------------+
6 rows in set (0.00 sec)

Railsアプリケーションからも正常に接続できます。

$ docker-compose exec web rails c

> ENV['RAILS_ENV']
=> "development

$ docker-compose exec web rails c -e test

> ENV['RAILS_ENV']
=> "test"

『clone → docker-compose up』だけで環境が立ち上がるようにする

DockerでRailsの開発環境を構築する方法としてよく見かけるのが以下のようなパターンです。

# コンテナを立ち上げる
$ docker-compose up

# データベースの作成
$ docker-compose exec web rails db:create

# テーブルのマイグレーション
$ docker-compose exec web rails db:migrate

Railsアプリケーションとデータベースをdocker-composeで連携させ、アプリケーションで利用するデータベースはコンテナ上で構築するという方法です。

上記の方法でも問題はないのですが、この場合だと初回起動時にデータベースとテーブルの作成をコンテナ上で手動実行する手間がかかります。

複数人でDocker環境を共有する時のことを考えると、リモートリポジトリからアプリケーションファイルをcloneしてきたらdocker-compose upをするだけで環境が立ち上がるのが理想です。

共有メンバーに対して「初回起動時はデータベースの作成をコンテナ上で実施してね」とわざわざ伝えないといけない状況はできれば避けたいです。

ここからはリモートリポジトリからcloneしたらdocker-compose upするだけでRailsアプリケーションが起動できるようにするための設定を行います。

yarn installをコンテナ起動時に実行する

開発環境ではバインドマウト(ホストとコンテナのディレクトリの同期)を利用したソースコードの同期がよく利用されます。

リモートリポジトリからcloneしてきたアプリケーションファイルはpackage.jsonはありますが、node_modulesディレクトリはありません。
そのため、cloneしたあとdocker-compose upをするとnode_modulesがない状態のディレクトリがバインドマウントされます。その結果yarn installの実行を促すエラーが発生し、アプリケーションが起動できません。

========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================

To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).

yarn check v1.22.5
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.

これを防ぐにはコンテナ起動時にyarn installを実行する必要があります。
たとえば以下のようにすることでコンテナ起動時にyarn installを実行できます。

docker-compose.yml

version: '3'
services:
  web:
    build: .
+   command: ["./start.sh"]
-   command: ["rails", "server", "-b", "0.0.0.0"]
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
  db:
    image: mysql:8.0.21
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'

start.sh

#!/bin/bash -eu
yarn
rails server -b '0.0.0.0'

シェルのパーミッションを変更します。

$ chmod 755 start.sh

コンテナを起動する際のコマンドでシェルを呼び出し、シェルの中でyarn installを実行するようにしています。
yarn install実行後、rails serverでRailsアプリケーションが起動するため先ほどのエラーが解消されます。

マイグレーションを自動実行できるようにする

テーブルのマイグレーションもyarn installと同様、コンテナ起動時に呼び出されるシェルスクリプトで実行するようにします。

start.sh

#!/bin/bash -eu
yarn
+ rails db:migrate
rails server -b '0.0.0.0'

しかし、docker-compose.ymldepends_onを利用することでコンテナの起動順は制御できますが、コンテナの起動を待つということはできないため2、dbコンテナの起動準備が終わる前にdb:migrateが実行されるとマイグレーションが正常に行われません。

マイグレーションの自動化をするにはdbコンテナの起動を待ってからdb:migrateが実行されるようにする必要があります。

データベースの準備を待つ方法にはいくつかありますが、今回はwait-for-itを利用する方法を紹介します。

wait-for-it.shをカレントディレクトリに配置し、docker-compose.ymlを以下のようにするとデータベースの起動を待ってからスクリプトが実行されます。

docker-compose.yml

version: '3'
services:
  web:
    build: .
-   command: [""./start.sh"]
+   command: ["./wait-for-it.sh", "db:3306", "--", "./start.sh"]
    ports:
      - '3000:3000'
    volumes:
      - .:/sample_app
    depends_on:
      - db
  db:
    image: mysql:8.0.21
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
      MYSQL_DATABASE: 'sample_app_development'

まとめ

以上でRails 6 + MySQL 8のDocker環境の構築手順の紹介を終わります。

コンテナ起動時にシェルを実行することで、複数人でDocker環境を利用する場合でもスムーズに開発環境を構築できるようにしました。
なお、今回はコンテナ起動時にシェルを呼び出すことでyarn installdb:migrationを確実に実行するようにしましたが、コンテナ起動時のコマンドの制御はEntrykitと呼ばれるツールでも行えます。

EntrykitについてはRailsのDocker環境にEntrykitを導入し、bundle installを自動実行させる方法で紹介していますので、興味のある方はご覧になってください。

RailsのDocker環境にEntrykitを導入し、bundle installを自動実行させる方法

今回のまとめ
  • Rails 6からはyarnのインストールも事前に行う必要がある
  • MySQL 8の認証エラーが発生した場合は標準の認証に戻すことで解決する
  • コンテナ起動時にシェルを実行することでテーブル作成等の自動化が可能になる

今回紹介した手順をもとに作成したサンプルアプリケーションはnishina555/gihyosd2020-dockerに公開しております。少しでも参考になれば幸いです。

この記事がいいなと思いましたらTwitter(@nishina555)のフォローもよろしくお願いします!