The previous article in this series has left us with a minimally-configured Nginx installation running on an EBS-backed Ubuntu EC2 instance.
This article will pick up where we left off. The latest versions of Ruby and RubyGems will be downloaded and installed. Then the Rails and Thin gems will be installed. Nginx will then have the final configuration changes applied to enable it to proxy through to the Thin workers. Thin is a lean Ruby-based web-server that has been designed to replace Mongrel (which was the standard Ruby web server until development ceased), and uses various components lifted from Mongrel (e.g. the parser - giving us the same (or better?) speed and security as Mongrel).
It’s been a while since I deployed Ruby on Rails - and that was using Mongrel - so let’s see how Thin matches up.
libyaml
Make sure you have installed libyaml and the appropriate development files (headers, etc.) prior to compiling and installing. Forgetting this will result in a big facepalm and things like:
|
1 2 3 |
/usr/local/lib/ruby/1.9.1/yaml.rb:56:in `<top (required)>': It seems your ruby installation is missing psych (for YAML output). To eliminate this warning, please install libyaml and reinstall your ruby. |
To fix this, install libyaml and reinstall your ruby like the good warning above. On Ubuntu, I installed libyaml-0-2 and libyaml-dev
|
1 |
# apt-get install libyaml-0-2 libyaml-dev |
and reinstalled my ruby as per the following procedure. If you need other specific functionality, install any prerequisites (gdbm, curses, readline, Ruby/Tk, etc.) now, prior to compiling Ruby. For example, I already had libssl1.0.0 and libssl-dev installed, so SSL support was automatically configured and compiled in.
I’ll also install libsqlite3-dev here as Rails depends on it.
|
1 |
# apt-get install libsqlite3-dev |
Ruby Installation
Ruby will be installed from source so the latest features and security patches are included (and, perhaps, the latest bugs). The most recent available stable version at the time of writing is 1.9.3-p385. The tarball has been placed in /usr/local/src - extract it:
|
1 2 3 |
# cd /usr/local/src # tar xzf ruby-1.9.3-p385.tar.gz # cd ruby-1.9.3-p385 |
Next, configure Ruby. I’ll be installing with a --prefix of /usr/local. Once configure has been successfully executed, compile and install Ruby.
|
1 2 3 |
# ./configure --prefix=/usr/local # make # make install |
Ruby is now installed - fire it up interactively and test that it works. You can run Ruby interactively with the irb interpreter:
|
1 2 3 4 5 6 7 8 |
# which irb /usr/local/bin/irb # irb irb(main):001:0> puts "Hello, world!" Hello, world! => nil irb(main):002:0> # ^D |
Good - Ruby is done.
RubyGems Installation
The latest RubyGems download available to me now is 1.8.25. I downloaded to /usr/local/src and extracted:
|
1 2 3 |
# cd /usr/local/src # tar xzf rubygems-1.8.25.tgz # cd rubygems-1.8.25 |
Install RubyGems:
|
1 |
# ruby setup.rb |
Observe any error messages, and note any warnings. Also note the following:
|
1 2 |
RubyGems installed the following executables: /usr/local/bin/gem |
The gem command will be used to install gems, i.e. Rails and Thin in our case.
Gem Installation
Two gems are required: the rails framework and the Thin Ruby web server. Install rails:
|
1 |
# gem install rails |
You’ll see any gem dependencies fulfilled and installed prior to rails itself. The gem command is Ruby’s answer to PHP’s pear and Perl’s cpan.
At this point I decided to check that the Rails installation was successful. I ran into an issue which I’ll describe here, and provide the solution for. First, I created a temporary directory:
|
1 2 |
# mkdir /var/tmp/test # cd /var/tmp/test |
I then created a new Rails application called test1:
|
1 |
# rails new test1 |
When I attempted to start the WEBrick-based Rails server, I received the following error:
|
1 2 3 4 |
# cd test1 # rails server /usr/local/lib/ruby/gems/1.9.1/gems/execjs-1.4.0/lib/execjs/runtimes.rb:51:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable) from /usr/local/lib/ruby/gems/1.9.1/gems/execjs-1.4.0/lib/execjs.rb:5:in `<module:ExecJS>' |
To fix this, I installed the therubyracer gem and its dependencies:
|
1 |
# gem install therubyracer |
Next, I modified my Rails project’s Gemfile and uncommented the following line:
|
1 |
gem 'therubyracer', :platforms => :ruby |
The Rails server now started as expected.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# rails server => Booting WEBrick => Rails 3.2.12 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-02-21 13:20:44] INFO WEBrick 1.3.1 [2013-02-21 13:20:44] INFO ruby 1.9.3 (2013-02-06) [i686-linux] [2013-02-21 13:20:44] INFO WEBrick::HTTPServer#start: pid=3988 port=3000 ^C [2013-02-21 13:20:45] INFO going to shutdown ... [2013-02-21 13:20:45] INFO WEBrick::HTTPServer#start done. Exiting |
Now that’s working, install Thin and its dependencies:
|
1 |
# gem install thin |
Using the same Rails project I just created, I verified the Thin installation as follows:
|
1 2 3 4 5 6 7 |
# thin start >> Using rack adapter >> Thin web server (v1.5.0 codename Knife) >> Maximum connections set to 1024 >> Listening on 0.0.0.0:3000, CTRL+C to stop /usr/local/lib/ruby/gems/1.9.1/gems/thin-1.5.0/lib/thin/backends/tcp_server.rb:16:in `connect': cannot load such file -- thin/connection (LoadError) ... |
Uh-oh! Whoops, I forgot to add thin to my Gemfile. Adding the following line
|
1 |
gem 'thin' |
fixed the issue.
With the gems all installed and tested, you can now cease further Ruby-related tasks as root. Rails projects can be created by any user, in their appropriately-owned directories.
Rails Deployment Preparation
I’ll create a deploy user. I’ll use this user to create Rails applications, or to deploy them to an appropriate location, and to run the Thin instances. I’ll use /var/www/sitename/<railsapp> to serve my applications. For now, create the deploy user with /var/www as their home directory.
|
1 2 3 |
# groupadd -g 8001 deploy # useradd -m -d /var/www -s /bin/bash -g deploy -u 8001 deploy # passwd deploy |
Add the following to /etc/sudoers:
|
1 |
deploy ALL=(ALL:ALL) /usr/local/bin/bundle install |
And set appropriate permissions over /var/www:
|
1 |
# chmod 755 /var/www |
Rails Deployment
Let’s create our base directory - working as the deploy user:
|
1 |
$ mkdir /var/www/www.example.com |
Change directory, and create the Rails application superfoo:
|
1 2 |
$ cd /var/www/www.example.com $ rails new superfoo |
Enter your password at the following prompt (this will use sudo to install the bundle).
|
1 |
Enter your password to install the bundled RubyGems to your system: |
Modify your Gemfile
|
1 2 |
$ cd superfoo $ vi Gemfile |
and ensure the following lines are present:
|
1 2 |
gem 'therubyracer', :platforms => :ruby gem 'thin' |
Start thin in the foreground and ensure that all is well:
|
1 2 3 4 5 |
# thin start >> Using rack adapter >> Thin web server (v1.5.0 codename Knife) >> Maximum connections set to 1024 >> Listening on 0.0.0.0:3000, CTRL+C to stop |
Hit Ctrl-C - now we’ll create our Thin cluster.
NOTE
Using ruby-1.9.3-p448, rubygems-2.0.3 and rails 4.0.0 required a lot more sudo configuration to work correctly. First, a permissions and ownership change:
|
1 2 |
# chmod 775 /usr/local/lib/ruby/gems/1.9.1/build_info # chgrp deploy /usr/local/lib/ruby/gems/1.9.1/build_info |
Then the /etc/sudoers rules in full:
|
1 2 3 4 5 |
deploy ALL=(ALL:ALL) /usr/local/bin/bundle install deploy ALL=(ALL:ALL) /bin/mv /var/www/* /usr/local/lib/ruby/* deploy ALL=(ALL:ALL) /bin/cp -R /var/www/* /usr/local/lib/ruby/* deploy ALL=(ALL:ALL) /bin/cp -R /var/www/* /usr/local/bin deploy ALL=(ALL:ALL) /usr/local/bin/gem * |
With these in place, a rails deployment as the deploy user (using the aforementioned software versions) should work.
Configuring a Thin Cluster
For Production purposes, never run a single Thin instance. Much like Mongrel, we can run up two or three Thin workers to handle our requests and server our Rails applications. The Thin usage documentation is very clear so have a read of that alongside this article.
From within our Rails project directory, I’ll generate a configuration file (superfoo.yaml) by starting Thin in config mode:
|
1 2 |
$ thin config -C superfoo.yml -s 3 -p 8000 >> Wrote configuration to superfoo.yml |
Here, I configure Thin to start 3 servers, starting at port 8000 (so - 800{0,1,2} will be used by our three workers), and writing the configuration out to superfoo.yml.
Now, I can start the Thin cluster using the YAML configuration file:
|
1 2 3 4 |
$ thin start -C superfoo.yml Starting server on 0.0.0.0:8000 ... Starting server on 0.0.0.0:8001 ... Starting server on 0.0.0.0:8002 ... |
And stop it:
|
1 2 3 4 5 6 7 |
$ thin stop -C superfoo.yml Stopping server on 0.0.0.0:8000 ... Sending QUIT signal to process 4874 ... Stopping server on 0.0.0.0:8001 ... Sending QUIT signal to process 4884 ... Stopping server on 0.0.0.0:8002 ... Sending QUIT signal to process 4895 ... |
You should configure your system startup scripts (upstart, init.d scripts, SMF, whatever) to automatically start Thin, and to do so prior to Nginx coming online.
You can check that Thin is correctly serving your application by running:
|
1 2 3 |
$ curl http://localhost:8000 $ curl http://localhost:8001 $ curl http://localhost:8002 |
If you’ve stopped Thin, start it up and leave it running. The final step is to complete our Nginx configuration.
Nginx Configuration
If you recall from the first article, I created the directory /etc/nginx/vhosts.d to hold my virtual host configuration files. An include statement in /etc/nginx/nginx.conf is used to include any *.conf files under this directory. This enables me to disable a virtual host by doing something like
|
1 2 3 |
# cd /etc/nginx/vhosts.d # mv 00_www.example.com.conf 00_www.example.com.conf-NOT # /usr/local/nginx/sbin/nginx -s reload |
and disable a virtual host. It is obvious that it’s just as simple to enable a virtual host.
Nginx is a feature-rich web server, and can be used to serve static content to save Thin from having to do so. Nginx is often used in this way, serving static content and proxying other requests through to application servers (or other web servers such as Thin) for dynamic content to be served.
We don’t have any static content to serve (but could easily configure Nginx with the appropriate location stanzas to serve it directly - see the documentation) so the following is what my /etc/nginx/vhosts.d/00_www.example.com.conf file looks like:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# Define the upstream hosts that we will proxy too. # This is our Thin cluster upstream example_thin { server 127.0.0.1:8000; server 127.0.0.1:8001; server 127.0.0.1:8002; } # Define a server block server { # Listen on port 80 listen 80; # What to look for in the Host: header server_name www.example.com; # Per-virtual host logging access_log /var/log/nginx/www.example.com-access.log; error_log /var/log/nginx/www.example.com-error.log; # Location / - match all requests location / { # Set the following HTTP headers so we can keep track # of the true requestor IP on the backend proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # We're not doing any redirection with URIs returned # from the proxied servers proxy_redirect off; # The important bit - proxy all requests to our # upstream cluster - example_thin proxy_pass http://example_thin; } } |
Again, commented so as to be self-explanatory. Run a configuration test, and if happy, reload Nginx:
|
1 2 3 4 |
# /usr/local/nginx/sbin/nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # /usr/local/nginx/sbin/nginx -s reload |
You should now be able to hit the Ruby on Rails application you created as follows:
|
1 |
$ curl http://www.example.com |
(or if this the only virtual host configured on the server, hit http://localhost). You should see the Ruby on Rails welcome page appear.
Well done - it’s all working!
Conclusion
This two part series has guided you through the installation of Nginx, Ruby, RubyGems, Rails and Thin - all of the various components for a high-performing Ruby on Rails installation on an Amazon EC2 Ubuntu instance.
Various configuration concepts have been introduced, especially around Nginx and virtual hosts.
You can read more about Nginx on the project website.