Roles Attributes: Embracing a Chef Anti-Pattern

There is a fairly large foundation of concepts in Chef that new adopters need to wrap their heads around. And even once they have done that, it doesn’t become any easier to find the right methodology to use when building your infrastructure. One of the main ideas that we have embraced at SimpleReach is pervasive use of roles and role attributes. Using role attributes is considered a Chef anti-pattern. Which begs the question, if this is really a Chef anti-pattern, why are we doing it?

Before I get to the why, let me first explain why using lots of roles and role attributes, especially in the way we do, is considered an anti-pattern. We use one environment and many roles. The problem is that, as long as a role doesn’t apply to a single environment (which it should IMHO, but that’s a separate rant), then you have to use nasty hacks such as making role names like this: prod-web_server, test-web_server, and dev-web_server. And these roles are likely going to be very WET. Since we all like to write re-usable code whenever possible, this is clearly an anti-pattern. Otherwise, you’d have re-usable code.

In a great article on Chef patterns and anti-patterns, Doug Ireton discusses the value of not setting the run_list in a role and setting custom attributes in cookbooks instead of roles. However, that is the option we chose. Many of the reasons not to use these patterns only present themselves when using more than one environment. There is a methodology in the startup world that revolves around doing things that don’t always scale. When you are a small team managing a lot of systems, you need to do whatever gets things done. And in this case, using the anti-pattern to get things done happened to work in our favor.

One of the foundations of our infrastructure at SimpleReach is using a lot of micro-services for the service oriented architecture. So we have a base role that is shared by all the micro-services (an api for example):

name 'api-base'
description 'Base role for api'
run_list(
    'role[base]',
    'role[monitoring]',
    'role[golang]',
    'recipe[sr-deploys::api]'
)

default_attributes(
  tag_list: %w{api},
  app: {
    name: 'api',
    user: 'application',
    group: 'application',
    type: 'golang',
    repo: 'git@github.com:simplereach/api.git',
    root: '/srv/application',
    bin_dir: '/srv/application/api/bin'
  },
  go: {
    packages: %w{github.com/tools/godep}
  }
)

Then each micro-service becomes a simple and relatively DRY role:

name 'api-users'
description 'API Users end point'
run_list(
    'role[api-base]'
)

default_attributes(
  tag_list: %w{api-users},
  app: {
    goapps: [
      {
        name: 'users',
        desc: 'api end point for users',
        family: 'api',
        port: 8001
      }
    ]
  }
)

It’s important to note that all this is being done via the role attributes and the role run_lists. Doing this in the recipe could be just as easily done with a helper method. But it’s cleaner (IMHO) to write the recipe to be generic and process the role attributes to change types. Otherwise the role would simply contain a (short) list of recipes. And if you wanted to change the port or add an environment variable, you would end up having to push a change to the entire cookbook. With the attributes in role, you can make a quick change to the role and just push up the role change. This is even more true if you force cookbook version pinning to be honored in the roles. Then you’d have to push up an updated role with the new cookbook version in addition to pushing the new cookbook version.

The value of role attributes here is that they provide flexibility and enable DRY code essentially using them as inherited objects. And although this is generally considered to be an anti-pattern, it’s value is clear for certain use-cases.

Posted in Chef. Tags: . No Comments »