Using nginx as a load-balancing proxy to a mongrel cluster on Dreamhost

Posted by Trey Mon, 18 Feb 2008 18:27:00 GMT

There’s been a big push recently for Dreamhost to support rails better or for rails to be easier to support. Finally, those efforts are starting to pay off and Dreamhost is beginning to respond.

Now, with a few quick clicks of a button in Dreamhost’s panel (if you are using their Private Server service), you can have your rails app running under mongrel and being proxied through apache.

Not bad. In fact, it’s the easiest rails deployment I’ve seen barring the “we’ll set it up for you” kind. However, there’s still one glaring problem. Note the singularity of the word ‘mongrel’. That’s right, you get one, I repeat, one mongrel for your app. If you wasnt to perform the standard mongrel deployment of setting up a mongrel cluster and balancing the load between all of those mongrels, you’re back to being all on your own. What kind of rails app only needs one mongrel anyway?

I would love to migrate all of my rails apps over to my Dreamhost private server, but first I have to figure out how to balance the load between my many mongrels. Since, Dreamhost doesn’t let you touch your apache config, you can’t do the standard apache mod-proxy to mongrel cluster approach. You need another step. Apache to blank to mongrel clusters.

After investigating Pen, Pound, and Balance, all good options for the right circumstances, I decided I should get my hands dirty with that new server from mother Russia, nginx, that I’ve been hearing so much about.

Since nginx isn’t officially supported on Dreamhost, you’be got to compile and install you’re own. This actually turns out to be a good thing in this case, as there is a patched version of nginx that supports fair balancing instead of the round-robin approach used by the official stable branch.

Installing nginx

First, if you haven’t installed software on you Dreamhost account before, create a src directory in your home folder to store any source code you will download.

$ mkdir src

Now, nginx requires a few libraries to be present in order to work. From the nginx English wiki:

Requirements
  • gzip module requires zlib library
  • rewrite module requires pcre library
  • ssl support requires openssl library

We’ll be using all of these. Luckily, though, Dreamhost already has the openssl and zlib libraries installed. We just need to grab the pcre library. The latest version is 7.6.

Note: this might actually be installed on Dreamhost somewhere, but I was unable to find it.

Change into your source directory and download the library.

$ cd src
$ curl -O http://superb-east.dl.sourceforge.net/sourceforge/pcre/pcre-7.6.tar.bz2
$ tar -xjvf pcre-7.6.tar.bz2

Now, we aren’t actually going to install the library. Instead, we’re just going to tell nginx where the library is when we compile it, so it can grab the necessary chunks and get on with it’s business.

Next, let’s download that patched nginx so we can have fair balancing. If you for some reason don’t want fair balancing, you can get substitute the url below with this one: http://sysoev.ru/nginx/nginx-0.5.35.tar.gz

Still in the ~/src directory:

$ curl -O http://www.12spokes.com/nginx.git-upstream_fair-0.6.tar
$ tar -xvf nginx.git-upstream_fair-0.6.tar 
$ cd nginx.git
$ ./configure --with-pcre=/home/<your username>/src/pcre-7.6/ --prefix=/home/<your username>/local/nginx --with-http_ssl_module
$ make
$ make install

Note: I couldn’t figure out how to curl or wget this tarball from the git repository, so I downloaded it and put it in my directory for now. If anyone can figure out how to get it form the command-line, please let me know in the comments. If you want to get the file straight from the source, the url for the repo is http://git.localdomain.pl/?p=nginx.git;a=tree;h=upstream_fair-0.6;hb=upstream_fair-0.6

Now you have nginx installed on Dreamhost. You just need to set yourself up with a config file and you’ll be ready to start balancing those mongrels (fairly even). My config file is basically a combination of the default config file found in /home//local/nginx/conf and Ezra’s optimized config

Set up your mongrel clusters

If you don’t already, have some mongrels running, set some up:

$ cd /directory/of/your/rails/app
$ mongrel_rails cluster::configure -e production -p 8000 -N 3 -c /directory/of/your/rails/app -a 127.0.0.1

Then fire ‘em up.

$ mongrel_rails cluster:start

If you set everything up correctly, you should now have three mongrels running on ports 8000, 8001, and 8002. You’ll want to wrap this last command into a startup script, so you can feed it to cron and have monit monitor your mongrels, but that’s beyond the scope of this tutorial.

Configureing nginx

Now, in your nginx.conf file set up the cluster

upstream mongrel_yourapp {
    fair;
    server 127.0.0.1:8000;
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
}

Another thing you’ll need to change is the port that nginx listens to. Most of the examples on the web are using nginx to replace apache. Since this isn’t an option on Dreamhost, we’ll need to have nginx running on a different port. I use 8080.

server {
    # port to listen on. Can also be set to an IP:PORT
    listen       8080;

    ...

Starting nginx

Now, test your config file with the -t option and if everything is peachy, fire it up.

$ /home/<your username>/local/nginx/sbin/nginx -t
    2008/02/18 09:52:25 [info] 11891#0: the configuration file /home/<your username>/local/nginx/conf/nginx.conf syntax is ok
    2008/02/18 09:52:25 [info] 11891#0: the configuration file /home/<your username>/local/nginx/conf/nginx.conf was tested successfully
$ /home/<your username>/local/nginx/sbin/nginx

At this point, you have nginx running on port 8080. It then proxies to your mongrels running on port 8000, 8001, and 8002, keeping everything nice and even. We just need to tell Dreamhost to have apache proxy all requests to our application’s domain to nginx instead of trying to serve them itself.

Configure Dreamhost’s Proxy

Go to Domains->Mongrel and Proxy. Under ‘Set Up A Proxy Server Port’, choose the url for your application (leaving the path blank if your app serves the entire domain) and enter 8080, the port for nginx, into the ‘Port Number to Proxy’. Click the ‘Add Proxy Server’ and wait. After the standard Dreamhost 10 minutes to do anything passes, you should be able to go to your main url and see your application served up through all those little proxy tubes we just finished putting in place.

Voila.

The initial tests that I’ve performed shows a slight hit by having to still go through apache, but nothing too terrible. There’s probably a lot of other things we can do to optimize our applications before we get to that one. After I get some more benchmarking done, I’ll post my results.

IE conditional comment helper for Rails 1

Posted by tieg Thu, 05 Apr 2007 00:10:00 GMT

These days, now that we have IE7, it’s common for those of us developing for the web to use conditional comments instead of CSS hacks to make sure our sites look good in different versions of Internet Explorer. (Or to pass anything else exclusively to IE)

I find myself using them on a regular basis, so here’s a little helper you can throw in your ./helpers/application.helper.rb to Rubify some of that commentage:

  # info at http://msdn.microsoft.com/workshop/author/dhtml/overview/ccomment_ovw.asp
  def conditional_comments(options={}, &proc)
    inverse    = options[:inverse] || ''
    comparison = options[:comparison] ? " #{options[:comparison]}" : ''
    version    = options[:version] || 5
    concat("<!--[if#{comparison} #{inverse}IE #{version}]>\n#{yield}\n<![endif]-->", proc.binding)
  end

Just put it in an ERb evaluation block and feed it a block of output:

<% conditional_comments :comparison => 'gte', version => 5.0 do
      stylesheet_link_tag 'layout_ie', 'typography_ie'
   end %>

Output:

<!--[if gte IE 5.0]>
<link href="/stylesheets/layout_ie.css" media="screen" rel="Stylesheet" type="text/css" />
<link href="/stylesheets/typography_ie.css" media="screen" rel="Stylesheet" type="text/css" />
<![endif]-->

It might be a little too much abstraction or processing for your tastes, but I was just a little sick of seeing the comments in every header. Yatta!

Prototype: New Site

Posted by tieg Thu, 18 Jan 2007 23:00:31 GMT

Woo! A new Prototype site with complete docs has been announced! Very nice.

Unit Testing and Politics 1

Posted by Trey Fri, 10 Nov 2006 18:19:00 GMT

Last night, after spending much of yesterday writing unit tests for the Dimewise redesign, combined with the past two days of constant political discussion, I had a quite unusual dream. I remember being elated when I finally got the following test to pass.

assert_equal nancy_pelosi, speaker_of_the_house

Yay!

"The position being taken is not to be mistaken..." 1

Posted by tieg Thu, 26 Oct 2006 19:08:00 GMT

One thing that is new to Edge Rails lately is a <div style="margin:0;padding:0"> tag that wraps your FORM’s HTTP method input (<input type='hidden' name='_method' value='put' />).

The reason for this is because in XHTML the INPUT tag is not a valid child of a FORM element (why? I don’t know), so you have to wrap it in a block-level element. I’m 100% in support of Rails being validation-friendly, but I have one suggestion:

Instead of a DIV tag, would it be better to wrap the INPUT in a tag like <fieldset style='display:none'>? Although the DIV is sort of like the XHTML Everyman, the FIELDSET tag seems more FORM-appropriate. I’d submit a patch for this, but it’s more a preference than an enhancement. Any thoughts?

RubyOSA is the soul of the wit. 1

Posted by tieg Wed, 25 Oct 2006 19:04:00 GMT

One of the most exciting presentations at RubyConf2006 for me was Laurent Sansonetti’s “Leveraging Mac OS X from Ruby”. He’s working for Apple on Ruby-related things, and one thing he mentioned was the integration of Ruby as a framework. Basically you’ll be able to have multiple version of Ruby in your ~/Library/.framework directory (if I understand that correctly?), which might be very useful.

The thing that got everyone excited was his talk about RubyOSA, which will be a bridge between AppleEvents and Ruby (there’s currently something similar called RubyAEOSA, but it’s not very Rubyful and supposedly slow).

RubyOSA is able to open an OS X application and create the Ruby object for it based on the application’s sdef (an XML file).

He has some sample scripts available, but here’s a short script I wrote that I shall call Rapid Rails that you can drop in your ‘Sites’ directory, run ruby rapid_rails.rb and it’ll create a Rails app for you and open it in Textmate. Pretty measly right now (plus you don’t really need RubyOSA for this example), but you could also add some DB stuff to make it ‘rapider’.

  1. require 'rbosa'
  2. begin
  3. puts ""
  4. print "What is the name of the Rails application? "
  5. app_name = $stdin.gets.chomp
  6. end
  7. app_name = app_name.empty? ? 'my_rails_app' : app_name.gsub(/\s/,'_')
  8. `rails #{app_name}`
  9. begin
  10. app = OSA.app_with_name 'textmate'
  11. app.open(app_name)
  12. rescue Exception => e
  13. puts "Rapid App Error: #{e}"
  14. end

I’m over a dog about this RubyOSA!

NOTE FOR YOU: if you have problems with RubyOSA and XML, try downloading the newest LibXML from http://libxml.rubyforge.org/

UPDATE: there’s a script in the ‘sample’ directory of RubyOSA that will generate the sdef file of opened application for you.

New in Edge: Prettier Default Error Pages

Posted by Trey Tue, 10 Oct 2006 20:31:00 GMT

David just checked-in a revision that finally spruces up those default 404 and 500 error pages.

They’ve gone from

blah

Old Error Message

to

New Error Message

It’s not a huge deal, but it definitely adds to the out-of-box professional experience of rails. I know I’ve felt a bit odd when presented with a client saying “it says ‘Application error’”. I know I should have created custom error pages, but all to often that step gets overlooked. Now, even if it’s forgotten, you still look professional.

I’m not sure what that part about the notification is yet, maybe Rails adopting Jamis’ exception notifier?

Quick Rails Tip (Environment Info)

Posted by tieg Mon, 31 Jul 2006 00:41:00 GMT

If you’re switching between applications and can’t remember to which Rails version you’re frozen or on which migration you are, use the Rails Info module:

  1. Rails::Info

And that should output all the important environment info.

Or, like the ‘public/index.html’ file, you can browse to the Rails::InfoController like so:

  1. http://localhost:3000/rails/info/properties

Quick Firefox/CSS tip

Posted by tieg Fri, 09 Jun 2006 20:31:00 GMT

If you ever do something like   * { margin: 0; padding: 0; } in your stylesheets to override default browser styles, you might be curious as to what the actual default browser styles are so you can backtrack and reset some (for example, putting the padding back on OPTION).

For the Firefox default styles:

OS X:

Find the Firefox package (usually in your Applications folder). Right-click on it and choose “Show Package Contents”. Go into “MacOS” and then “res” and you’ll find the motherload (for example, form styles are in forms.css).

WinXP:

Browse to the Firefox directory, which is usually at “C:\Program Files\Mozilla Firefox\”. Go into the “res” directory and voila.

An Unexpected Battle Between Models and Controllers: Namespaces 1

Posted by Trey Sat, 20 May 2006 23:35:00 GMT

I was updating one of our client’s applications from Rails 1.0 to 1.1.2 today. Unfortunately, during the first test spin, I got an ‘Uninitialized Constant BaseController’ error on all of my pages within a module. In this app we have a module that groups a bunch of related controllers. All of these controller then inherit from the BaseController in that folder. For example,

  • controllers
    • one_controller.rb
    • fruit
      • base_controller.rb
      • apple_controller.rb
      • etc.
    • juice_controller.rb
    • etc.
  • helpers
  • etc.

All of the controllers in the module were then defined like

  1. class Fruit::AppleController < Fruit::BaseController

Note: this worked in Rails 1.0

I searched and searched. I couldn’t find anything, in the code or online. Finally, I tried creating another module to see if modules were simply broken in the 1.1 version of rails. Nope, the test worked great. So I knew that the problem had to be with this specific module.

Then it hit me. I also had a module with the same name; in this case – fruit.

Models are defined like

  1. class Fruit < ActiveRecord::Base

And this was the problem; there was a namespace conflict between my model class and my controller class. When rails would try to load my apple controller, it would look for a subclass of my fruit model, not my fruit controller class.

Luckily for me, we had stopped using that particular model and it hadn’t been deleted yet. With a quick

  1. script/destroy model Fruit

our app was back up and running correctly.

Is this how this should be behaving? I guess so, though the script/generate could be nicer by warning us that we have a namespace conflict.

Anyone else have similar problems?

Older posts: 1 2