この記事はランサーズ Advent Calendar 2020 20日目の記事になります。
皆様こんにちは!開発部インターンの@44511336tatuyaです!
私は11月の頭から開発部のインターンとしてLancers Creativeという月額定額制のクリエイティブサービスの開発をしています。こちら開発言語・フレームワークにRuby on Railsを採用し、開発環境はDockerで構築しております。今回はDocker × Ruby on Rails で快適に開発できるために取り組んだことをまとめてみました。
当初の課題
- DockerとMacOSの共有ファイルシステムの差異により、オーバーヘッドが発生して遅い
- コンテナを再起動しないとソースコードがリアルタイムで反映されない時がある
- デバックツールのpry-rails(gem)がNginxの設定によりタイムアウトして使えない
課題解決に取り組んだこと
- ホストのディレクトリをマウントするのではなく一旦コピーしてみる(原因調査)
- docker-syncを導入してみる(速度改善)
- Dockerの設定のUse gRPC FUSE for file sharingをOFFにする/mount時にdelegatedの追加(速度改善)
- Railsのアプリケーションファイルの
config/environments/development.rb
を一部変更する(ソースコード反映) - デバックに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
で直接ホスト側のディレクトリをマウントするのではなく、rsync
やunison
の仕組みでホスト側のファイルをコンテナ側に転送しよう、というものです。
(詳細はわかりませんが、実際には、シンク用の名前付きボリュームを作成し、そこにホスト側のファイルを同期した上でそのボリュームをコンテナにマウントすることで実現しているようです)
早速検証してみました。
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.rb
のfile_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さんです!