依存ライブラリの設定をするときは必ずバージョン指定をしようという話

この記事の内容は主にRubyの話ですが、それ以外の言語でも共通して言えることだと思うのでRubyistじゃない人もぜひ読んでみて下さい。

みなさんはGemfileでインストールするgemのバージョンを指定をしていますでしょうか? 僕は指定するように意識はしていたのですが、開発環境でインストールしたバージョンがGemfile.lockファイルで固定されるため、Gemfile自体のバージョン指定はそれほど重要なこととは意識していませんでした。(この時点で理解が甘かったのですが。。)

しかし、タイトルの通りGemfileのバージョン指定が重要と意識するきっかけとなる事象がありました。それを以下に記載します。

環境

  • AWS ElasticBeanstalk
  • 64bit Amazon Linux 2016.09 v2.2.0 running Ruby 2.3 (Puma)

問題が起きたGemfile

事象に関係する箇所のみ切り出しています。
※2016/11/30現在、Rails5でActiveAdminを使うためには、githubのリポジトリを参照する必要があります。

source 'https://rubygems.org'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'

# ActiveAdmin for Rails5 Support
gem 'activeadmin', github: 'activeadmin'
gem 'inherited_resources', github: 'activeadmin/inherited_resources'

事象

ElasticBeanstalk に Railsアプリケーションをデプロイした際に、前日までは正常にデプロイできていたのに突然いままで見たことのないエラーが発生しました。

/var/log/eb-activity.log
-------------------------------------
  ++ PATH=/opt/rubies/ruby-2.3.1/bin:/opt/elasticbeanstalk/lib/ruby/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin
  +++ /opt/rubies/ruby-2.3.1/bin/ruby -
  ++ eval 'export RUBY_ENGINE=ruby;
  export RUBY_VERSION=2.3.1;
  export GEM_ROOT="/opt/rubies/ruby-2.3.1/lib/ruby/gems/2.3.0";'
  +++ export RUBY_ENGINE=ruby
  +++ RUBY_ENGINE=ruby
  +++ export RUBY_VERSION=2.3.1
  +++ RUBY_VERSION=2.3.1
  +++ export GEM_ROOT=/opt/rubies/ruby-2.3.1/lib/ruby/gems/2.3.0
  +++ GEM_ROOT=/opt/rubies/ruby-2.3.1/lib/ruby/gems/2.3.0
  ++ ((  0 != 0  ))
  + cd /var/app/ondeck
  + su -s /bin/bash -c 'bundle exec /opt/elasticbeanstalk/support/scripts/check-for-rake-task.rb assets:precompile' webapp
  + '[' false == true ']'
  + su -s /bin/bash -c 'bundle exec rake assets:precompile' webapp
  I, [2016-11-29T20:45:54.239448 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/admin/active_admin-f5a537e92b2c49222b39c8405cfba0b5d04ccd7a642c5fa42a958a72f1688b4e.css
  I, [2016-11-29T20:45:54.239904 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/admin/active_admin-f5a537e92b2c49222b39c8405cfba0b5d04ccd7a642c5fa42a958a72f1688b4e.css.gz
  I, [2016-11-29T20:45:54.240259 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/active_admin/nested_menu_arrow-15084d93c65c1964d7077700ea748bd2d70cfa2d4c19707c58a9c64e232dd442.gif
  I, [2016-11-29T20:45:54.240564 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/active_admin/nested_menu_arrow_dark-7c43b8e0a5f8823875f49a093c9d7a6b374f885b6f9cc248ae9cd7e6e9b29034.gif
  I, [2016-11-29T20:45:54.240845 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/active_admin/datepicker/datepicker-input-icon-d9c2bb73769af777c8a71720d29741f3a499aebd5a043e9a119bd0d9597aed47.png
  I, [2016-11-29T20:45:54.241145 #22218]  INFO -- : Writing /var/app/ondeck/public/assets/active_admin/orderable-29374dbb55b0012d78a37c614d573bb3474f0779849b478a147d0f1845ca6617.png
  rake aborted!
  Sprockets::FileNotFound: couldn't find file 'jquery-ui/datepicker' with type 'application/javascript'
  Checked in these paths:

eb-activity.log を調べていくと、 activeadmin が依存する jquery-ui/datepickerassets:precompile に失敗していることが分かりました。しかしローカル環境(Mac)では正常に assets:precompile ができます。

原因

結論から言うと、 activeadmin の Gemfile に jquery-ui-rails のバージョン指定がされていなかったことにより 、こちらのバグを踏むこととなりました。

要約すると、 activeadmin が依存している jquery-ui-rails のバージョンが 6.0.0 になると、 activeadmin の assets の参照が正しくできなくなると言うことです。Gemfile.lockを確認すると、確かにローカル環境は query-ui-rails (5.0.5) 、ElasticBeanstalk環境は query-ui-rails (6.0.0) になっていました。

しかし、Gemfile.lock もデプロイしているはずなのに何故 ElasticBeanstalk の環境だけ勝手に jquery-ui-rails のバージョンが上がってしまったのかという疑問が残りました。

それは以下の記事に答えがありました。

ただし、ローカル環境がWindows、リモート環境がLinux、というようなシチュエーションでは、バイナリありのgemだと入れるバージョンが異なってきうること、さらには片方だけ入れるgemが発生することもあって、Gemfile.lockを共有することができず、それぞれ別個に生成せざるを得なくなってしまっています。

さらに悪いことに、リモートサーバをAWSのElastic Beanstalkで生成していたため、サーバの変動ごとにGemfile.lockが生成されることとなり、Gemfileで指定しない場合、gemは常に最新版が呼ばれることとなってしまいました。

つまり、ローカル環境(Mac)で作られた Gemfile.lock は ElasticBaenstalk 環境では使われず、 今回のデプロイのタイミングでたまたま query-ui-rails の最新版がリリースされていたため今回の事象が発生したのでした。

暫定対処

activeadmin の Bugfix までの暫定処置として、Railsアプリケーションの Gemfile に jquery-ui-rails のバージョン指定を追加することでバージョンを固定して凌ぐことができました。

# 暫定処置
gem 'jquery-ui-rails', '~> 5.0.5'

まとめ

  • Gemfile.lockは動作環境が異なる場合は適用されないので、必ずGemfileにバージョンを指定しよう
  • そうでなければ本番環境と全く同じ環境で開発をするか、Dockerなどを使おう
  • Ruby(Gemfile)に限らず他の言語でも必ず依存ライブラリにはバージョンを指定しよう