Installing Ruby - Reliably Deploying Rails Applications: Hassle free provisioning, reliable deployment (2014)

Reliably Deploying Rails Applications: Hassle free provisioning, reliable deployment (2014)

8.0 - Installing Ruby

Overview

It’s extremely important that our production environment is running the same version of Ruby as our development environment. If this is not the case we’re likely to run into tough to debug errors and unpredictable behavior.

Generally the Ubuntu package repository contains a fairly old version of Ruby. One solution to this is to use third party repositories to update the system version of Ruby to match your local version.

Whilst this can work I prefer to make use of tools which are designed for easy switching of Ruby versions. The key benefit of this is that it’s possible to have multiple versions installed alongside each other and switch quickly back and forth between them.

This is a particular benefit if you perform an update and discover an unexpected issue. Rather than having to completely rollback the installation, you can simply switch back to the previous version.

Rbenv v RVM

‘My tool is better than your tool’ tends not to add much to development discussions. RVM and Rbenv both have their advantages and disadvantages.

The official rbenv page on why to choose rbenv over rvm is here:

https://github.com/sstephenson/rbenv/wiki/Why-rbenv%3F

In a nutshell I prefer rbenv because I find its operation simple to understand and therefore simple to troubleshoot. As a result it’s rbenv which is covered in this book, if you’re keen to use rvm, there are several good third party cookbooks for installing it so adapting the template to use one of these shouldn’t be too complex.

How rbenv works

$PATH

To understand rbenv we first need to understand $PATH. At first $PATH seems quite intimidating, it’s constantly referred to in troubleshooting threads and Stack Overflow answers (“ahhh, it’s obviously not in your $PATH”) but with very little explanation.

$PATH is actually very simple. If you type “echo $PATH” at your local terminal, you will see something like the following:

1 /Users/ben/.rvm/gems/ruby-1.9.3-p286-falcon/bin:/Users/ben/.rvm/gems/ruby-1.9.\

2 3-p286-falcon@global/bin:/Users/ben/.rvm/rubies/ruby-1.9.3-p286-falcon/bin:/Us\

3 ers/ben/.rvm/bin:/Users/ben/.rvm/gems/ruby-1.9.3-p286-falcon/bin:/Users/ben/.r\

4 vm/gems/ruby-1.9.3-p286-falcon@global/bin:/Users/ben/.rvm/rubies/ruby-1.9.3-p2\

5 86-falcon/bin:/Users/ben/.rvm/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin\

6 :/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/Users/ben/.rvm/bin

We can see that this is just a list of directories, separated by colons. As you can see, on this machine I’m using rvm so I have a lot of rvm specific directories in mine. If you were running rbenv you might see something like this:

1 /usr/local/rbenv/shims:/usr/local/rbenv/bin:/usr/local/sbin:/usr/local/bin:/us\

2 r/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games

When you enter a command, such as “rake” or “irb,” in the console, the system searches through each of the directories in $PATH, in the order they are displayed, for an executable file with that name.

What Rbenv is doing

As you can see in the second output above, rbenv has added a directory to the beggining of my $PATH variable:

1 /usr/local/rbenv/shims

Since $PATH is evaluated from left to right, this means that when I enter a command such as bundle or ruby, the first directory it will look in is /usr/local/rbenv/shims. If it finds a matching command there, it will execute it.

So if, for example, there is an executable file with the name “bundle” in this directory, it will execute it.

If we look at the directory listing (ls /usr/local/rbenv/shims) we see:

1 -rwxr-xr-x 1 root root 393 Apr 17 2013 bundle*

2 -rwxr-xr-x 8 root root 393 Apr 17 2013 erb*

3 -rwxr-xr-x 8 root root 393 Apr 17 2013 gem*

4 -rwxr-xr-x 8 root root 393 Apr 17 2013 irb*

5 -rwxr-xr-x 8 root root 393 Apr 17 2013 rake*

6 -rwxr-xr-x 8 root root 393 Apr 17 2013 rdoc*

7 -rwxr-xr-x 8 root root 393 Apr 17 2013 ri*

8 -rwxr-xr-x 8 root root 393 Apr 17 2013 ruby*

9 -rwxr-xr-x 8 root root 393 Apr 17 2013 testrb*

If we were to look at each of these files (e.g. cat /usr/local/rbenv/shims/ruby) we’d see that each one is just a simple shell script. This shell script performs some processing on the input and then passes our command back to rbenv to process.

Rbenv will then determine which ruby version to execute the command with from the following sources, in order:

1. The RBENV_VERSION environment variable

2. The first .ruby-version file found by searching the directory which the script being executed is in, and all parent directories until reaching the filesystem root

3. The first .ruby-version file found by searching the current working directory and all parent directories until reaching the filesystem root

4. ~/.rbenv/version which is the users global ruby. Or /usr/local/rbenv/version if installing system wide.

5. If none of these are available, rbenv will default to the version of Ruby which would have been run if rbenv were not installed (e.g. system ruby)

Actual ruby versions are installed in ~/.rbenv/versions or, if you’ve installed rbenv system wide rather than on a per user basis (more on this later) in /usr/local/rbenv/versions. Version names in the sources above are simply names of folders in this directory.

We’ll be installing both rbenv and ruby versions using a chef recipe below but for more on using rbenv in your development environment, see the official documentation which is excellent.

https://github.com/sstephenson/rbenv

The rbenv Cookbook

We’ll be using fnichols excellent chef-rbenv cookbook which will be in your cookbooks/chef-rbenv folder.

In our rails-app.json role we include the rbenv::system recipe and contains the following default attributes:

1 "rbenv":{

2 "rubies": [

3 "1.9.2-p320"

4 ],

5 "global" : "1.9.2-p320",

6 "gems": {

7 "1.9.2-p320" : [

8 {"name":"bundler"}

9 ]

10 }

The rbenv::system performs a system wide install of rbenv, this means that it’s installed to /usr/local/rbenv rather than ~/rbenv. This is generally my preference as it reduces issues caused by, for example, cron jobs running as root.

The rubies attribute contains an array of ruby versions to be installed and made available. If you’re planning on hosting multiple applications on the same server, each of which requires a different ruby version, you can specify them here and ensure the correct one is used by specifying it in a .ruby-version file in your project’s root directory.

The global attribute specifies the global system ruby (/usr/local/rbenv/version) which rbenv will fall back to as per the hierarchy defined above. This should be one of the rubies specified in the previous array.

The gems attribute should contain a key for each of rubies being installed. This should contain an array of gems to be installed for that version of ruby. In general I install just bundler and allow each applications deployment process to take care of installing its specific gems (covered in section 2 of this book).

Next we’ll look at how to use Monit to have the server automatically recover from many types of failure and alert us if there’s a failure it cannot recover from.