Counting bytes and measuring latencies

Chef Pattern 1 :: Modeling environment specific differences

At work we have notion of multiple environments. Dev  (used by the developers), Staging(used for showcasing stories) , QA/Testing (used by qas) and Production (caters to end user). At time Pre-production environment  is also present to simulate production like scenarios. Though we fight hard to minimize the differences between these environments , they do exists and it is necessary to measure them, which in turn can provide us some automated mechanism to easily spot environment specific difference. And in this write up I'll be writing some of the techniques I'm using to model them. There are two generic goal or aim for this techniques:

  • Testability of the recipe.
  • Ease with which one can spot the environment specific differences

The techniques:

  1. Conditionals:  Use raw if else conditions two apply different resources in different environments. If you are sure that the difference will be only limited to certain minute details I start employing raw conditional  statements like this:
    if node.chef_environment == 'development' do
      #
      # do not configure basic auth
      #
    else
      #
      #  configure basic auth
      #
    end
    What is important is that we should also writ equivalent unit tests for such scenarios.
  2. Attributes: Use attributes to model the differences  In chef attributes can be overridden on per environment basis. This provides an elegant way to implement environment specific changes. In following example version of ruby  needs to be different in two environment.
    # the default attribute looks like
    default[:ruby][:version] = '1.8.7' 
    
    #
    # the resource inside ruby recipe might use it like this
    #
    ruby_version= 'ruby-'+ node[:ruby][:version]
    
    execute "download_ruby" do 
      cwd "/opt"
      command "wget -c http://someurl/#{ruby_version}.tgz"
    end
    
    #
    #
    Then this can be overridden in all servers within staging environment with this
    name "staging"
    override_attributes("ruby"=>{"version"=>'1.9.3'})
  3. Environment specific chef recipes/components: Use environment specific recipes and other components on the fly If the amount of differences is large enough involving multiple resources and significant configuration differences within a single file you can use the environment name itself to dynamically load an environment specific template or  environment specific sub recipe. For example the deploy recipe invokes data base backup as well as load balancer rotation (the instance should be out of load balancer during the deployment) tasks only in production not in any other environments. For this we can have a dedicated recipe name deploy::production . On the other hand the main deploy recipe  uses something like this
    pre_deployment_sub_recipe = "deploy::#{node.chef_environment}_pre_deployment"
    include_recipe sub_recipe
    #
    # Perform deployment
    #
    post_deployment_sub_recipe = "deploy::#{node.chef_environment}_post_deployment"
    You can use similar strategies to include environment specific configurations also. e.g
    template "/etc/nginx/nginx.conf" do
      source "nginx." + node.chef_environment + "conf.erb"
    end
    This one will pick up nginx.staging.conf.erb as template in staging environment's nginx configuration while nginx.production.conf.erb in production environments. This does impose a problem that every environment now should have a corresponding sub recipe or template. But again you can further scope this using one of the earlier two technique. In this case also , it is possible to do a file glob and pick up the environment specific differences.