Last Updated: December 07, 2019
·
7.792K
· centipedefarmer

* Running Multiple Versions of Ruby in Production with RVM, Phusion Passenger, and Apache

Some time ago I updated a system to run in Ruby 1.9. Before
upgrading to Ruby 1.9 on our servers, however, I also realized that
we had a few old Rails apps we needed to keep up and
running on the same servers under Ruby 1.8.7. I knew that multiple
versions of Ruby can be installed and switched among using RVM, but
what remained was to figure out how to run apps on these multiple
versions through the same web server. Ultimately what I arrived at
was pieced together from a few sources found online, so I thought it
would be good to write an article tying it all together.

Common setup

This article assumes the fairly typical Linux/Apache 2 web server environment. In addition, we'll figure on Ruby and Phusion Passenger being installed, as I intend to show how you can transition from a setup using only the "system" Ruby to one using RVM Rubies. The Phusion Passenger users guide details the usual installation methods. We've used the gem method and the Ubuntu package before, and use the gem-based install in the setup outlined here.

Installing RVM

Installing RVM is a fairly simple process, familiar to most Ruby developers by now and explained in detail on the RVM website. However, as we are setting it up for production or staging use here, rather than your individual development environment, I recommend using the instructions given there for a system-wide or "multi-user" install, since your new RVM rubies will need to be available to Passenger and probably to your deployment system.

The main Passenger

The RVM site has some info about setting up Passenger to use RVM rubies but a fair bit of it I found unnecessary. More helpful, and what most of our setup is based on, is this
post on the Phusion blog
. First, you should pick out which Ruby version is your "main" one, that is, the one that the majority of the apps on your server will be using. Chances are, that will be the version you have installed already, or maybe a newer patchlevel of the same minor version, but we will be reinstalling it under RVM. The post I just linked to demonstrates setting up Ruby 1.8.7 in this role and a couple different 1.9 versions as the secondary ones in Passenger Standalone instances. We go the other way around; this setup can be adapted either way.

In our case, the majority of apps on the server are different client-specifc installations of the same application. This is fortunate because then they all have the same gem dependencies. If there is a way to get these apps using different gemsets as well, I have yet to discover it, but I suspect it might be possible through some scheme involving environment variables set with SetEnv directives in each app's virtual host configuration. For our purposes here, we will not be using gemsets on the main Ruby, or if we do, all apps on it will be using the same one.

After installing your "main" Ruby version with rvm install, switching to it with rvm use, and installing the gems needed by those apps, install the passenger gem as well. A simple gem install passenger should do the trick, followed by running passenger-install-apache2-module. You probably used this same command when installing Passenger before, but this time it will build a new mod_passenger.so within the current RVM ruby (and gemset, if applicable).

This installer will also tell you the three configuration settings you need to make in Apache to load the Passenger module and have it use this ruby. These specify the path where the new mod_passenger.so can be found on your system, the path to the Passenger gem's directory, and the path to the Ruby executable to be used. The relevant bit of configuration is probably found in either your main Apache configuration file (probably /etc/apache2/apache2.conf), or may be a separate file in /etc/apache/conf.d/, depending on how you like to keep your Apache configuration organized; you may recall having added it when you installed Passenger before. There should be a LoadModule passenger_module followed by the file path to mod_passenger.so, then a PassengerRoot setting and a PassengerRuby setting. Modify these to what was just given to you by passenger-install-apache2-module and restart Apache to reload the configuration. The existing apps that will use this Ruby should continue to work with the same Apache virtual host configurations you were already using.

The secondary (Standalone) Passengers

The post from the Phusion blog describes firing up the other Passenger instances at the command line with passenger start, but this means having to start them back up again by hand if they go down or the server is rebooted. The general gist of it, however, is that the sites using your other, secondary rubies each run on their own Passenger process, and these are then served up through the main Apache server with reverse proxying. This turns out to be surprisingly simple, albeit with a couple of extra moving parts compared to the setup we just did for the main ruby. The apps using your secondary Rubies have the advantage of being able to use their own gemsets.

Usually the first thing people want to know about this setup is how to get those Passenger Standalone processes set up to run automatically so you don't have to babysit them. Most folks have never had to write a Linux init script themselves, and they are not easy. Thankfully, the kind folks at Railsware have written a very nice initscript for passenger that can manage multiple Passenger Standalone instances with a modular configuration like Apache virtual hosts but made up of YAML files. They have made it available for free here on github. This script is a real lifesaver, and is easy to install, configure, and use with the instructions in its README.

Once you've installed passenger-initscript, each of your sites running through Passenger Standalone will get its own YAML configuration file in /etc/passenger.d/. A typical one will look like this, and it's fairly self-explanatory. You'll notice from the @ in the top line that I even have this site using its own gemset:

rvm: ruby-1.8.7-p352@some-old-site
cwd: /var/www/sites/some-old-site.com/current
user: deploy
address: 127.0.0.1
port: 3001
environment: production
max-pool-size: 4
min-instances: 1
pid-file: /var/www/sites/some-old-site.com/current/tmp/pids/passenger.pid
log-file: /var/www/sites/some-old-site.com/current/log/passenger.log

The passenger-initscript responds to all the usual initscript commands, i.e. sudo /etc/init.d/passenger start. Of course, after adding or modifying one you will want to restart it. A pleasant surprise, it even compiles the new Passenger binary for you on the spot. Once you have an app configured and running with it, you should be able to pull it up in your browser at the server's IP and the port number you gave it in this configuration. Each will need its own port; it is common to start at 3000 or 3001, then increment from there: 3002, 3003, etc.

Then to get them showing up at their normal domains/addresses through Apache, you only need to give them a virtual host file that sets up a reverse proxy to them, something like this (note the loopback IP address and the port number given in the ProxyPass and ProxyPassReverse settings):

<VirtualHost *:80>
  ServerName some-old-site.com
  DocumentRoot /var/www/sites/some-old-site.com/public
  PassengerEnabled off
  ProxyPass / http://127.0.0.1:3001
  ProxyPassReverse / http://127.0.0.1:3001
</VirtualHost>

.rvmrc and /etc/profile

As RVM users should be familiar with, an .rvmrc file in a given directory causes RVM to switch to a certain ruby (and, if you like, a certain gemset) when that directory is visited (provided RVM is activated in that shell, that is). It is basically just a shell script run by RVM, and usually contains a command such as rvm use 1.9.2@myapp --create. However, RVM doesn't trust this file the first time it sees it; instead it issues a prompt asking you whether this .rvmrc is cool by you. This may cause your deployment to get stuck -- if, for example, part of your deploy is to cd to this directory and do a rake db:migrate. It is, however, a good idea to have this .rvmrc there in a case like this so that the right versions of Ruby and any gems to be used will be loaded. Visit the directory or use rvm rvmrc trust {directory} to mark the .rvmrc file as trusted.

Other times, issues can arise when RVM isn't activated in a shell
because that shell doesn't read /etc/profile, as in the
case of a non-interactive shells such as those used by a cron job. So
if, for example, you have a cron job to perform some housekeeping
tasks for your app that may involve running rails scripts or rake
tasks, the job may fail or run with the wrong versions of ruby or some
gems. A simple fix is to make the task a short shell script (be sure
the file's permissions are set to executable) and have it load
/etc/profile at the start, for example:

#!/bin/bash
source /etc/profile
cd /var/www/sites/some-site.com/current
rake do:my:stuff