Managing software deployments of your PHP applications II
This is not (really a part) two of my series, but an Intermezzo (1) between Part I and Part III — because I have no time to finish Part III.
In Part I, I talked about my approach to deploying a website and I offered pear and subversion as solutions to the problem. To briefly elaborate on my subversion part, I want to share the following Capistrano recipe with you.
Capistrano, isn't that Ruby?
Yes it is! Capistrano is a nifty piece of software. Not because it's so super-duper complex, but because it's pretty robust and works really well.
I am sure everyone heard of the "the right tool for the job" theory and this is along those lines. Please don't be afraid of a little Ruby to ease the deployment process. (They don't deserve your fear anyway. ;-))
Installation
On FreeBSD:
cd /usr/ports/devel/rubygem-capistrano && make install clean
On Debilian/Ubuntu:
apt-get install rubygems
gem install capistrano
Go on and create a capfile
in $HOME
, and this is where we'll keep our tasks.
A short introduction
A capfile
(in a nutshell) is a description of tasks to do. Generally, you define a couple roles role :www
and entities (webserver01...
). set :foo, "bar"
are declarations of variables (in PHP: $foo = "bar";
) used inside the tasks. And generally things you may have to adjust when you copy and paste my recipe. The #{foo}
are the same variables used inside a tasks.
The Tasks
Checkout a release
At first I need a task to check out a new release on all servers. If you remember Part 1 of this series, you remember my subversion layout:
/repo/tags/1.0/
/repo/tags/1.1/
/repo/trunk/
...
And this is the task to do the checkout:
desc "Checkout release on the servers"
task :checkout_release, :roles => :www do
set(:version, Capistrano::CLI.ui.ask("Version: "))
run "svn co #{svn_repo}/#{version} #{www_dir}/#{version}"
run "sudo chown -R #{www_user} #{www_user}/#{version}/var/tmp"
run "sudo chown -R #{www_user} #{www_user}/#{version}/var/upload"
end
The set(:version, Capistrano::CLI.ui.ask("Version: "))
is particularly interesting because it allows you to specify a version when you execute the tasks. If you don't want the prompt, just remove it and replace it with set :version, "1.0"
at the beginning. I enjoy the extra bit of flexibility though.
Deploy it
desc "Put a release live, or rollback to a previous release"
task :deploy, :roles => :www do
set(:version, Capistrano::CLI.ui.ask("Version: "))
run "sudo sudo ln -sf #{www_dir}/#{version} #{www_dir}/nginx-root"
run "sudo #{phpfcgid} restart"
run "sudo #{nginx} restart"
end
The above task does the following:
Put it all together!
role :www, "webserver1.example.org", "webserver2.example.org", "webserver3.example.org"
set :svn_repo, "http://example.org/svn/repo/tags"
set :www_dir, "/var/www/example.org"
set :www_user, "www-data"
set :nginx, "/etc/init.d/nginx"
set :phpfcgid, "/etc/init.d/phpfcgid"
desc "Checkout release on the servers"
task :checkout_release, :roles => :www do
set(:version, Capistrano::CLI.ui.ask("Version: "))
run "svn co #{svn_repo}/#{version} #{www_dir}/#{version}"
run "sudo chown -R #{www_user} #{www_user}/#{version}/var/tmp"
run "sudo chown -R #{www_user} #{www_user}/#{version}/var/upload"
end
desc "Put a release live, rollback"
task :deploy, :roles => :www do
set(:version, Capistrano::CLI.ui.ask("Version: "))
run "sudo sudo ln -sf #{www_dir}/#{version} #{www_dir}/nginx-root"
run "sudo #{phpfcgid} restart"
run "sudo #{nginx} restart"
end
desc "Deploy a hotfix to specific tag/version"
task :hotfix, :roles => :www do
set(:version, Capistrano::CLI.ui.ask("Version: "))
run "cd #{www_dir}/#{version} && sudo svn update"
run "sudo #{phpfcgid} restart"
end
Note:, there's a hotfix
task included, but it should be obvious what it does. ;-)
The next step is to put this into your capfile
and then run cap -T
in the same directory to display all available tasks. It should look similar to this:
admin01% cap -T
cap checkout_release # Checkout release on the servers
cap deploy # Put a release live, rollback
cap hotfix # Deploy a hotfix to specific tag/version
cap invoke # Invoke a single command on the remote servers.
cap shell # Begin an interactive Capistrano session.
There are our tasks, checkout_release
, deploy
and hotfix
. invoke
and shell
are included in capistrano, a simple example invoke
would look like the following:
admin01% cap invoke COMMAND="df -h" ROLES=www
...
... the above would show us df -h
from all servers defined in www
.
Execution
Executing tasks is rather simple — I also gave it away with the above:
admin01% cap checkout_release
Refactoring
So, after a while, your capfile will probably grow beyond two or three tasks and each task will even include duplicate code, so here some Ruby so you can avoid that:
def hello (txt='world')
p txt
puts "Hello #{txt}"
run "/some/command --switch #{txt}"
end
def
and end
make a function in Ruby, simple as that. The rest of it should be obvious to any beginner PHP coder. The paramter is txt
, it is optional and therefor has a default value. p
in Ruby is similar to PHP's var_dump()
. The rest is self explanatory!
Chaining
If you're a little familiar with jQuery, you probably love it's chaining capabilities. An example would be:
$('#foo').load('/some/file').show('slow');
In a nutshell, this loads /some/file
into the container (<div id="foo" />
) and displays the container when it's done — I'm assuming here, it was hidden.
Chaining in Capistrano is a little different, but not less clever.
Example
The naming is somewhat fail (maybe, epic), but anyway: after
to the rescue!
set :foo, "/tmp/foo"
desc "Create a file!"
task :create_foo, :roles => :server do
run "touch #{foo}"
end
desc "Make foo world writable!"
task :make_foo_writable, :roles => :server do
run "chmod 777 #{foo}"
end
after "create_foo" do
make_foo_writable
end
after
makes sure that after the create_foo
task ran, the make_foo_writable
task is executed! So simple too, but it took me a while and a bit of Google to extract a meaningful example from a mailing list.
Conclusion?
I'm loving it! I'm currently in the process of turning our deployment into true auto-deployment with as little zero intervention in order to make use of RightScale's nifty autoscaling setup. More on this in another post.
For further reading I'd suggest Capistrano's Getting Started, The Absolute Moron's Guide to Capistrano and this Ruby Tutorial with Code examples. Also, special thanks to my co-worker Lukas Rieder for some initial Ruby foo and general Capistrano pointers! :-)
Trackbacks
The author does not allow comments to this entry
Comments