Quickshiftin - Clever Crazy Code

Fabric for Application Deployments Revisited

February 14th, 2014

Months ago I was just getting my feet wet with Fabric and Python. Python is a pleasant language, I’m not sure how much I’ll do with it, but I’m happy to have ramped up on the basics. Fabric is nice too. Some of my original thoughts about designing my Python/Fabric setup have changed a bit over the past few months to reflect lessons from practical experience.

Over-designing

Originally I wanted to encapsulate my Fabric configuration inside an object model. Basically I wanted as little procedural code as possible. I rationalized this by wanting to associate additional metadata about my servers beside the data points Fabric provides to enumerate servers and group them into roles.

The problem was when I wanted to move from simple tasks to more complex meta-tasks, the system I had began to break down. Using the @roles decorator wasn’t meshing well with the backend class I had and my lack of Python knowledge made it even worse. So I decided to simplify my life. I moved the configuration out of the AppDeploy class and put it at the top of my fabfile.

The result is dead simple code that’s expressive and easy to follow. I still have the backend AppDeploy class, and a set of various other classes to implement behind-the-scenes logic, the only major change really is where the configuration lives. There’s some other bells and whistles I’ll show you too!

New World Order

Without further ado let’s take a look at my new setup. First thing you’ll notice is the role configuration atop fabfile.py. You’ll also notice there are no more hideous configure_env calls in every task. This contributes to a much leaner AppDeploy.py which we’ll see in a minute.
You can see now I’m using the @roles decorator to specify which systems the tasks run over. Very clean to implement and easy to understand. Take a look too at the specialized service tasks which leverage shared code from AppDeploy.py and fabfile.py. You’ll see calls to execute which facilitates so called meta-tasks. Big ups to Brandon Whaley from the fab-users mailing list for the tip-off on that one! execute will honor the @roles decorator on the task from which it’s called when it invokes sub-tasks. Another great and simple way to reuse code across logical sets of systems.
fabfile.py

from fabric.api import *

# Define the fabric roles
env.roledefs = {
    'dev'  : ['dev01.mycorp.com', 'dev02.mycorp.com' ],
    'prod' : ['prod01.mycorp.com', 'prod02.mycorp.com' ],
    }

# Instantiate the backend class which
# no longer has configuration logic
deployer = AppDeploy()

def __service_jetty(action):
    deployer.service(deployer.jettyService, action)

## Development tasks ========================================

# Install app1 on dev
@task
@roles('dev')
def deploy_app1_dev(version):
    deployer.installJavaApp('app1', version)

# Install app2 on dev
@task
@roles('dev')
def deploy_app2_dev(version):
    deployer.installJavaApp('app2', version)

# Start Jetty in dev
@task
@roles('dev')
def service_jetty_dev_start():
    execute(__service_jetty, 'start')

# Stop Jetty in dev
@task
@roles('dev')
def service_jetty_dev_stop():
    execute(__service_jetty, 'stop')

## Production tasks ========================================

# Install app1 on prod
@task
@roles('prod')
def deploy_app1_prod(version):
    deployer.installJavaApp('app1', version)

# Install app2 on prod
@task
@roles('prod')
def deploy_app2_prod(version):
    deployer.installJavaApp('app2', version)

# Start Jetty in prod
@task
@roles('prod')
def service_jetty_prod_start():
    execute(__service_jetty, 'start')

# Stop Jetty in prod
@task
@roles('prod')
def service_jetty_prod_stop():
    execute(__service_jetty, 'stop')

I think you’ll agree the new fabfile is looking rather tidy compared to my old one. What’s more the code is more expressive and more compact than before. That’s a win-win! Now let’s take a look at the revised AppDeploy class. Right away you’ll notice the configuration voodoo is all gone and that makes for a much tighter class. The logic here could easily go in the fabfile, but again, I like classes! In reality I have a more complex set of classes behind my fabfile, but this should give you a view into the paradigm.

AppDeploy.py

from fabric.api import *

class AppDeploy:
    #------------------------------------------------------------
    # Red Hat services
    #------------------------------------------------------------
    jettyService = 'jetty'

    #------------------------------------------------------------
    # Run a specified action on an arbitrary service e.g.
    # start, stop, restart, status etc.
    #------------------------------------------------------------
    def service(self, name, action):
        run('sudo service ' + name + ' ' + action)

    #--------------------------------------------------------------
    # Install an arbitrary version of a package (if it's available)
    #--------------------------------------------------------------
    def __yumInstallPkgVersion(self, pkgName, pkgVersion):
        run('sudo yum remove -y ' + pkgName)
        run('sudo yum install -y ' + pkgName + ' ' + pkgVersion)

    #------------------------------------------------------------
    # Install a Java app deployed on Jetty
    #------------------------------------------------------------
    def installJavaApp(self, pkgName, pkgVersion):
        self.service(self.jettyService, 'stop')
        self.__yumInstallPkgVersion(pkgName, pkgVersion)
        self.service(self.jettyService, 'start')

In Retrospect

Overall I think Fabric is solid. If you want to step outside the defacto way of doing things, it’s probably best if you have a decent grasp on Python. If not, stick with the samples for your paradigm. Also, I’ve found the fab-users mailing list very helpful.
In general I’ve been getting deeper and deeper into BASH lately, and find myself wondering why use Ruby, Python or anything else for an SSH-based multi-server utility. I think one day I’ll try my hand at a simple BASH system with similar goals to those of Capistrano and Fabric. Maybe a rainy day though, I’ve got a lot of things going on right now and Fabric is working just fine!

Share

If you enjoyed this post please consider sharing it!

  • Twitter
  • LinkedIn
  • Facebook
  • Reddit
  • Digg

2 Responses to Fabric for Application Deployments Revisited

  1. jeski says:

    why not use ansible?

    • Nathan Nobbe says:

      Thanks for reading! Have a look at this page. Generally Fabric is a lot simpler of a deployment technology, while Ansible encompasses much more.

      I’d say if you’re using Ansible to manage your infrastructure configuration, the use it for deployment as well. In my case I was using Puppet to configure our infrastructure so Fabric was a good fit.

Leave a Response