Hire Us

Capistrano delivery via Patch

Today we’ll talk about lightweight deployment method for your applications via Capistrano.

Preface

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.

Rollback

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.

Capistrano Patch

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.

Patching example

$ 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:
   [patcher@scm.railsware.com] 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

Conclusion

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)

References