Use Capistrano to disable_web On Your PHP Apps

Just like using Capistrano to deploy my Rails apps made my PHP apps jealous, being able to disable the entire app with a single command did too.

Using a simple cap disable_web command allows my Rails apps to be completely disabled while I perform maintenance on the site without having to worry about users accessing it. Being able to suspend the site with a single command is wonderful, trust me.

Well I want to do this with my PHP apps too. Here's how I did it.

First create a generic page that you want your users to see when your site is disabled and name it maintenance.html. Then throw it into your /config directory.

Cap Tasks

Next, write a disable_web task for Capistrano.

desc "This will disable the application and show a warning screen"
task :disable_web do
  run "cp #{current_path}/config/maintenance.html #{current_path}/maintenance.html"
end

This simple one liner will copy the maintenance.html file into the root of your application. Now lets write a task to re-enable the application.

desc "This will enable the application and remove the warning screen"
task :enable_web do
  run "rm #{current_path}/maintenance.html"
end

This will simply delete the maintenance file.

Ok, so, basically, when you run cap disable_web, Capistrano will move your maintenance file into the root of your app, and enable_web will delete it. So far this does nothing to disable your PHP application.

mod_rewrite Magic

The trick to this is using mod_rewrite to look for your maintenance file. If it's at the root of your application, then all requests will be directed to it. Otherwise the application works as normal.

RewriteEngine On

# Check for maintenance file and redirect all requests
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*(\.html|\.php)$ /maintenance.html [L]

This will look for any requests of .html and .php files. All requests that fit that condition will pointed to the maintenance.html page if it exists at the root of the application.

Huzzah! You've now got a "we're fixin' stuff" page. And you can go from this:

To this:

Popularity: 11% [?]

    Categories: Deployment, PHP     1 Comment »

Mongrel_Processes Updated To 0.6

Ok, well it never had a version number before, but I thought I'd add one now.

So What's New?

As requested, it has a way to fix the fact that PID's exist if the server fails. Typically, if the server fails, and you reboot, the PID's are still there and the script won't start the clusters (because it see's the PIDs and thinks the cluster is already started). So it bit me in the butt enough that I finally fixed it.

Now, this won't happen on boot, you'll have to do a force_restart. But I'm going to take a leap and expect that if a server hangs, and you have to manually reboot it, you'll be there to do a force_restart on your apps to insure they're working.

I didn't want to do this on the start task because it wasn't as graceful. Since this is a 20% thing and not an 80% thing, I put it in force_restart.

Also, since the start task is ran on boot, this isn't going to fix itself on boot. That's why you'll have to do a force_restart on it. If anyone knows more about that, let me know. I'd love to get it going on boot, without putting it in the start task.

Grab it here with SVN or just a copy and paste.

Popularity: 9% [?]

    Categories: Deployment     6 Comments »

Start and Stop All Your Mongrel_Cluster Processes

This has been updated. You can find the update here

So, Its possible to use mongrel cluster in a shared environment. I've shown that. But what about stopping and starting all these processes? No one wants to cap restart in every source directory after a server gets rebooted. And what if I need to shut them all down at once!? I can't be ghetto and sudo killall mongrel_rails, I've got self respect.

So to solve my little problem, I took the kick in the butt from Peter Cooper and finally created an init script for all the mongrel_clusters on a shared box.

The beauty of this is that when I add a new app to the server, I don't have to tell the script. Using conventions, it knows what rails apps I have installed.

Basically, when you call start, it iterates through the APP_DIR directory. When it finds a rails app with mongrel_cluster.conf in its config directory, it starts it, but not before it verifies that there isn't a pid under /log/. If it finds a pid, it just reports back to you that the app has already been started.

Calling stop does the inverse, with the same protections. Restart just does a stop then a start. If you've got a stuck process or some other wackyness going on, force_restart will skip past the checks and force restart your rails apps.

Huzzah! Another virtue of using conventions.

Grab the script here: svn export http://code.jtoe.net/rails/scripts/mongrel_processes.rb

Put it anywhere, chmod it, and symlink it to init.d

svn export http://code.jtoe.net/rails/scripts/mongrel_processes.rb
chmod 755 mongrel_processes.rb
sudo ln -s /home/dev/bin/mongrel_processes.rb /etc/init.d/mongrel_processes

If you're on (ubuntu|debian), add it to your start up processes like this:

sudo update-rc.d -f mongrel_processes defaults

And you're off...err automatically on...just reboot and try it out.

Popularity: 33% [?]

    Categories: Deployment, Rails     21 Comments »

Clustered Mongrel In A Shared Environment

Working at a marketing firm, a lot of our client work doesn't end up as mission critical, high bandwidth sites. We end up pushing out projects that are either corporate sites requiring content control or content management apps that are used by a small subset of their employees.

Because of this, we have always deployed sites in a controlled, shared environment. We have our own boxes that our client work sits on. As we deploy more sites powered by Ruby and Rails, I've been on a mission for a nice deployment stack.

Since RailsConf, I've come to a satisfying decision of Apache 2.2, mod_proxy_balancer, and Mongrel. My next task was to get this stack working with multiple Rails apps in a shared environment. Luckily, getting this working is very trivial.

If you've read Coda's motivating post on getting this stack up and running, then some of this will be familiar to you. I'd suggest reading this first. My tips just build off of it.

Getting multiple Rails apps working in a shared environment is basically all up to Apache. Because your sites will be driven individually by Mongrels on separate ports, Apache proxies to them and vhosts become your friend.

mongrel.common

Like Coda, I kept my configuration DRY by creating a single file called mongrel.commmon. This contains all the configuration that every Rails app will need when being proxied from Apache. There's no need to repeat this stuff in every vhost.


RewriteEngine On

# Check for maintenance file and redirect all requests
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
RewriteRule ^.*$ /system/maintenance.html [L]

# Rewrite index to check for static
RewriteRule ^/$ /index.html [QSA]

# Rewrite to check for Rails cached page
RewriteRule ^([^.]+)$ $1.html [QSA]

# Deflate
AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch bMSIE !no-gzip !gzip-only-text/html

First, we have Apache keep a look out for our maintenance.html file which is created by our nice disable_web Capistrano task. If its found, all traffic coming through Apache will be routed to it.

Next, we're using mod_rewrite to make sure Apache serves out our index files and cached pages. Having Apache serve our cached static files rather than our Mongrels, speeds everything up quite a bit.

The last thing we're telling Apache to do is to compress our static files to speed up and lower our outbound bandwidth. Everything gets decompressed on the clients browser. More on mod_deflate here.

site.vhost

The other file we need to get our new domain set up on our shared server is the domain's specific vhost file. Everything in this file will be contextual specifically to the domain.


<VirtualHost 62.44.22.11:80>
Include conf/mongrel.common

ServerName domain.com
ServerAlias www.domain.com
DocumentRoot /home/dev/apps/domain.com/current/public


ErrorLog logs/domain.com_errors_log
CustomLog logs/domain.com_log combined

<Directory "/home/dev/apps/domain.com/current/public">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>

<Proxy balancer://domain.com_cluster>
BalancerMember http://127.0.0.1:8000
BalancerMember http://127.0.0.1:8001
</Proxy>

#Redirect all non-static requests to cluster
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ balancer://domain.com_cluster%{REQUEST_URI} [P,QSA,L]


</VirtualHost>

First off, add the ip address that your vhost will sit on listening for traffic. This will usually be the ip address of your server, and most importantly the ip that your domain resolves to. People familiar with shared hosting will recognize this. In addition, make sure you add...


NameVirtualHost 62.44.22.11:80

This allows you to use name based virtual hosting with Apache.

The next thing we do is include our nice abstracted configuration, mongrel.common. Then we add the typical things an Apache vhost file would have, ServerName, alias, logs, etc. We make sure we point the DocumentRoot at the public folder of our sites so Apache can serve our static files correctly.

Then we define our Mongrel cluster. Define a name for our cluster, and tell Apache where our Mongrels are running (these are defined in your config/mongrel_cluster.yml). Next, tell Apache which cluster you want your traffic proxied to via mod_rewrite. See the named cluster?

And that's it. Include the vhost file via your httpd.conf Apache conf file, and your new domain is ready to go. All you have to do to set up another site on this same server is (after doing the normal Rails portion setup. You ARE using Capistrno right?) to create a new vhost file and restart Apache.

I've probably used bit more words than needed to go over this, but I just wanted to show that its easy to use this great deployment stack in a shared environment.

Popularity: 24% [?]

    Categories: Deployment, Rails     6 Comments »

Automated PHP Deployment With Capistrano

Sure, I'd like to spend my days and nights writing Ruby and cap deploy ing it to success, but I have to face the facts.

I Still Have PHP Apps To Maintain.

I have no one to blame but myself. I wrote them. And I still work where they were written. There's no escape. After using Capistrano more than one time, you'll be completely spoiled.

But Capistrano is only for crazy Rails apps right? Nope. Its simply a platform for automation. It just so happens that deployment usually requires quite a bit of steps. Hmmm...lots of steps...automation... a match made in heaven. So sure, Capistrano was thought up with Rails in mind, but created openly so you're able to use it any way you'd like.

Capistrano's power is in its remoting features. You tell it what to do on that other server, and it does it. Beauty. So, if your PHP deployment process requires an svn export combined with a few other repetitive steps you have to do every time, Capistrano is here to help you too.

Without going into all the details of Capistrano (following the link will give you a nice overview), it uses a deploy script (recipe) to understand your deployment environment and required tasks.

Ok ok ok, Lets Get On With It

So, we have a PHP app that we need to deploy to server X. It needs to be sucked out of its repository, its version symlinked to a doc_root, and then a shared directory symlinked into the site. This is a pretty common deployment scenario, so lets go with this.

First, we need a Capistrano recipe. Currently, it can only be auto generated for a Rails app, so psst, here it is. Download this and create a directory at the root of your PHP app and name it config.


mkdir config
cd config
wget http://simplisticcomplexity.com/assets/2006/9/21/deploy.rb
 

Capistrano wants its recipe to be in the config directory, so do as it likes. Now lets open it up and make it work with us. Once again, a complete description of the Capistrano recipe file is bit out of scope for this little post, so please read up.

We'll edit all the normal items

set :application, "php_app"
set :repository, "http://code.phpdevelopers.com/#{application}/trunk"

We only deploy to one server, so edit...

role :web, "phpsite.com"

Now you need to add the directory you want to deploy to. Add..

set :deploy_to, "/home/www/#{application}"

Capistrano defaults to checkout when pulling from subversion. I prefer an export, if you do too, then add..

set :checkout, "export"

Ok, so if you're familiar with Capistrano, you know exactly what we did. If you're not, the most important thing to know is that Capistrano is going to suck your code from the repository defined, do everything it's told to do in /home/www/php_app on your deployment server, phpsite.com.

Set'r Up

Believe it or not, we're half way done. Next, lets let Capistrano get the directory we just defined ready for its magic.


cap setup

If you look on your deployment server, you should see 2 directories that were just created.


drwxrwxr-x    2 user  group      4096 Aug 16 01:48 releases
drwxr-sr-x    4 user  group      4096 Aug 16 01:48 shared

WOW! We're already saving time! releases is where your app will be sucked down to. Capistrano exports your PHP app into a directory named after the current date/time. Each version you deploy will be kept in this directory. Shared is where you can keep files like uploaded assets or images that aren't kept in your repository, but need to be around every time you export from subversion. If your users upload files all day long, and you redeploy your PHP app from subversion, all your uploaded files will be gone. So we keep those in here.

Here's the deployment rub. Every time you deploy, you have to go symlink these directories from your /shared directory, into your current version of your PHP app. And a small bit of math can calculate the complexity when you start adding 2,3, or 6 asset folders. Yuck.

That's where Capistrano helps ya out.

Capistrano, Take Me Away

Ok, so our deploy directory is set up and ready to get our files. Now we need to tell Capistrano exactly what to do when we deploy. Get it? Recipe...

Define a new task called deploy

desc "This will deploy the app"
task :deploy do
 
end

Inside the block we'll just tell Capistrano exactly what it needs to do in order to deploy our app.

desc "This will deploy the app"
task :deploy do
  run "svn --quiet #{checkout} #{repository} #{release_path}"
  run "ln -nfs #{release_path} #{current_path}"
  run "ln -nfs #{shared_path}/photos #{current_path}/photos"
end

Ok, lets walk through exactly what this does. First, Capistrano uses subversion to suck your app out of the repository and into the releases directory. That releases_path is just a nice convenient variable for /home/www/php_site/releases/200608162012. Next, it will create a current symlink to that version of your app in the releases directory. Then we told it to symlink the directory photos from shared into the current version of the application.

And since you're hosting a PHP app with Apache (a 99% guess), there's no need to restart any servers. You're done. You're new version will be deployed safely and linked. The IMPORTANT thing to note, is that your new doc_root for your site will be /home/www/php_app/current.

Deploy!

Now, all ya gotta do to redeploy your site is...


cap deploy

Capistrano will go on to do everything you told it to do, on your deployment server. When its done, your directory should look like this...


lrwxrwxrwx   1 user group   42 2006-08-16 01:32 current -> /home/www/php_site_/releases/200608162012
drwxrwxr-x   4 user group 4096 2006-08-16 01:31 releases
drwxr-xr-x   4 user group 4096 2006-06-24 02:08 shared

And there you have it. Automated PHP deployment, with one simple command. Peering inside the current directory, you'll see where the photos directory has been symlinked into it.

The moral is, Capistrano isn't just for Rails deployment. Its powerful remote execution can be exploited to do anything you want. While our Rails apps get a lot for free when it comes to recipes, creating your own isn't complicated. Especially if its replacing repetitive tasks you've done over and over and over and over and over again.

You'll see the payoff the minute you get this set up. Especially on a Friday night at 5:30p when you're walking out the door. And its not just for Rails!

Popularity: 70% [?]

    Categories: Deployment, PHP     24 Comments »