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
- 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
Create, deliver, check and apply patch!It is clear that the patching procedure should contain the following four steps:
Step 1. CreateSave 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. DeliverDeliver the patch file to each application server.
Step 3. CheckBefore 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. ApplyThis 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.
RollbackIf 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.
Capistrano PatchTo 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-patchThere are 4 simple recipes:
cap patch:create # Create patch cap patch:deliver # Deliver patch cap patch:apply # Apply patch cap patch:revert # Revert patchAnd one aggregated recipe:
cap patch # Create, deliver and apply patchPatch 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 examplesWe assume you use Git for your project as the SCM and capistrano as the deployment tool.
Applying patch flowCreate 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.2or just use simple aggregated recipe:
$ cap patch FROM=v0.0.1 TO=v0.0.2It performs ‘create,’ ‘deliver,’ ‘check,’ and ‘apply,’ of previously described steps. On each step you’ll be asked to confirm the operation.
Reverting patch flowYou 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.2The 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
ConclusionUse 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)