ХостингИспользуем bundler на locum.ru


Странные сообщения об ошибках

Если вы читаете эту статью, то вполне вероятно вы видели такую ошибку, несмотря на то, что gem  с нужной версией ruby on rails у вас установлен.

Missing the Rails 2.3.8 gem. Please `gem install -v=2.3.8 rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.

Или может быть такую?

You have already activated ** rack** 1.1.0, but your Gemfile requires rack 1.0.1. Consider using bundle exec. (Gem::LoadError)

Версии, указанные в примерах могут, конечно, отличаться, но сути это не меняет.  В этой статье я объясню от чего такое возникает и как этого избежать.

Rack и проблемы, связанные с ним

Всему виной rack, точнее несоответствие его версии тому, что хочет видеть rails. Причем первое сообщение об ошибке очень странное, никаким образом не упоминает rack и даже никак не намекает на него, но давайте посмотрим в код, который выводит это сообщение. Открывает config/boot.rb, и видим:

class GemBoot < Boot
   def load_initializer
       self.class.load_rubygems
       load_rails_gem
       require 'initializer'
   end
   def load_rails_gem
       if version = self.class.gem_version
          gem 'rails', version
      else
          gem 'rails'
      end
      rescue Gem::LoadError => load_error
          $stderr.puts %(Missing the Rails #{version} gem.
          Please `gem install -v=#{version} rails`,
          update your RAILS_GEM_VERSION setting in config/environment.rb
          for the Rails version you do have installed,
          or comment out RAILS_GEM_VERSION
          to use the latest version installed.)
          exit 1
      end

Тут-то и становится понятно, что это сообщение выводится не только когда не удается загрузить именно rails, но и в любом другом случае, когда возбуждается исключение Gem::LoadError. Стоит отметить, что в последующих версиях rails (где-то после 2.3.10), сообщение об ошибке стало более информативно, и пользователи перестали предпринимать отчаянные попытки установить нужный gem несколько раз подряд, до этого видимо авторы rails не подозревали, что такое исключение может возникнуть в иной ситуации.

Вторая ошибка уже более информативна и подсказывает нам, что дело тут в rack, как это на самом деле обстоит и в первом случае. Суть в том, что отличный сервер для rails — unicorn, использует rack для своей работы. Современные версии Ruby On Rails так же используют rack для своей работы, и необходимо, чтобы они использовали одну и ту же версию. Иначе будем получать такие вот ошибки, потому что вызов activate! у rack во время загрузки rails не пройдет.

Очевидным решением было бы добавить в config/environment.rb строку следующего вида:

config.gem 'rack', '=1.0.1'

Однако, попробовавшие это решение будут разочарованы — ничего не поменяется. Проблема в том, что unicorn вначале запускается сам, загружает свой код и активирует rack. Причем написан он довольно грамотно и активировать будет самую новую версию, которая есть, работает он с ней нормально. И уже только после этого unicorn будет пытаться загрузить Ruby On Rails, только тогда считается environment.rb и эта строка, но более новый rack уже будет активирован.

Еще одним простым решением будет просто удалить локальную инсталляцию более нового rack, оставив только нужную нашему rails-проекту версию, но такое решение подходит лишь для тех, у кого один проект, или на всех проектах нужен один и тот же rack. Если у вас есть проекты, например на rails 2.3.8, 2.3.11 и 3.0.2 — вам потребуется для каждого из них своя версия rack и вариант с удалением не подойдет.

Тут на выручку приходит bundler.

Решаем проблему с помощью bundle

Bundler — это средство управления различными окружениями gem. Он легко позволяет создать изолированную инсталляцию gem для конкретного проекта, чтобы остальные, установленные на локальной машине gem не мешали работать. Так же при помощи bundler удобно устанавливать большие пачки gem в системные или иные директории. На всех серверах Locum.ru bundler уже установлен. Если при попытке выполнить команду bundle вы получаете ошибку:

bash: bundle: команда не найдена

То убедитесь, что у вас правильно настроена переменная окружения PATH. Мы рекомендуем устанавливать следующее значение PATH=»/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin:/$HOME/.gem/ruby/1.8/bin:/var/lib/gems/1.8/bin».

Подробнее о том, как работать с bundler и что он позволяет вы всегда сможете узнать в официальной документации, тут же мы кратко рассмотрим только ограничение версий нужных нам gem. Итак, для начала создаем Gemfile. Gemfile — это такой список используемых в проекте gem, с указанием нужных версий. Если версию не указать — будет доступна самая последняя из установленных. Важно перечислить все gem, необходимые для работы проекта, так как то, что не указано в Gemfile, не будет доступно из кода ни rails, ни unicorn. Ровно по этой же причине необходимо добавить сам unicorn в список. Gemfile должен находиться в той директории, откуда запускается unicorn, т.е. в нашем случае это current в каталоге capistrano, RAILS_ROOT. Если вы используете систему контроля версий, рекомендуем поместить Gemfile  в нее, так как он будет нужен для каждого деплоя.

Вот пример Gemfile для запуска redmine из trunk:

source "http://rubygems.org"
gem "unicorn"
gem "rack", "1.1.0"
gem "rails", "2.3.11"
gem "rake", "0.8.7"
gem "i18n", "0.4.2"
gem "rubytree", "0.5.2", :require => "tree"
gem "RedCloth", "~>4.2.3", :require => "redcloth" # for CodeRay
gem "mysql"
gem "coderay", "~>0.9.7"

Сохранив такое содержимое рекомендуем выполняем следующие команды (все запускается из директории current):

bundle install --path ../../shared/gems
kill -QUIT `cat /var/run/unicorn/имя_пользователя_на_сервере/имя_проекта.логин_в_панели_управления.pid`
bundle exec unicorn_rails -Dc /etc/unicorn/имя_проекта.логин_в_панели_управления.rb

Первая команда должна установить все gem с нужными версиями (просто так, на всякий случай), важно не забывать про ключ —path, потому что иначе bundler будет пытаться установить все в системную директорию и требовать с вас пароль на sudo. Вторая — остановить уже запущенный экземпляр unicorn для проекта. Если он упал ранее по причине ошибки — ничего страшного, PID-file просто будет отсутствовать. Последняя команда стартует unicorn в созданном нами окружении bundle. После этого все должно работать, перезагрузка кода так же выполняется отправлением сигнала USR2. Так же для этого есть специальная кнопка в панели управления хостингом Locum.ru.

Если вы используете capistrano, как мы всем рекомендуем, то нужно добавить вызов bundle exec перед командой запуска unicorn в ваш deploy.rb.

Иногда при работе со старыми версиями Rails возникает ошибка, примерно следующего вида:

uninitialized constant ActiveSupport::Dependencies::Mutex (NameError)

Эта проблема не связана с bundle и rack, но возникает не редко, связанно это с изменениями в новых версиях rubygems, на которые не рассчитаны старые версии rails. Решается проблема добавлением вызова require ‘thread’ в config/environment.rb или config/boot.rb. Спорный вопрос в какой из этих файлов идеологически правильнее добавить вызов, но сработают оба варианта. Кто имеет четкое мнение по этому вопросу — напишите, пожалуйста, в комментариях. Что до меня, так идеологически правильным я вижу только одно решение — обновить версию rails до такой, где ошибки не возникает, а все остальное примерно одинаково костыль, но костыль абсолютно рабочий.

Подведем итог

Bundler — действительно удобное средство для решения таких проблем. Любая система установки дополнительных пакетов, вроде rubygems, которая позволяет держать много версий одной и той же библиотеки, в итоге должна столкнуться с проблемами конфликтов версий и как-то их решать. Недостатком является только то, что саму версию rubygems выбрать при помощи bundler никак не получится, но это нужно уже совсем редко, в таких ситуациях придется скорее всего решать проблему при помощи RVM. Процесс деплоя с bundler не сильно усложнился, тем более все это так же легко автоматизируется с Capistrano.  Ограничивать версию разных gem для rails можно было и раньше, используя вызовы config.gem в environment.rb и хорошим тоном всегда считалось перечислять там нужные gem. Сейчас просто ограничивается набор gem не только для rails, но и для сервера приложений, а вызов bundle install просто заменяет нам старое rake gems:install.

На этом прощаемся с вами, всем удачи в развитии своих проектов и их размещении на Locum.ru. Кстати, кто-нибудь сталкивался в своей практике с необходимостью ограничить версию чего-то еще не только для самого rails-приложения, но и для сервера, кроме Rack? Если да — напишите, пожалуйста, об этом в комментариях. Пока по общению с нашими клиентами и собственной практике разработки и деплоя мы не встречались с другими примерами.

  1. RVM на хостинге не планируете запустить?

  2. Михаил, вообще планируется. По секрету скажем, кому очень нужно — те и сейчас запускают, но со временем должно в стандартную поставку попасть.