Wednesday, October 31, 2007

Fun With Apache and Virtual Hosts

Specifically, name based virtual hosts.

I recently tried to add IPv6 support to my web server. I used to have it, I remember having it, so this should not be all that hard.

After an hour of hacking, I ended up finding two gotchas:

  • Make certain, I mean certain, that all virtual hosts for name-based servers have a unique ServerName line.
  • Make certain, and I mean certain, to save your original configuration files.
Yea, I know, I should have known better. But this is a simple thing to change, right?

A very useful tool is apachectl -S, which lists all virtual hosts. Even better is to run that output through sort.

Sunday, October 28, 2007

Mongrel, Apache, and Rails

When I first started running Rails applications on my web server, I chose to use FastCGI. Specifically, the mod_fcgid module, which had some features I wanted. It also has the unfortunate by-product of corrupting Apache's memory. Bad news.

I've since removed FastCGI entirely and moved to a proxy to mongrel_cluster setup. And I've started deploying with Capistrano.

Capistrano

I have a certain amount of concern with moving to a deployment system I knew very little about. Just like a new backup system, I feel like I'm handing the keys to my data over to something not written by me. And, while it is fairly simple to set up, Capistrano is somewhat complicated internally.

I already push out my operating system upgrades in an automated way. I compile NetBSD on one machine here at home, and push the binaries out to all my machines. This means about 7 machines rsync from the build box with one command. This can be scary, but I've been doing it for 5 years now, and it just works. How can a web site be scary compared to kernels and system binaries?

The answer is, it's not. If something breaks it is fairly easy to manually reconfigure if I need to. So, I've relaxed a bit. My concerns are still there, and I'm keeping a careful watch on how Capistrano runs each time I deploy. I have yet to do a *real* deployment after all! So far, I've not done a single migration, and have not had to roll back. And I'm pushing to a single machine, which runs the database as well as the site.

I suspect that, as I become comfortable with this new method to update my web sites, I'll start thinking of it as rsync++. It really is that simple.

mongrel_cluster

Mongrel is a vary amazing little widget. Sure, it's slower than Apache, but that's ok. Mongrel is still far, far faster than restarting Rails for each web hit, and far more reliable than mod_fcgid.

In my configuration, I run each site on ports 10000, 10010, 10020, etc. with up to 3 servers per. This means application #1 is on 10000 through 10002, with room to grow should I need to run more. If I find myself running more than 10 servers for a site it needs a new machine anyway, or more machines. And if that happens, I hope I'll have a budget.

Apache load balancing

This is a new feature in Apache 2.1, and apparently is very reliable with Apache 2.2. This is currently my favorite way to run a web site.

My configuration, which happens to be for this site:

<proxy balancer://blog>
  BalancerMember http://localhost:10010
  BalancerMember http://localhost:10011
  BalancerMember http://localhost:10012
</proxy>
<VirtualHost blog.flame.org:80>
  DocumentRoot /www/blog/flame-blog/current/public
  <directory "/www/blog/flame-blog/current/public">
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    Allow from all
  </directory>

  ProxyRequests off
  <proxy *>
    order deny,allow
    allow from all
  </proxy>

  RewriteEngine on

  # Check for maintenance file. Let apache load it if it exists
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteRule . /system/maintenance.html [L]

  # Rewrite index to check for static
  RewriteRule ^/$ balancer://blog%{REQUEST_URI} [L,P,QSA]

  # Let apache serve static files (send everything via mod_proxy that
  # is *no* static file (!-f)
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
  RewriteRule .* balancer://blog%{REQUEST_URI} [L,P,QSA]
</VirtualHost>

It is important, at least on my host, to use localhost in the balancer destinations. This is due to mongrel suddenly running on IPv6 loopback (::1) rather than the usual IPv4 loopback (127.0.0.1). I don't know why this happened, but the localhost trick makes Apache try both addresses, and whichever works it will use.

This configuration makes Apache serve static content, and sends all other requests off to one of the Mongrel processes.

Saturday, October 27, 2007

Ursae-Lyons

Last weekend my wife and I attended a lovely little event in the Barony of Bjornsborg in the Kingdom of Ansteorra. We had a wonderful time. There was music, a bardic circle, and lots of singing (most of it good!) coming from Cynric's tavern.

The best things to happen there, in my opinion, was that Baron Cynric of Bedwyn was awarded with the Kingdom's highest persona award, Lions of Ansteorra, Devenders of the Dream, and his lady wife Baroness Seraphina Maslowska was awarded with a Pelican! Congratulations to both of you!

RailsMode (not ENV['RAILS_ENV'])

I've been spreading things like this all around my code, where I wanted to do something differently in production vs. development mode. Previously, I'd write something like this:

if ENV['RAILS_ENV'] == 'production'
  # perform magic, in production mode ...
end

While this is pretty simple, it just didn't feel very DRY. So, I decided to use this as a reason to learn about modules and mixins.

I have a generic plug-in in vendor/plugins that I put small bits of code like this. You might as well, but if you don't, you can drop this in a file in lib or in a helper.

module RailsMode
  def railsmode(*list)
    list.map! do |item|
      item.to_s
    end

    if block_given?
      if list.include?(ENV['RAILS_ENV'])
        yield
      end
    else
      return list.include?(ENV['RAILS_ENV'])
    end
  end
end

I also put this line in my environment.rb file:

include RailsMode

This mixes the module into the current class. Doing this in environment.rb makes it available everywhere in rails. Putting that line in a specific file would also work, such as a controller, or a helper.

With this, I can now write:

if railsmode(:production)
  # perform magic, in production mode ...
end

I can also check for multiple modes at once:

if railsmode(:production, :development)
  # perform magic, in production or development ...
end

And of course, who needs an if when I can pass in a block:

railsmode(:production) do
  # perform magic, in production mode ...
end