この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
モバイルアプリサービス部の五十嵐です。
先日、あるRailsアプリケーションの開発環境を同僚のマシンに作成しようとしたところ、gemのインストールに1日かかってしまいました。環境構築は手順化されていたのですが、トラブったのは主にNative Moduleを利用する libv8
、 therubyracer
、 rmagick
などのおなじみの面々です。手順を作った時は、これらのgemのインストールに必要なライブラリを brew install
で最新バージョンをインストールするだけでよかったのですが、時が経ちライブラリの最新バージョンが更新されていたことが主な原因でした。この状況はいかんな〜と思い、Railsアプリケーションの開発環境もDockerにすることにしました。
本記事では、Railsアプリケーションの開発環境をDockerにするときに検討したことや問題点などを書いています。なお、本記事で紹介するDockerfileやdocker-compose.ymlは開発環境用なので、本番環境の参考にはしないでください。
アジェンダ
- セットアップ
- デバッグ
- Tips
- 成果物
セットアップ
Railsアプリケーションのセットアップには、主に bundle install
と rake
コマンドの実行があります。これらそれぞれをタイミングで行うのが良いか検討しましました。
bundle install
結論としてはDockerfileに含めてbuildの段階で実行することにしました。理由は、Gemfileの変更がそれほど頻繁ではないこと、Gemfileを変更しなければbuild時にキャッシュが効くので時間がかからないことなどがあります。また、コンテナ起動後にGemfileを変更した場合は、 docker-compose exec rails /bin/bash
でコンテナのシェルを起動し、 bundle install
することでgemの更新ができます。
もう一つのアイディアとして、Data Volume Container を作成し、そこにgemをインストールする方法を試しました。このメリットは、volumeを削除しない限りインストールしたgemのデータを再利用できるため、Gemfileを頻繁に更新してもbuildが素早くできるということです。ただし起動時にはgemがインストールされている必要があるので、どこかのタイミングで docker-compose run --rm rails bundle install
などを実行する必要があります。起動時にcommandなどで bundle install
するのは、起動が遅くなるのがいまいちでした。しかしこの方法には、DockerのVolumeの読み込みが非常に遅いという大きな問題があったので諦めました。この問題についてはDocker for Macが遅い問題をdocker-syncで解決する - Cluex Developersブログが詳しいです。
採用はしませんでしたが、Data Volume Container を使う設定はこんなかんじです。不要な箇所は省いています。docker-compose.yml の version:3 から volumes_from
がなくなっていて、代わりの書き方を調べるのに苦労しました。いまだにしっくりきません。もしかして間違っているかも。
docker-compose.yml
version: "3"
services:
rails:
build: .
command: bundle exec rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/myapp
- bundle-volume:/myapp/vendor/bundle
ports:
- 3000:3000
datastore:
build:
context: .
dockerfile: Dockerfile-datastore
volumes:
- bundle-volume:/myapp/vendor/bundle
volumes:
bundle-volume:
datastoreのイメージを作成するための Dockerfile。
Dockerfile-datastore
FROM busybox:latest
RUN mkdir -p /myapp/vendor/bundle
VOLUME /myapp/vendor/bundle
Data Volume Container は複数のコンテナ間でvolumeを共有するための方法です。私もまだ理解が怪しいので、詳しくはManage data in containers - Docker Documentationを参照してください。
rake コマンド
結論としては手動で docker-compose run --rm rails rake db:migrate
などを実行することにしました。初期化用にスクリプトにまとめておいてもいいかもしれません。前述した通り、起動時のcommandに設定してしまうと、必要もないのにコマンドが実行され毎回の起動が遅くなるため、起動とは分けました。
デバッグ
次にデバッグの方法について説明します。
byebug, pry-byebug
コンテナ内で起動しているRailsアプリケーションに対しての、byebug
や pry-byebug
を利用したデバッグは、コンテナに Attach することでできます。Attachしてコンテナの外から命令を送るために stdin_open: true
と tty: true
の2つの設定が必要になります。
docker-compose.yml
version: "3"
services:
rails:
build: .
command: /myapp/script/start.sh
volumes:
- .:/myapp
ports:
- 1234:1234 # debug port
- 3000:3000
stdin_open: true
tty: true
これで docker-compose up -d
を実行し、アプリケーションが起動した後に docker attach xxx_rails_1
を実行します。すると以下のようにdebug画面が表示されます。以下は byebug の例です。
[8, 17] in /myapp/app/controllers/messages_controller.rb
8: @per = params[:per] || 20
9: @page = params[:page] || 1
10: @message = Search::Message.new(search_params)
11: @messages = @message.matches.page(@page).per(@per)
12: byebug
=> 13: end
14:
15: # Message新規作成画面を表示
16: def new
17: @message = Message.new
(byebug) @per
20
(byebug)
Attach したコンテナから抜ける場合は、 Control + P, Q
でコンテナを終了せずコンソールだけ抜けます。 Control + C
をするとコンテナが終了してしまいます。
参考: Docker and ruby on rails – Dominik Burgdörfer – Medium
Intellij IDEA, RubyMine
Intellij IDEA や RubyMine でもデバッグ機能を使うことができます。まずはじめに「intellij docker rails debug」とかで検索するとRubyMine 2017.1 Help :: Docker - Creating a Docker Deployment run/debug configurationという、いかにもデバッグ実行ができそうなドキュメントが見つかるのですが、こちらはIntellijからDockerやDockerCopmoseを起動する設定で、デバッグではありませんでした。
よくよく調べると、Intellij IDEA や RubyMine のRubyのデバッグ機能は「Ruby remote debug」というIDEの機能と ruby-debug-ide
という gem を使っているということが分かり、こちらの記事に UPDATED: Running the Rails debugger in a Docker container using RubyMine on a Mac にやり方がそのまま載っていました。
まず IDE の RUN > Edit Configuration... を開き、「+」ボタンから「Ruby remote debug」を追加します。設定は以下のとおりです。
Gemfileに以下を追加します。
Gemfile
group :development, :test do
# remote debug
gem 'ruby-debug-ide'
gem 'debase'
end
rdebug-ide で Railsサーバを起動します。
bundle exec rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 26162 -- /myapp/bin/rails s -b 0.0.0.0 -p 3000 -e development
以下のメッセージが表示されますので、この状態で先ほど作成した「Ruby remote debug」を実行します。
Fast Debugger (ruby-debug-ide 0.6.0, debase 0.2.1, file filtering is supported) listens on 0.0.0.0:1234
Rails serverが起動します。
Fast Debugger (ruby-debug-ide 0.6.0, debase 0.2.1, file filtering is supported) listens on 0.0.0.0:1234
=> Booting WEBrick
=> Rails 4.2.4 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
あとは、Intellijにブレイクポイントを設定して通常と同じデバッグができます。ただ、ステップインでソースコードが移動しないなど、完璧な動作ではありませんでした。
Ruby remote debug の トラブルシューティング
ブレイクポイントで止まらない
Cannot render console from 172.19.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
このエラーが出力されている場合、config/environments/development.rb
に以下を追加します。
config/environments/development.rb
config.web_console.whitelisted_ips = '0.0.0.0/0'
ブレイクポイントでNoMethodErrorが発生する
`undefined method '+' for nil:NilClass`
Trace log
byebug (5.0.0) lib/byebug/processors/command_processor.rb:27:in `at_breakpoint'
byebug (5.0.0) lib/byebug/context.rb:78:in `at_breakpoint'
app/controllers/messages_controller.rb:16:in `new'
byebug
のバージョンが古いことが問題でした。 byebug
のバージョンを最新にします。
Tips
プロセスの起動ファイルが消えない場合がある
docker stop
したときに、 tmp/pids/server.pid
が削除されず、次に docker start
した時にRailsアプリケーションが起動できないことがあります。雑な対応ですが、 docker start
時に実行するシェルに rm /myapp/tmp/pids/server.pid
を1行追加して対処しました。
CircleCI 2.0 のホスト名の解決
docker-compose で depends_on したサービスのホスト名はサービス名でDNS解決するのですが、CircleCI 2.0 では 127.0.0.1 で解決するので設定に差異が生まれました。これはホスト名を環境変数にすることで対処しました。また、ホスト名のデフォルト値を 127.0.0.1 にすることで、Dockerを使わないローカル環境の起動もできるようにしています。
.circleci/config.yml
version: 2
jobs:
build:
working_directory: ~/my-app
docker:
- image: ruby:2.2.3
- image: mysql:5.7.11
environment:
MYSQL_ROOT_PASSWORD: password
TZ: JST
- image: fingershock/dynamodb-local
以下略
config/database.yml
test:
host: <%= ENV['RDS_HOSTNAME'] || '127.0.0.1' %>
成果物
最後に完成した Dockerfile と docker-compose.yml を紹介をします。Docker公式のドキュメントにRailsアプリケーション用のDockerfileとDockerComposeのサンプルがあるのでこれをベースとしました。
Quickstart: Compose and Rails - Docker Documentation
Dockerfile
FROM ruby:2.2.3
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
# aws-cliのインストール
RUN apt-get install -y python2.7-dev \
&& curl -O https://bootstrap.pypa.io/get-pip.py \
&& python get-pip.py \
&& pip install awscli
RUN mkdir /myapp
WORKDIR /myapp
ENV BUNDLE_JOBS=4
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
Railsアプリケーションのセットアップに aws-cli
が必要なので、追加してインストールしています。
docker-compose.yml
version: "3"
services:
mysql:
image: mysql:5.7.11
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=password
dynamodb:
image: fingershock/dynamodb-local
ports:
- 8000:8000
rails:
build: .
command: /myapp/script/start.sh
environment:
AWS_DEFAULT_REGION: ap-northeast-1
AWS_ACCESS_KEY_ID: dummy
AWS_SECRET_ACCESS_KEY: dummy
DYNAMODB_ENDPOINT: http://dynamodb:8000
RDS_HOSTNAME: mysql
volumes:
- .:/myapp
ports:
- 1234:1234 # debug port
- 3000:3000
depends_on:
- mysql
- dynamodb
stdin_open: true
tty: true
script/start.sh
#!/bin/bash
# プロセスが正常終了しなかったときに、 server.pid ファイルが残ることがあるので削除する。
rm /myapp/tmp/pids/server.pid
bundle exec rails s -p 3000 -b '0.0.0.0' -e development
script/debug.sh
#!/bin/bash
# プロセスが正常終了しなかったときに、 server.pid ファイルが残ることがあるので削除する。
rm /myapp/tmp/pids/server.pid
bundle exec rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 26162 -- /myapp/bin/rails s -b 0.0.0.0 -p 3000 -e development
さいごに
これでローカル環境にRubyすらなくてもRailsアプリケーションの開発ができるようになりました。開発環境の構築も10分くらいで済むでしょう。トレードオフとして開発者はある程度のDockerの知識が必要となり、慣れるまで1日くらいかかるかもしれません :)