
In Developing Rails Applications on Mac
OS X Leopard, we created a web application using the latest Rails features
with Xcode 3.0. In Customizing Rails
Applications on Mac OS X Leopard, we learned how to customize views,
create web forms, and add AJAX and iPhone support. In this third article,
we'll finish up by deploying the Rails application on Mac OS X Leopard Server.
Traditionally deployment has been painful because it involves getting all the
required software installed, configuring various moving parts, remembering to
copy the right files at the right time, and so on. Leopard Server changes all
that. In addition to Ruby and Rails, Leopard Server comes pre-installed with
everything we need to deploy and run a production Rails application: Apache
2.2, mod_proxy_balancer, MySQL, Mongrel, Capistrano, and a few other unique
goodies. Indeed, Leopard Server raises the bar when it comes to ease of Rails
deployment.
In a short amount of time we've built a functional expense-tracking
application. Developing on Leopard gave us a big productivity boost with its
built-in support for Ruby and Rails. Deploying our Rails application should be
no different. So now we're going to transition over to Leopard Server for
another boost.
Updating Ruby Gems
The first thing we need to do is update the necessary Ruby gems to their
latest versions. Log in to your production server running Leopard Server
and type these commands:
sudo gem update --system
sudo gem install rails
sudo gem update rake
sudo gem update capistrano
sudo gem update mongrel
In addition to updating the RubyGems system, Rails, and Rake as we did on our
development machine, these commands also update two additional gems we'll need
for deployment. Capistrano is an automated deployment utility and Mongrel is a
popular web server for running Rails applications. Running all these commands
may take a while. When you've finished, double-check that you're using the
latest version of Rails (2.0.2 for this article) by typing
rails -v
Setting Up the Production Database
We developed this application using a SQLite3 database. In production,
we'll switch over to a MySQL database. MySQL 5.0.45 is pre-installed on Leopard Server, and configuring our application to use MySQL in production couldn't be easier.
By default, all Rails applications have three runtime environments:
development, test, and production. When our application runs in the
production environment, it changes its personality: it runs
faster, logging is less verbose, users see friendlier error pages, and so on.
Equally important, in production the application uses the database that's
configured in the production section of the
config/database.yml file.
On your development machine, update the production section of the
config/database.yml file inside your Rails application directory to use MySQL as follows:
production:
adapter: mysql
database: expenses_production
username: root
password: your-mysql-root-password_here
socket: /var/mysql/mysql.sock
Then, back on the server, open the Server Admin app and start the MySQL
Service, if it's not already running. Next, open a Terminal and create the
expenses_production database by typing
mysqladmin -u root -p create expenses_production
Now, before we run our application in production we'll need to make sure the
schema in the expenses_production MySQL database matches the
schema in our SQLite3 development database. Here's where database migrations
really shine.
Remember, our migration files in the db/migrate directory
consist of database-agnostic Ruby code that represents our schema. That means
we can run the migrations against any database supported by Rails. We just
need to make sure to apply all the migrations to our production database when
we deploy the application. We'll take care of that a little later.
Putting the Code Under Version Control
Before we go any further, let's get our application code safely tucked away in
a Subversion version control repository. Mac OS X 10.5 Leopard ships with
Subversion 1.4.4 pre-installed, so it's easy to start getting the benefits of
version control early.
First, we need to create a Subversion repository. We'll put it in the
/Users/Shared/svnrepo directory on our server, but it can live
anywhere. We just need it to be accessible from our production server. To
create the repository, log in to your server and type
svnadmin create /Users/Shared/svnrepo
Next, we need to import our Rails application into the repository. Back on
your development machine, make sure you're in the expenses
directory containing the Rails application. Then, substituting in your server name, type
svn import -m 'Initial import' . \
svn+ssh://your-server-name/Users/Shared/svnrepo/expenses
At this point, we have a copy of our Rails application in the Subversion
repository on the server. That means we can check out a copy of the
application onto any machine that has secure shell (SSH) access to our server.
To do that on your development machine, for example, you'd type
svn co svn+ssh://your-server-name/Users/Shared/svnrepo/expenses
We'll use the version of our application in the Subversion repository as
the "gold master" that gets deployed to our production server.
Creating a Deployment Recipe
Deploying our application to the production server is something we plan to do
often. As new features are developed and bugs are fixed, we'll want to
re-deploy the application without a lot of fuss. We don't want to have to log
in and out of servers, copy files around, and run a checklist of tasks
manually every time we deploy the application. Instead, we want to automate
the deployment process so that it's consistent and repeatable.
Leopard includes the Capistrano automated deployment utility which makes
deploying Rails applications a breeze. All the deployment ingredients and steps are neatly spelled out in a deployment recipe file. Every time you want to deploy a new version of your Rails application, you simply type one command.
Back on your development machine, change directory to the expenses directory containing the Rails application
and type
capify .
This command creates a template Capistrano recipe in the config/deploy.rb file. Clear out the file and add the following sections incrementally as we go.
First, Capistrano needs the deployment ingredients. Add the following variables and settings in the config/deploy.rb recipe file:
set :application, "expenses"
set :repository, "svn+ssh://your-server-name/Users/Shared/svnrepo/#{application}"
set :deploy_to, "/Library/WebServer/#{application}"
set :deploy_via, :export
set :scm_username, "your-subversion-username"
set :scm_password, "your-subversion-password"
ssh_options[:forward_agent] = true
The application variable is just an arbitrary name for our application. The repository variable is where the
master copy of our application source code lives. In this case, Capistrano will use the
svn+ssh protocol to access the Subversion repository we set up previously. (Capistrano supports other version control systems, as well.) The deploy_to variable is the name of the directory on the production server where the application will get deployed. You'll need to set the scm_username and scm_password variables for your environment. The last line
may be important depending on how your SSH keys are set up. Basically, it lets
the SSH connection to the Subversion repository use your SSH agent, if one is
running, so you don't have to enter passwords repeatedly.
With this part of the recipe complete, Capistrano knows how to check out the Rails application from our Subversion repository and deploy it into the
/Library/WebServer directory on the production server. The one missing ingredient is the name (or IP address) of the production server.
Add the following to your recipe file, substituting your server name or IP address:
role :app, "your-server-name.com"
role :web, "your-server-name.com"
role :db, "your-server-name.com", :primary => true
A role is simply a named group of servers that Capistrano uses to specialize
the behavior of certain deployment tasks. In this case, we're deploying to a
single server and the role distinction doesn't matter. However, roles become
important when you start to scale up with multiple servers, as we'll see later.
That takes care of the deployment ingredients. Next, Capistrano needs to know
the deployment steps, or tasks. It has a number of default tasks
including start, stop, and restart.
We'll define our own versions of those tasks to start, stop, and restart our
Rails application.
We'll be running our Rails application using Mongrel, which is a
web server pre-installed on Leopard for running Rails applications.
Mongrel has a mongrel_rails command for starting and stopping
Mongrel processes. To streamline deployment, Leopard Server includes an
enhanced version of the mongrel_rails command called
mongrel_rails_persist. It creates a launchd plist
file to run a Mongrel process persistently across reboots. It also registers
the Mongrel process with Bonjour so that clients looking for
httpd services can find the Mongrel process.
Add the following Mongrel-specific variables to the recipe file:
set :mongrel_cmd, "/usr/bin/mongrel_rails_persist"
set :mongrel_ports, 3000..3003
set :user, "administrator"
set :group, "admin"
These aren't special variables; we've just defined them here to keep the rest
of the recipe tidy. The first two variables define the full path to the mongrel_rails_persist command and a range of ports (3000-3003) for each Mongrel process. In this case, we'll be starting four Mongrel processes. We'll use the user and group variables simply to set the user and group ownership for each process.
Next, add the following Capistrano tasks to your recipe file:
namespace :deploy do
desc "Start Mongrels processes and add them to launchd."
task :start, :roles => :app do
mongrel_ports.each do |port|
sudo "#{mongrel_cmd} start -p #{port} -e production \
--user #{user} --group #{group} -c #{current_path}"
end
end
desc "Stop Mongrels processes and remove them from launchd."
task :stop, :roles => :app do
mongrel_ports.each do |port|
sudo "#{mongrel_cmd} stop -p #{port}"
end
end
desc "Restart Mongrel processes"
task :restart, :roles => :app do
stop
start
end
end
The start and stop tasks loop through each port
number (3000-3003). Each time through the loop, the sudo method
is used to run the mongrel_rails_persist command, which starts or
stops a Mongrel process on the appropriate port. Notice that the start task starts our Rails application in the production runtime environment using the -e option.
It's important to note that the sudo method runs the
mongrel_rails_persist command on the production server(s)
listed in the app role via the sudo command.
Capistrano uses the secure shell (SSH) to execute these command remotely. You
can pass an :as option to the sudo method to specify
who the command is executed as. Alternatively, you can use the
run method to run the command on the production server without
sudo privileges.
The restart task simply runs the
start and stop tasks in succession.
Deploying for the First Time
Capistrano assumes we'll want our application deployed to a consistent directory structure on all the production machines. It's the directory we specified in the deploy_to variable, which in this case
is /Library/WebServer/expenses. We haven't created that directory yet. Rather than logging into our server and creating the directory manually, from this point forward we'll use Capistrano to run tasks remotely for us.
On your development machine, type
cap deploy:setup
In the output you'll see Capistrano log in to your production server using SSH
and create the following directory structure:
/Library/WebServer/expenses/
/Library/WebServer/expenses/releases
/Library/WebServer/expenses/shared
/Library/WebServer/expenses/shared/log
/Library/WebServer/expenses/shared/system
/Library/WebServer/expenses/shared/pids
Now we're ready to deploy our Rails application for the first time. This is
somewhat of a special case. Typically our application will already be running
on the production server, and we'll just redeploy it. The first time we
deploy, however, the application isn't running. Moreover, we haven't applied
the database migrations to our production database.
For a first-time deployment, run a special "cold" deploy task by
typing
cap deploy:cold
In the output you'll see that this task logs in to your production server via
SSH, checks out the application code from Subversion into a new directory
under the /Library/WebServer/expenses/releases directory, and
then creates a /Library/WebServer/expenses/current symlink that
points to the new release. This current symlink is used as the
root directory of the currently-active release.
The task then runs all the migrations so that our production database schema
matches our development database schema. Finally it runs the
start task we wrote earlier to fire up all four Mongrel processes
for the first time.
At this point your application is running on the production server. Assuming
you're logged in to your production server, point Safari at http://127.0.0.1:3000/events and you
should see the expenses application.
Configuring the Web Server
We're able to access our application by talking directly to a Mongrel process
via a port. Now we need to configure the Web Service to transparently proxy
all incoming requests to our pack of Mongrel processes.
Follow these steps to configure the Web Service on Leopard Server:
-
Open the Web Service. On the production server, open the Server
Admin app and connect to the server. Then click the triangle to the left
of the server. From the list of services, select Web.
-
Configure the Web Service. Click Sites and select the website in
the list. Then click Proxy below the websites list. Select the Enable
Reverse Proxy checkbox and verify that the Proxy Path field is set to
"/". This will proxy all URLs within the website to the
balancer members.
-
Add Balancer Members. Each balancer member corresponds to a
Mongrel process, running on either the local host or other hosts. Click
the Add (+) button below the Balancer Members list. In the Server URL
pop-up menu, you should see four Mongrel processes listed with unique
URLs as shown in Figure 14.
Figure 14:
Leopard Server Admin: Adding Balancer Members
These are the Mongrel processes that were started when we cold deployed
the application. Server Admin was able to find the processes because the
mongrel_rails_persist command registered them with Bonjour.
Select a balancer member and set its Load Factor to 25 to distribute the
load equally among balancer members. Leave the Route field blank unless
you have a specific reason to enter a value.
Repeat this step until all four balancer members have been added. Figure
15 shows the final configuration.
Figure 15:
Leopard Server Admin: Configuration for a dedicated website
-
Save the Configuration. Click Save and then click Start Web
Service, if it's not already running.
-
Confirm Access. Point Safari at http://127.0.0.1/events to confirm
that the URL is proxied to the Rails application.
Using this configuration, our website (virtual host) is dedicated to the Rails
application. Alternatively, we can arrange things so that the website is
shared with the Rails application. Say, for example, we want the Rails
application to be mounted under the /expenses URI.
To do that, simply change the mongrel_rails_persist command in
the start task of the Capistrano recipe to include a
--prefix /expenses option.
sudo "#{mongrel_cmd} start -p #{port} -e production \
--prefix /expenses --user #{user} --group #{group} -c #{current_path}"
Then, in the Proxy Path field of the Web Service configuration, enter the
prefix with both a leading and trailing slash. In our example this would be
/expenses/. When you add Balance Members, the Server URLs
shown in the pop-up menu will include /expenses as shown in
Figure 16.
Figure 16:
Leopard Server Admin: Configuration for a shared website
Then to access the shared application, point Safari at http://127.0.0.1/expenses/events.
Deploying New Releases
After the first deployment, things get even easier. When you have a new release you want to deploy, first make sure all your code is checked in to the Subversion repository. Then, on your development machine, redeploy by typing
cap deploy
That command checks the latest version of your Rails application out into a
timestamped subdirectory of the releases directory, flips the
current symlink to the new release, and restarts all the Mongrel
processes so that they pick up the new code.
If there's a problem with the new release, you can easily roll back to the previous release by typing
cap deploy:rollback
Rolling back is painless (and fast!) because it just updates the
current symlink to point to the previous release and restarts all the Mongrel processes.
Scaling Up
In this example we deployed to a single Leopard Server. As your application
becomes more popular, eventually you may want to start building a cluster of
Leopard Servers, each with their own speciality. Capistrano makes it very easy
to add new servers to your deployment process.
First, simply tell Capistrano about each server, and what role each plays.
Here's an example:
role :app, "your-app1-server.com", "your-app2-server.com", "your-app3-server.com"
role :web, "your-web1-server.com", "your-web2-server.com"
role :db, "your-db-server.com", :primary => true
Next, set up each new server by typing
cap deploy:setup
Then deploy code as usual by typing
cap deploy
When you run a Capistrano task such as deploy or
deploy:rollback, it's run in parallel (and atomically) on all
machines that are assigned to the app role in the Capistrano
recipe file. Think about that: As the number of machines and processes in your
deployment environment varies, your deployment procedure remains constant and consistent. It's just one command.
Conclusion
Ruby on Rails is a highly-productive web application framework. It scales from
the simplest expense tracking application to full-featured applications with
respectable numbers of users. Leopard is there to help every step of the way.
You can get a useful Rails application up and running quickly on your
PowerBook, then seamlessly deploy it to a production-ready Xserve (or cluster
of Xserves) running Leopard Server.
For More Information
This article has introduced you to Ruby on Rails on Leopard. The following references
will help you dig deeper:
Posted: 2008-03-28
|