ランサーズ(Lancers)エンジニアブログ > Docker > RailsプロジェクトのDocker開発環境の改善に取り組んだこと

RailsプロジェクトのDocker開発環境の改善に取り組んだこと

hirano.tatsuya|2020年12月20日
Docker

この記事はランサーズ Advent Calendar 2020  20日目の記事になります。

皆様こんにちは!開発部インターンの@44511336tatuyaです!
私は11月の頭から開発部のインターンとしてLancers Creativeという月額定額制のクリエイティブサービスの開発をしています。こちら開発言語・フレームワークにRuby on Railsを採用し、開発環境はDockerで構築しております。今回はDocker × Ruby on Rails で快適に開発できるために取り組んだことをまとめてみました。

当初の課題

  • DockerとMacOSの共有ファイルシステムの差異により、オーバーヘッドが発生して遅い
  • コンテナを再起動しないとソースコードがリアルタイムで反映されない時がある
  • デバックツールのpry-rails(gem)がNginxの設定によりタイムアウトして使えない

課題解決に取り組んだこと

  1. ホストのディレクトリをマウントするのではなく一旦コピーしてみる(原因調査)
  2. docker-syncを導入してみる(速度改善)
  3. Dockerの設定のUse gRPC FUSE for file sharingをOFFにする/mount時にdelegatedの追加(速度改善)
  4. Railsのアプリケーションファイルのconfig/environments/development.rbを一部変更する(ソースコード反映)
  5. デバックにweb-consoleを導入する(デバックツール適用)

構成

├── dev
│   ├── README.md
│   ├── app
│   │   ├── Dockerfile
│   │   ├── common
│   │   ├── gemrc
│   │   ├── nginx
│   │   │   ├── app.conf
│   │   │   ├── domain.conf
│   │   │   └── nginx.conf
│   │   └── supervisor
│   │   ├── app.conf
│   │   └── delayed_job.sh
│   ├── docker-compose.yml
│   ├── elb
│   │   ├── Dockerfile
│   │   ├── h2o
│   │   │   ├── conf.d
│   │   │   │   ├── domain.conf
│   │   │   └── h2o.conf
│   │   └── service.sh
│   └── mysql
│   ├── Dockerfile
│   ├── mysql_init.sh
│   ├── mysqld.cnf
│   └── service.sh

本番環境とより近い構成にするため、ELB、App、MySQL、の3つのコンテナから構成されております。

環境

  • MacOS Catalina バージョン10.15.7
  • Ruby 2.7.1
  • Rails 6.0.3
  • MySQL 5.7

1. ホストのディレクトリをマウントするのではなくソースコードをコピーしてみる。(原因の切り分け)

まずアプリ側なのかMacの共有ファイルシステムに問題があるのか原因の切り分けを行いました。といっても本番環境はECS/Fargateで運用しており、問題なくレスポンスは早いので、Macのファイルシステムが問題なのは間違いないと予想できます。

docker-cp ~/www/app <コンテナID> : /var/www/app

リロード時間は10秒→2秒台後半に短縮!これにより、App側に問題は無く、Macの共有ファイルシステムが問題だと判明しました。

2. docker-syncを導入してみる(速度改善)

docer-syncの使う理由と仕組みは下記の記事を参考にしました。

参考: docker-syncでホスト-コンテナ間を爆速で同期する

docker-syncの仕組みとしては、 docker の -v で直接ホスト側のディレクトリをマウントするのではなく、rsyncunisonの仕組みでホスト側のファイルをコンテナ側に転送しよう、というものです。

(詳細はわかりませんが、実際には、シンク用の名前付きボリュームを作成し、そこにホスト側のファイルを同期した上でそのボリュームをコンテナにマウントすることで実現しているようです)

早速検証してみました。

gem install docker-sync

brew install fswatch

brew install unison

docker-sync.yml

version: '2'

syncs:
 sync-volume:
  src: ~/www/app
  sync_strategy: native_osx

docker-compose-dev.yml

version: '2'

volumes:
 sync-volume:
  external: true

services:
 app:
  volumes:
   - sync-volume:/var/www/app/

docker-sync start # docker-syncで同期開始
docker-compose -f docker-compose.yml -f docker-compose-dev.yml up

こちらもリロード時間を2秒代前半まで短縮できました!しかしこれだと開発環境の構築手順が増えてしまい、Windowsでも開発している方もいるので動作確認などの手間が増えてしまいます。

3. Dockerの設定のUse gRPC FUSE for file sharingをOFFにする(速度改善)

こちらの設定は前回のブログでも@adachin0817さんが紹介してくれたので詳細は割愛します。
以下参考
https://engineer.blog.lancers.jp/2020/12/replace-ecs-fargate-lancers/

https://blog.hanhans.net/2020/11/28/docker-for-mac-slow-again/

こちら適用してみたところリロード時間が1/2に短縮されました!Dockerの設定を一つ変えるだけでこれは素晴らしい!たた、これをオフにしてしまうとrails cにログインできなくなります。なので基本はオンにするべきです。

  • mountの部分にdelegatedを追加する

書き込みと読み込みの一貫性を担保しない代わりにパフォーマンスが向上されるものです。基本こちらは追加するべきでしょう。

volumes:
- ~/www/hoge:/var/www/hoge/:delegated

4. Railsのアプリケーションファイルのconfig/environments/development.rbを一部変更する(ソースコードが反映されない問題)

こちらの設定はソースコードがリアルタイムで反映されない問題の解決策になります。
参考: 【Rails6】Dockerコンテナを再起動しないとソースコードが反映されない

ファイルの変更を検知しているのはconfig/environments/development.rbfile_watcherという部分です。
この設定ファイル内でconfig.file_watcher = ActiveSupport::EventedFileUpdateCheckerによって変更イベントを検知しています。(名前にもEventedとありますね。)

しかし、Dcokerによる環境構築ではvolumeで共有ファイルとしてホスト側のディレクトリを仮想環境にマウントしており、この方式だと変更イベントが発生しないようです。故にファイルの変更を検知することができず、コンテナを再起動しないとソースコードの変更がされないという事態になります。

config.file_watcher = ActiveSupport::FileUpdateCheckerはソースコードの更新をサーバー起動中にチェックする機能なのでこれでviewだけでなくController処理の変更も再起動せずによくなります。

なるほど!当初ソースコードが反映されないのは前述のファイルシステムの差異によるものだと思っていましたが問題は別にあったみたいです。

development.rb

config.file_watcher = ActiveSupport::EventedFileUpdateChecker

以下に変更。

config.file_watcher = ActiveSupport::FileUpdateChecker

しっかりソースコードがリアルタイムで反映されるようになりました!

5. デバックツールにweb-consoleを導入する

やはりRailsプロジェクト開発にはpry-railsによるデバッグが欠かせません。しかし、当プロジェクトはNginxとunicornをリバースプロキシしていることによりタイムアウトになってしまうことからpry-railsがDocker開発環境で使えないことが判明しました。@adachin0817さんに相談していたところ、web-consoleでデバッグすることができました。

早速使ってみる

console を立ち上げたい View または、Controller で console メソッドを呼び出します。
projects_controller.rb

def index
    service = Projects::SearchService.new(search_params: params.permit(:page, :type, :search_type, q: {})).authorized_by(current_user).invoke
    @search = service.search
    @projects = service.projects
    @selected_tab = service.selected_tab
    console
  end

これで以下のような画面が立ち上がります。


あとはrails consoleと同じように欲しい値を入力すると値の中身が取れます。

  • Docker x Nginx x Unicornでbinding.pryを利用したい場合

NginxでUnicornをリバースプロキシしてbinding.pryでデバッグするとサイトがタイムアウトされてしまいます。binding.pryはrais sで起動するため、unix socketsドメインだと不可能です。なので開発環境ではUnicornのportで起動するようにし、supervisorでunicornを停止後に bundle exec rails sで起動すればデバッグ可能になります。


 まとめ

結果的に速度改善に関してはDockerの設定であるmountの部分でdelegatedを追加することでストレスなく開発ができるようになりましたので、docker-syncの導入は無くなりましたがdocker-syncを導入する過程でMacとDockerのファイルシステムの違いなどを詳しく調べたことは良い勉強になりました。また、会社全体のインフラや環境構築などの課題を一手に引き受けてくれているSREチームの皆様は本当に凄いなと思い、尊敬の念に堪えません。
私もバックエンドの開発だけでなく、インフラも含めたプロダクト管理ができるようになりたいと強く思いました。

明日はSREチームのyKanazawaさんです!