Today we’ll talk about lightweight deployment method for your applications via Capistrano.
Standard capistrano deployment via ‘cap deploy’ is quite sufficient, but the standard deployment isn’t very fast. Each time, you have to do following:
- check out the code
- create symbolic links
- run your custom tasks
- switch to a new release
- reload your back-end (passenger, unicorn, etc) processes
- reload your background (daemons, resque-workers, etc) processes
- and run deployment notifications
On top of that, real projects are big and often have several megabytes of code. Sometimes, you need to quickly apply a ‘hotfix’ for a critical bug. Usually, a fix like that is small enough (containing only several lines of code) and affects only a part of the application. If so. it’s not necessary to restart the whole application – you need to restart only part of the running processes. In the end, you don’t need to go through the entire deployment process.
The simple, but NOT RECOMMENDED, solution is to go to each server, manually change bad code, and then restart the back-end processes. It’s a bad idea for many reasons:
- you must change the same code N times where N is number of servers
- it is painful to switch back to original code
- it’s a boring process and so there’s always the opportunity for mistakes because of the human factors
But then, how can you deliver a ‘hotfix’ in the right way? Try another approach called, capistrano patch.
Create, deliver, check and apply patch!
It is clear that the patching procedure should contain the following four steps:
Step 1. Create
Save the differences between the original version and the ‘hotfix’ version in a patch file. In the UNIX world there are two well-known tools called diff and patch. The first one can generate the patch while the second one can apply it.
Step 2. Deliver
Deliver the patch file to each application server.
Step 3. Check
Before the actual patching run, first launch the patch in a dry run mode. That means pretending to apply and checking if the patch can be applied. Then, when something is wrong with the code, one server patching procedure stops. Thus, you will NOT have application servers with different code.
Step 4. Apply
This operation performs the actual patch applying, so your application code will be changed.
IMPORTANT: now you should restart the appropriate Ruby-processes to see the changes.
If your ‘hotfix’ doesn’t help, you are able to quickly revert the patch because the patch file is already on the server.
Then, you can just go to the server and apply the same patch in the reverse mode.
To automate this process, we created set of handy Capistrano recipes in a form of Ruby gem.
Just install the capistrano-patch gem:
$ gem install capistrano-patch
There are 4 simple recipes:
cap patch:create # Create patch cap patch:deliver # Deliver patch cap patch:apply # Apply patch cap patch:revert # Revert patch
And one aggregated recipe:
cap patch # Create, deliver and apply patch
Patch arguments are environment variables:
- FROM – means revision/tag/branch from which the patch begins
- TO – means revision/tag/branch to which patch ends
$ cap patch:create FROM=v0.1.1 TO=css-fix $ cap patch:create FROM=master TO=f7d23233cd6b95edca4ba0c512b9cba3626d7d87
Patching flow examples
We assume you use Git for your project as the SCM and capistrano as the deployment tool.
Applying patch flow
Create a branch and fix the code there. Test the code afterwards. Merge ‘hotfix’ into master/stable branch when you are ready to deploy. Set a new tag. Now you can deploy via patch. Run:
$ cap patch:create FROM=v0.0.1 TO=v0.0.2 $ cap patch:deliver FROM=v0.0.1 TO=v0.0.2 $ cap patch:apply FROM=v0.0.1 TO=v0.0.2
or just use simple aggregated recipe:
$ cap patch FROM=v0.0.1 TO=v0.0.2
It performs ‘create,’ ‘deliver,’ ‘check,’ and ‘apply,’ of previously described steps. On each step you’ll be asked to confirm the operation.
Reverting patch flow
You can revert changes if something goes wrong:
$ cap patch:create FROM=v0.0.1 TO=v0.0.2 $ cap patch:deliver FROM=v0.0.1 TO=v0.0.2 $ cap patch:revert FROM=v0.0.1 TO=v0.0.2
though it’s enough to simply run the next recipe, since the previously created patch file is already on the server and you don’t need to create and deliver it again:
$ cap patch:revert FROM=v0.0.1 TO=v0.0.2
The recipe will check the patch file before reverting and then will revert all changes. Again, you yourself are responsible for corresponding back-end process restarting.
$ bundle exec cap banana patch FROM=v7.0 TO=v8.0 * 15:59:35 == Currently executing `banana' triggering start callbacks for `patch' * 15:59:35 == Currently executing `multistage:ensure' * 15:59:35 == Currently executing `patch' * 15:59:35 == Currently executing `patch:create' executing locally: "git ls-remote git://scm.railsware.com/banana.git v7.0" executing locally: "git ls-remote git://scm.railsware.com/banana.git v8.0" * executing "mkdir -p /var/www/patches/banana && git diff-tree --binary 61076f6745a7948c6ee522d7a299c354c2f74c08..e196d16de53c455c2f842e379effcb5774734170 > /var/www/patches/banana/61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch" servers: ["scm.railsware.com"] Password: [firstname.lastname@example.org] executing command command finished Patch location: http://scm.railsware.com/patches/banana/61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch Is created patch ok? (y/n) y * 15:59:50 == Currently executing `patch:deliver' * executing "cd /var/apps/banana/current && wget -N -q http://scm.railsware.com/patches/banana/61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch" servers: ["stage.railsware.com"] [stage.railsware.com] executing command command finished * 15:59:53 == Currently executing `patch:apply' Apply 61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch ? (y/n) y * executing "cd /var/apps/banana/current && git apply --binary --check 61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch" servers: ["stage.railsware.com"] [stage.railsware.com] executing command command finished * executing "cd /var/apps/banana/current && git apply --binary 61076f6745a7948c6ee522d7a299c354c2f74c08-e196d16de53c455c2f842e379effcb5774734170.patch" servers: ["stage.railsware.com"] [stage.railsware.com] executing command command finished * executing "echo e196d16de53c455c2f842e379effcb5774734170 > /var/apps/banana/current/REVISION" servers: ["stage.railsware.com"] [stage.railsware.com] executing command command finished
Use your normal deployment for feature delivery, and the capistrano-patch recipe when you need a ‘hotfix.’ The patch recipe can be handy if:
- you need quickly to deliver fix
- the fix is very trivial (e.g. tiny fix in Rail’s view)
- the fix affects not running code (e.g spec file)