While we still make use of Puppet, we’ve also added Chef to our repertoire. Similar to Puppet, Chef allows for a client/server model in which a Chef server stores and hosts a series of definitions, called recipes, which are packaged as cookbooks. While this works really well, it doesn’t always make sense for us to setup a full Chef Server — particularly if we’re just trying to manage a single server running within a customer’s existing ecosystem.
Fortunately, we can run Chef in a standalone mode referred to as Chef Solo. To facilitate the execution of Chef Solo and get the necessary Chef cookbooks and recipes in place, we utilize Capistrano. (It really is a magnificent utility, even if poorly documented.)
The general pattern is:
- Bootstrap Chef Solo via Capistrano.
- Upload Chef cookbooks and recipes via Capistrano.
- Run Chef Solo via Capistrano.
To maintain configuration code, we create a separate git repository for each set of Chef Solo instances that we run. If we happen to be using Chef Solo to manage more than one server (such as a database and application server as a single instance), we make use of Capistrano Multistage to handle running Chef Solo on the individual servers. All cookbooks, recipes, Capistrano configuration files, etc. are stored in this repository.
Depending on the situation, we may package the Chef cookbooks and recipes in the same repository as the application for which Chef manages the servers. This keeps all application, deployment, and configuration information centralized.
For a generic Rails application, our source control repository may look something like this (emphasis on directories used by Chef and Capistrano):
Bootstrapping Chef Solo
The first piece of this pattern is the Chef Solo bootstrap, which actually installs Chef on the target server. The primary tool used to manage Chef in a client/server environment is knife. Knife provides a convenient way to bootstrap Chef clients to connect to a Chef server. We can make use of the same approach to setup Chef Solo, but provide a custom bootstrap template:
We invoke knife via Capistrano and call for bootstrapping with our custom template. We pass in the SSH user and server IP based on variables set in our stage. Knife will then SSH to the target server as the given user, and install Chef:
After bootstrapping, we can use the Chef Solo pattern to configure and manage our servers individually.
When we actually run Chef Solo, we need to upload the Chef cookbooks and roles to the remote server. We use Capistrano to do this more easily. We then use Capistrano to invoke Chef, specifically using the
.json file corresponding to the stage we have configured for the target server:
Running Chef Solo
solo.rb file provides the configuration information to allow Chef to run solo. It points chef to the location of the cookbooks and roles on the local file system (as opposed to on the Chef Server where they would normally be hosted).
*.json files define what roles (or recipes) actually get applied to each server. For example,
server_app.json specifies that the apache2 role should be applied, and
server_db.json specifies that the mysql role should be applied.
(Note that we are just telling Chef that it should apply the mysql role to the server, which is defined in
When we run Chef Solo via Capistrano, we specify which
.json file to use depending on the server we wish to operate upon. While perhaps not its original intention, we use Capistrano Multistage to accomplish this.
Here is an example stage:
You can find a skeleton template depicting this pattern on GitHub: