Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 1st Edition (2005).pdf
Скачиваний:
28
Добавлен:
17.08.2013
Размер:
7.99 Mб
Скачать

A TRINITY OF ENVIRONMENTS 448

to that shortly), that it should boot 15 servers initially (a good starting number for a dedicated server), and that we want the timeout to be 60 seconds instead of the default 30.

This timeout is a critical value. If any request takes longer than the limit allows, Apache will assume that FastCGI crashed and return an error 500 (and possibly kill the process). You may need to push the timeout even higher, depending on your application. This is especially important if your application talks to remote servers and even more so if it needs to transfer large amounts of data to them.

With FastCGI both installed and configured, you’ll just need to change your public/.htaccess file6 to referencedispatch.fcgi instead of dispatch.cgi, restart your server, and hit Refresh in your browser. If all went well, you’ll pay the start-up price of initialization, and then all subsequent requests should be riding the FastCGI lightning.

If all didn’t go well, you’ll have three log files to investigate. First is the Apache error log, which is configured either in your vhost or in the master httpd.conf. This is normally where you’ll find errors about mod_fastcgi being misconfigured (pointing to the wrong dispatcher file, for example). Next is fastcgi.crash.log, which is located in your application log/ folder. This might contain a trace of problems that occur after the Dispatcher had been found and triggered. Finally, there’s the regular Rails production log, which may contain errors from within your application. Configuration problems show up in the first two of these logs, and application problems in the third.

22.2 A Trinity of Environments

Rails has three different environments: development, test, and production. Throughout the book, we’ve been using the default development environment, which reloads the application on every request and makes sure none of the caching mechanisms is active. In the testing chapter, we used the test environment that, for example, ensures that the Action Mailer simulates sending e-mail, rather than actually delivering it.

When we deploy our Rails applications, we use the production environment, where ease of development is traded for speed. As can be seen in config/environments/production.rb, the most important change from development to production is the change of Dependencies.mechanism from :load

6If you want to squeeze the last drop of performance out of Apache, you could make these configuration changes in the server’s main configuration file (often httpd.conf) instead.

Prepared exclusively for Rida Al Barazi

Report erratum

A TRINITY OF ENVIRONMENTS 449

to :require. This ensures that once a model, controller, or other class has been loaded, Rails won’t load it again. In the development environment it is convenient to have these files reloaded, as it means that Rails will pick up changes we make. In production we trade that convenience for speed: there’s no overhead of recompiling on each request, but changes in the application’s source files won’t be honored until the server is restarted.

Rails distinguishes requests that come from local—friendly—hosts from those that don’t. If a failure occurs while handling a request from a local host, Rails displays a wealth of debugging information on the browser as an aid to the developer. In the development environment, Rails assumes that all requests are local. In production, this assumption is disabled; any request coming from outside the local host will no longer see the debugging screen on error. Instead, they’ll see the generic public/500.html page. We’ll return to the implications of this in Section 22.3, Iterating in the Wild, on the following page.

Caching is enabled in production environments. This means that things such as caches_page, the sweepers, and the rest of the caching infrastructure will actually start performing their duties. In development, the parameter ActionController::Base.perform_caching is set to false, and they simply have no effect.

Switching to the Production Environment

You need to tell Rails to use the production environment in order to enjoy the speed and caching it supports. The trick is that you would rather not make any changes to your application in order to do so since that would require a different code base for production and development. For quick tests of changing environments, you could hack config/environment.rb and force the constant RAILS_ENV to be something other than "development", but that’s messy.

That’s why the Rails environment is also changeable through an external environment variable, also called RAILS_ENV. If the environment variable is set, Rails uses its value to define the environment. If RAILS_ENV isn’t set, Rails defaults to "development". To run your application in the production environment, you have to make sure that ENV[’RAILS_ENV’] is set to "production" before Ruby compiles environment.rb. This is easier said than done.

The problem is that the three different web servers each have a unique way of setting environment variables.

Prepared exclusively for Rida Al Barazi

Report erratum

ITERATING IN THE WILD 450

WEBrick:

./script/server --environment=production

Apache/CGI:

In the vhost configuration in httpd.conf, or in the local .htaccess file, set

SetEnv RAILS_ENV production

Apache/FastCGI:

In httpd.conf, add the following option to the FastCgiServer definition.

-initial-env RAILS_ENV=production

lighttpd/FastCGI:

In the fastcgi.server definition file, set

"bin-environment" => ("RAILS_ENV" => "production")

See the Rails README for a longer example.

To change the environment when using scripts such as the Rails runner, you can use a shell assignment, such as

myapp> RAILS_ENV=production ./script/runner 'puts Account.size'

22.3 Iterating in the Wild

Now that your application is being served through FastCGI in the production environment, how do you keep moving forward? Deploying the application is just the beginning of life outside the lab. You need to be able to react to errors and update the codebase to fix these errors (or add features). You also need to be able to diagnose problems when things go wrong.

Handling Errors

In development, everyone sees the debugging screen when something goes wrong. Presenting the end user with a stacktrace when they encounter a problem isn’t particularly friendly, though. So in the production environment, you get a debugging screen by default only when operating from localhost. While that protects the user from being exposed to the system internals, it does the same for the developer trying to debug a problem on the production server, which is not really what we want either.

Luckily, that’s easy to remedy. Action Controller provides a protected method called local_request?( ), which it uses to determine if a request is coming from a local host. In production, this by default returns true if the

Prepared exclusively for Rida Al Barazi

Report erratum

ITERATING IN THE WILD 451

request is coming from 127.0.0.1. You can change this to check against a certain session value tied to your authentication scheme or you could just expand the range of IPs to include the public IPs of your developers.

def local_request?

["127.0.0.1", "88.88.888.101", "77.77.777.102"].include?(request.remote_ip) end

Although this method can be overwritten on a per-controller basis, normally you’ll redefine it just once in ApplicationController (the file application.rb in app/controllers) to share the same definition local across all controllers.

How do you know if a user saw an error and that an investigation is required? You could search the logs every night, but you’d probably forget every now and then, leaving potentially critical errors unsolved for hours or days. It would be better to be notified the minute an exception is thrown and then decide whether it’s something that needs immediate attention or not. E-mail is great for this.

Action Controller has yet another hook that makes adding e-mail notifications on exceptions easy. The method rescue_action_in_public( ) in ActionController::Base is called whenever an exception is raised. This method can be defined in individual controllers, or you can make it global by putting it in application.rb. It’s passed the exception as a parameter. We could override it to send an e-mail to the application maintainer.

def rescue_action_in_public(exception) case exception

when ActiveRecord::RecordNotFound, ActionController::UnknownAction render(:file => "#{RAILS_ROOT}/public/404.html",

:status => "404 Not Found")

else

render(:file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error")

SystemNotifier.deliver_exception_notification( self, request, exception)

end end

In this example, we treat missing records and actions as 404 errors that need not be reported through e-mail. If the exception is anything else, the developers should know about it. SystemNotifier is an Action Mailer class; its exception_notification( ) method packages the exception and the environment in which it occured in a pretty e-mail that goes to the developers. A sample implementation of the notifier and the corresponding view is shown starting on page 511.

Prepared exclusively for Rida Al Barazi

Report erratum

ITERATING IN THE WILD 452

Pushing Changes

After running in production for a while, you find a bug in your application. The fix needs to get applied post haste. The problem is that you can’t take the application offline while doing so—you need to hot-deploy the fix. One way of doing this uses the power of symbolic links.

The trick is to make the application directory used by your web server a symbolic link (symlink). Install your application files somewhere else and have the symlink point to that location. When it comes time to make a new release live, check out the application into a new directory and change the symlink to point there. Restart, and you’re running the latest version. If you need to back out of a bad release, all you need to do is change the symlink back to the previous version, and all is well.

With symlinks, you can set up a structure where a revision of your code base that’s ready to be pushed live goes through the following steps.

1.Check out the latest version of the codebase into a directory labelled after the version, such as releases/rel25.

2.Delete the old current releases/rel24 symlink, and create a symlink to the new release: current releases/rel25. This is shown in Figure 22.2, on the following page.

3.Restart the web server and stand-alone FastCGI servers.

The situation is slightly more complicated if you also have to include changes to the database schema. In this case you’ll need to stop the application while you update the schema. If you don’t, you might end up with the old application using the new schema.

1.Check out the latest version of the code.

2.Stop the application. If you’ll be down for a while, redirect all requests to a simple Pardon our Dust page.

3.Run any database migration scripts or other post-checkout activities (such as clearing caches) that the new version might require.

4.Move the symlink to the new code.

5.Restart the web server and stand-alone FastCGI servers.

The last step, restarting stand-alone FastCGI servers, deserves a little more detail. We need to ensure that we don’t interrupt any requests when making the switch. If we simply killed and restarted server processes, we could lose a request that was in the middle of being processed. This would

Prepared exclusively for Rida Al Barazi

Report erratum

ITERATING IN THE WILD 453

 

releases/

 

 

www/

 

rel23/

rel24/

rel25/

public/

cgi-bin/

logs/

 

 

symlink

 

 

 

 

releases/

 

 

www/

 

rel23/

rel24/

rel25/

public/

cgi-bin/

logs/

 

 

symlink

 

 

 

Figure 22.2: Using a Symlink to Switch Versions

inconvenience our users and potentially cost us money (it could have been a payment transaction that we discarded). Apache features the graceful way of restarting softly by allowing all current requests to finish before bouncing the server. The FastCGI dispatcher in Rails has an identical option. On Unix systems, instead of sending the regular KILL or HUP signal to the processes, send them a SIGUSR1 signal. Rails will then allow the current request to finish before doing the bounce.

dave> killall -USR1 dispatch.fcgi

This approach takes a bit of preparation—you have to set up the deployment scripts, directories, and symlinks—but it’s more than worth it. The whole idea of Rails is to deliver working software faster. If you’re able to push changes only every second Sunday between 4:00 and 4:30 a.m., you’re not really taking advantage of that capability.

Using the Console to Look at a Live Application

Sometimes the cause of a problem resides not in the application code but rather in some bad data. The standard approach of solving data problems is to dive straight into the database and start writing queries and updates by hand. That’s hard work. Happily, it’s unnecessary in Rails.

You’ve already created a wonderful set of model classes to represent the domain. These were intended to be used by your application’s controllers.

Prepared exclusively for Rida Al Barazi

Report erratum