DockerでRails+MySQLの環境を構築

Railsチュートリアルを進めるための環境を構築しました.

概要

基本的にはDockerの公式ドキュメントに則って行いました. Rails用のコンテナとDBサーバー用のコンテナをそれぞれDockerfileから用意し, それらを連携するためにdocker-composeを用いています. データベースとしてはMySQL, WebサーバーとしてはApache等ではなくRails serverを使います.

なぜそう記述するのか, なぜそのコマンドなのか, 等の理由もできるだけ述べていきます. ではやっていきましょう.

作業環境

マシンOSとDockerのバージョンです.

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.5
BuildVersion:   17F77

$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   9ee9f40
 Built:        Thu Apr 26 07:13:02 2018
 OS/Arch:      darwin/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   9ee9f40
  Built:        Thu Apr 26 07:22:38 2018
  OS/Arch:      linux/amd64
  Experimental: true

作業ディレクトリは以下のような構成です.

myapp/
├── docker-compose.yml
├── db/
│   ├── Dockerfile
│   ├── conf/
│   └── data/
└── web/
    ├── Dockerfile
    ├── Gemfile
    └── Gemfile.lock

myapp/web/にはrailsのファイルが入り,
myapp/web/conf/にはMySQLの設定ファイル, myapp/db/data/にはMySQLのデータが入ります.

ひとつの疑問

ドットインストール等を見てみると, ローカル開発環境の構築にはCentOSが多く使われているように感じます. しかしDockerHubにある公式イメージを見てみると, Debianがベースとなっているものが多い印象です. なぜDockerではDebianが多く使われているのでしょうか. 理由は公式ドキュメント(日本語版)にありました.

私たちは Debian イメージ を推奨します。これは、非常にしっかりと管理されており、ディストリビューションの中でも小さくなるよう(現在は 150 MB 以下に)維持されているからです。

日本語訳されたドキュメントは少し情報が古いので, 英語の公式ドキュメントも見てみましょう.

We recommend the Alpine image as it is tightly controlled and small in size (currently under 5 MB), while still being a full Linux distribution.

こっちではAlpineを推奨していますね. しかしこのブログ記事を書くまで英語のほうは見ていませんでしたので, 本記事ではDebianをベースとして構築しています.

各種ファイル

5つのファイルを自分で用意します.

MySQLコンテナ用のDockerfile

# myapp/db/Dockerfile

# ベースとなるイメージを指定
FROM mysql:5.7

# MySQLのルートユーザー用のパスワードを設定
ENV MYSQL_ROOT_PASSWORD=password

データベースのほうはこれだけです. 同じ内容をComposeファイルに書くこともできるので, 必ずしもこのDockerfileは必要ではありません. しかし今回は練習だと思って作りました. もしも好み通りにカスタマイズしたい場合はDockerfileに書くことになるでしょう.

ベースイメージとしてはmysql:5.7を指定しました. mysql:8ではうまくいきませんでした.

上でも触れたようにこのイメージはDebianがベースになっています. したがって当然ながらFROM debianのようにOSのイメージを指定することもできます. しかしそうすると, DockerfileにMySQLをインストールする命令等も書かなくてはならないので非常に面倒です. 実際mysql:5.7のもとになっているDockerfileはかなりの行数が書かれていますしね. 素直に出来上がったものを採用するのが賢明でしょう.

ENV命令ではコンテナ内の環境変数を設定することができます. セキュリティ上はこんなところにパスワードを書くのはよろしくないんでしょうけど, Railsの練習用なのでよしとしましょう.

Webサーバーコンテナ用のDockerfile

# myapp/web/Dockerfile

# Debianがベースのrubyイメージを指定
FROM ruby:2.5.1

# 必要なものをインストール
RUN apt-get update -qq && apt-get -y install \
    build-essential \
    libpq-dev \
    nodejs \
    mysql-client

# rails用のディレクトリを作成
RUN mkdir /myapp

# ローカルマシン(Mac)からコンテナの中にファイルをコピー
COPY Gemfile /myapp
COPY Gemfile.lock /myapp

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

# 上でコピーしたGemfileに従ってGemをインストール
RUN gem install bundler && bundle install

こちらは公式ドキュメントを真似して書いています. しかし以下の5点異なるところがあります.

  • ひとつめのRUN命令では, WebサーバーのコンテナからMySQLにアクセスするために, クライアントコマンドをインストールしています.
  • COPY命令の第二引数ではディレクトリだけを指定しています. ファイル名を変えるわけではないのでこれでよいでしょう.
  • WORKDIR命令の位置が異なります. とくにCOPY命令で使っているわけではないのでこれでOKです.
  • bundlerをインストールしています. はじめからbundlerが導入されているのか不明だったのでこうしておきました.
  • 最後のCOPY命令がありません. 結局Composeファイル内でボリュームのマウントを指定するので必要ないと思われます.

2つのコンテナをまとめて扱うComposeファイル

# myapp/docker-compose.yml

version: '3'
services:
  web:
    build: ./web
    ports:
      - "80:3000"
    depends_on:
      - db
    volumes:
      - ./web:/myapp
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
  db:
    build: ./db
    volumes:
      - ./db/data:/var/lib/mysql
      - ./db/conf:/etc/mysql/conf.d
    expose:
      - "3306"

初めてYAMLを書きました. タブ文字を使ってはいけないというところは注意が必要ですね. コメントで説明すると見づらくなりそうなので, 以下で詳細を述べていきます.

  • version:ではComposeファイルのフォーマットのバージョンを指定しています. 2018年6月現在では3.6が最新ですが, 小数まで指定する必要はなさそうです.
  • services:によってwebとdbの2つのサービスを定義しています.
  • build:によって, 各サービスに対応したDockerfileの場所を指定しています.
  • ports:はポートフォワーディングの指定です. 時刻と認識されないようにダブルクォートが必要です. localhost:3000と指定するのは面倒なのでwell-knownポートの80を指定しています.
  • depends_on:により2つのサービスの依存関係を定義しています. これにより, dbが立ち上がった後にwebが立ち上がるようにしてくれます.
  • volumes:により, ローカルマシンのボリュームをマウントすることができます. これがあるので, Dockerfile内にCOPY . /myapp命令は必要ないでしょう. ボリュームのマウントについては詳しくこちらにまとめておきました.
  • command:はコンテナ起動時にコンテナ内で実行するコマンドです. DockerfileにWORKDIR /myappと書いたおかげで, /myappでこのコマンドを実行してくれます. rails sというコマンドはRails serverを起動するものです. デフォルトの3000ポートを指定していますね.
  • expose:により, 他のサービス(この場合はweb)へポートを解放します.

GemfileとGemfile.lock

Gemの一種であるRailsをインストールするので, Gemfileを作成し, コンテナ内にコピーします. Gemfile.lockは内容が空っぽのファイルを作成するだけでOKです.

source 'https://rubygems.org'
gem 'rails', '5.2.0'

Railsアプリの骨組みを構築

Composeファイルがあるディレクトリであることを確認して, 以下のコマンドを実行します.

$ pwd
/myapp
$ docker-compose run web rails new . --force --database=mysql

このコマンドはdocker run <image>と同じような働きをします. しかしdocker-composeの場合はサービス名 (この例ではweb)で指定できるのが便利ですね.

rails newがコンテナ内で実行されます. アプリ名を引数に指定してこのコマンドを実行することにより, 指定した名前でアプリの骨組みが作成されます. この場合はカレントディレクトリを表す.が引数となっているので, カレントディレクトリ名がアプリ名に指定されたことになります. DockerfileにおいてWORKDIR /myappと記述しているので, rails newが実行されるディレクトリは/myappです. したがってmyappという名前のアプリが作られることになるわけですね. またRailsによって自動生成された各種ファイルはこのディレクトリに配置されることになります.

--forceはファイルの上書きを許可するオプションです. rails newは新たにGemfileを作成しますが, このオプションにより先ほど用意したGemfileが上書きされます.

--database=mysqlは見たまんまですね. データベースの種別を指定しています.

Composeファイルにてcommand:を指定していましたよね. これはコンテナが起動するときに発動すると言いました. しかし今回のdocker-compose run実行時には発動しません. それはrails newで上書きされたからです. command:で指定したコマンドは上書きが可能なのです. はじめだけrails newを上書きで実行しておいて, 一旦環境が構築されてしまえば, もとのコマンドによりRails serverが起動してくれるということです. うまいことできてますね.

イメージを再構築

rails newしたことにより, Gemfileが上書きされました. この状態を保持するためにイメージを再構築します. そのコマンドがdocker-compose buildです. 公式ドキュメントには以下のように書かれています.

Now that you’ve got a new Gemfile, you need to build the image again. (This, and changes to the Gemfile or the Dockerfile, should be the only times you’ll need to rebuild.)

つまりGemfileやDockerfileに変更があった場合はこのコマンドでrebuild(再構築)しろってことですね.

データベース設定をRailsに教える

データベースの設定ファイルを編集します. ボリュームをマウントしているので, コンテナの中に入らずとも編集ができますね. ファイルパスはmyapp/web/config/database.ymlです.

# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver
#   gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
#   gem 'mysql2'
#
# And be sure to use new-style password hashing:
#   https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
#
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password    # 変更箇所!!
  host: db    # 変更箇所!!

development:
  <<: *default
  database: myapp_development    # 変更可能!!

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: myapp_test    # 変更可能!!

# As with config/secrets.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>

はじめに設定したrootユーザーのパスワードを書きましょう. またホストにdbを指定します. Dockerのおかげでサービス名で指定することができます.

開発環境とテスト環境のデータベース名があらじめ指定されていますが, 自分の使いやすい名前に変更することもできます.

サービスの立ち上げ

データベース設定ができたので, いよいよサービスの立ち上げです.

$ docker-compose up -d

-dオプションによりバックグラウンドでサービスを起動することができます.

ではさっそくブラウザで localhostにアクセスしてみましょう. するときっとエラーが出ると思います. これはデータベースが作成されていないことが原因です. ではデータベースを作成しましょう. 以下のコマンドを実行します.

$ docker-compose exec web rails db:create

公式ドキュメントではrake db:createを実行していますが, これは古いタイプのコマンドです. Rails5を使う場合はrails db:createを使いましょう.

さてこれでデータベースも作成されました. 再びlocalhostへアクセスしてみましょう. どうでしょうか. Yay! You're on Rails!が表示されれば成功です.

今後はこの環境を使ってRailsチュートリアルを進めていきます.