Chef:Powerful Infrastructure Automation
上QQ阅读APP看书,第一时间看更新

Extending Chef

Chef is developed in the open with flexibility and extensibility in mind. Most of the tools are architected to support loading custom plugins to support development of add-ons for new functionality. As you saw earlier, Knife's cloud service support is provided by plugins, one for each cloud service, including EC2, Azure, and Rackspace cloud. We will look at how that happens so that you can explore writing your own plugins for Knife, Ohai, and other Chef components should the need arise.

In addition to extending Chef's core components directly, it is possible to extend the functionality of your Chef ecosystem by building enhancements to existing tools that can leverage Chef's data APIs to provide data about your infrastructure.

Writing an Ohai plugin

Ohai (a play on the phrase oh, hi!) is a tool that is used to detect attributes on a node, and provide these attributes to the chef-client at the start of every chef-client run. Without Ohai, the chef-client will not function, and therefore, it must be present on a node in order for Chef to work. The data that is collected is authoritative—it has the highest level of precedence when computing attribute data for client runs on a node.

The types of attributes that Ohai might be used for include:

  • Platform details
  • Network usage
  • Memory usage
  • Processor usage
  • Kernel data
  • Host names
  • Information about the network topology
  • Cloud-specific information

Ohai implements an extensible architecture through plugins that allows end users to write custom extensions to report information that is collected about a node. For example, there are plugins for EC2 cloud hosts that use EC2-specific mechanisms to determine information about the host, including its internal IP address and other bits of information.

This is incredibly useful for integrating Chef with an existing infrastructure, as you can automatically probe for local configuration data and generate attributes from that data. Once this data is stored in Chef, it can be used in search queries, recipes, and everywhere else that you could otherwise use node attributes.

Every Ohai plugin follows a pattern: it registers itself as providing a certain class of attribute data and contains a combination of general purpose and data collection methods to gather information about the local state of the system and report it back to the Chef server. Ohai already has built-in collectors for a number of platforms, including Linux, Windows, and BSD.

A sample Ohai plugin would look like the following:

Ohai.plugin(:Region) do   
  provides "region" 
  
  def init   
    region Mash.new  
  end    
  
  collect_data(:default) do     
    # Runs on all hosts whose platform is not specifically handled
    init   
    region[:name] = "unknown"
  end    

  collect_data(:linux, :freebsd) do
    # Run only on Linux and FreeBSD hosts
    init
    region[:name] = discover_region_unix
  end  

  collect_data(:windows) do 
    # Run on Windows hosts
    init
    region[:public_ip] = discover_region_windows
  end

end

Here we register our plugin as providing a region data, there is an init method that creates a Mash for our region data, and a few data collection callbacks. Each data collection callback is registered as a block that is called for the specified platform(s). In our case, there are three callbacks registered, one for Windows systems, one for Linux and FreeBSD systems, and then a fallback that will be called for any platform not explicitly handled.

A note about writing Ohai plugins

The way we declare the region Mash in our init method is a little bit different than normal variable assignment in Ruby. In our plugin, we define the plugin's region property with the following code:

region Mash.new

On the surface, this might look like someone forgot an equals sign, as in the following Ruby code:

region = Mash.new

However, if there were an equals sign, the plugin would not work as intended. In this case, there is no equals sign missing and the code is correct. This is because Ohai's Plugin class leverages a special Ruby mechanism for intercepting calls to nonexistent methods and dynamically handling them. This mechanism is recognizable by the presence of a special method named method_missing in the code. In this case, the method_missing handler will call a special Ohai plugin method, get_attribute, if no arguments are passed, or it will call the set_attribute method if arguments are passed.

To demonstrate why this is used, if you wanted to have the same effect without the method_missing mechanism, then the plugin's init method could be written as:

def init
  set_attribute "region", Mash.new
end

Were you to do this, then our subsequent collect data methods would need to be rewritten as well. Here is an example of what they might look like:

collect_data(:platform) do 
  init
  region = get_attribute "region"
  region[:name] = get_data_mechanism
  set_attribute "region", region
end

You can see that the method_missing mechanism makes writing plugins more natural, though it takes a little bit of extra work to understand how to write them at first.

Chef with Capistrano

One example of extending Chef through external tools is the capistrano-chef gem that extends the popular deployment tool, Capistrano. Written in Ruby, Capistrano was designed to deploy applications and perform light systems administration. If you have existing applications that are being deployed using Capistrano, this is an example of how to leverage your configuration data stored in Chef to make integration as seamless as possible.

If you have an existing application that uses Capistrano, you will have a deploy.rb file that defines the various application roles. Each tool has an array of IP addresses to hosts that provide that role and might look something like this:

role :web, '10.0.0.2', '10.0.0.3' 
role :db, '10.0.0.2', :primary => true 

By using capistrano-chef, you can do this:

require 'capistrano/chef' 
chef_role :web,'roles:web' 
chef_role :db, 'roles:database_master', 
                   :primary   => true,
                   :attribute => :private_ip,
                   :limit     => 1 

Notice that here we have used a simple search query to determine the hosts that should be included in each Capistrano role. In this case, the :web role has been replaced with a Chef search query for all nodes that have the web Chef role associated with them. This allows you to model your data in Chef, but still use Capistrano to deploy your application stack, increasing the ease of integration.

This works because all of Chef's data is available via an HTTP API making integration as simple as making HTTP calls and parsing some JSON results (which, compared to some other integration mechanisms, is incredibly easy).