A destructible and repeatable chef workstation environment / by Matt Wrock

Several months ago I posted on the CentyryLink Cloud blog about how we reproduce our chef development environment with Vagrant using the chef solo provisioner that consumes the same cookbook as our build agents to create an environment for testing cookbooks. This post will dive into the technical details of such a Vagrantfile/cookbook and kicks it up a notch by publicly sharing a repo that is just that but stripped of any CenturyLink specific references like our Nexus server, Berkshelf API client setup or internal gem installs. Here I will share a chef_workstation repo that anyone can use to quickly spin up an environment that's awesome for developing and testing cookbooks. This repo can be found here in my github chef_workstation repo.

Why a vagrant box for chef development?

Many use vagrant for creating test nodes that converge cookbooks but it is not as common to hear about using vagrant for creating chef workstations/development environments. The fact is that ChefDK goes a long way to solve an isolated pre-built chef environment. However, every environment is different even if many use the ChefDK as a starting point. Its not uncommon to be changing or creating gems, adding tools, and tweaking rake tasks and such before you want to either "capture" these changes to be easily shared or blow these changes away and start fresh from a known, working base.

We have found that being able to easily reproduce shared dev environments has added a ton of value to our workflow.  So lets dive in and explore what exactly this repo provides.

Whats in the box?

I've tried to make sure the current readme describes the details of this box, what it contains, and how to use it. So please feel free to review that. Here is a high level list of what the chef_workstation repo provides:

  • chefdk
  • docker
  • nano text editor
  • git
  • squid proxy cache
  • SSH agent forwarding
  • generated knife.rb
  • Rakefile with tasks for cookbook testing
  • Support for VirtualBox, Parallels, Hyper-V and VMWare
  • Secret energizing ingredient guaranteed to infuze your soul with the spirit of automation!!

The repo contains a Vagrantfile at the root that builds an Ubuntu 12.04 image and sets up all of the above. I've been updating this repo in preparation for this post and I can say there is a fair amount that goes into getting this setup just right. Some of it seems trivial to me today but when I was just getting used to chef and linux a year ago, some of this seemed very mysterious then.

We use vagrant to develop but not for testing

Lets get this out of the way right now. In the automation team at CenturyLink Cloud, we use docker to test almost all linux based cookbooks and a custom vsphere kitchen driver for everything else. We don't use kitchen-vagrant. I do use kitchen-vagrant often on personal projects but it just does not make sense at work. We ARE a cloud so spinning up our own cloud resources needs to be central to our testing. Its also simpler for us to manage test instances in our own large scale capacity cloud than on a much thinner build agent's hard drive.

So if you primarily use kitchen-vagrant for testing, you may not benefit from all the features here but you may want to stick around before you run off. There is more here than just a test-kitchen environment and if you work mostly with linux infrastructure, I cant stress enough the benefits of containers to testing productivity and decreased feedback cycles. This workstation aims to reduce the friction of setting this up repeatably.

Cache all the things!

One feature of this environment that is very popular is its use of squid and the vagrant-cachier plugin. The first time one runs vagrant up with this environment, it may take 10 minutes to run. Give or take a few minutes. This setup caches most internet downloaded artifacts on the host so that all subsequent vagrant ups will build much more quickly.

This feature is not available to windows hosts but if you run a mac or a linux desktop, this works wonders to bring down box build times.

Standardize on a base tools install while maintaining freedom of dev tools

I've historically preferred not to work in VMs as a dev environment. I either need to make sure all my "stuff" is installed there or cope with an environment that has minimal tooling. For those who use vagrant, you know this is where it shines. It syncs folders seamlessly across guest and host regardless of differing operating systems so I can dev on the same files in my favorite text editor or IDE on the host and then run those bits immediately in the shared guest environment.

This environment standardizes on Ubuntu 12.04 as a guest for a few reasons. Its used in many of our production servers and the popular hashicorp/precise64 vagrant box image has providers for VirtualBox, Hyper-V, VMWare Fusion and parallels. This makes it easy to share among any member of our org. I don't think there is anyone who cannot accommodate any of these hypervisor configurations. We don't have to tell anyone that their OS is not welcome.

Decoupling the "shell" from the "core"

This repository is intended to serve as one's "master" chef cookbook repository but it allows one to keep individual cookbooks in separate repos or in a single repo that is separate from this one. This repository contains only one cookbook which actually builds the environment. Any additional cookbook added to the cookbooks directory will be gitignored and should not be maintained in this repo. Simply clone your cookbooks into the cookbooks directory to be maintained separately. As long as they are in the cookbooks directory, they will be included in the vagrant folder syncing and immediately available in your vagrant guest.

Common rake tasks

The readme goes into the specifics of how to call all of the available tasks. I removed quite alot from this Rakefile that was in our own CenturyLink Rakefile and tied into our internal CI/CD pipeline with tasks for syncing dependencies, bumping versions, promoting cookbooks to different environments, etc. However what remains are likely tasks that most would expect to be present in any basic chef repo:

The tasks can work on a single cookbook or all cookbooks in the cookbooks directory in a single task invocation. The kitchen task also allows you to provide your own cookbook level Rakefile if you need to "orchestrate" the test suites for more complex test behavior.

Chef server friendly

The idea here is that if you work with a chef server, upon bringing up this environment, knife commands should "just work" with your account with no gymnastics or secret winking patterns (not that I think secret winking patterns are an anti-pattern -- I'm a huge fan!).

The chef_workstation cookbook generates a knife.rb in the .chef directory of the repo and only if no other knife.rb file exists here. By default the username of the logged in user on the host is used and the chef server url of the publicly hosted chef server is included but is missing the org in the path. Both the user and server url are configurable in the Vagrantfile. Then it is up to the user to add their .pem file to the top level .chef directory with the same name as the user. The entire .chef directory is gitignored so you can add keys without fear of them being added to source control.

A brief walk through of building the box and testing with docker

Getting setup

See the readme for the exact prerequisites and install instructions. Chances are high you already have them. At the very least you want:

Unless you are on windows I strongly suggest installing vagrant-cachier but its completely optional. Finally clone the chef_workstation repo.

Make it your own

This wisdom applies equally to American Idol performances and customized chef workstation vagrant boxes. Edit the Vagrantfile chef.json property that will inject the node attributes into the chef_workstation cookbook that provisions the box. This can include chef server user name and url, gems you want added that are not in the default installed gems, or additional deb packages you want included. All the possible attributes are mentioned here.

Also add your own cookbooks that you intend to work with to the cookbooks directory. so far, everything we have mentioned in the walk through, you only have to do once.

For this walk through, I will add the same cookbook covered a couple posts ago that covered multi node testing with test-kitchen and docker. Its a fork of the couchbase cookbook that can add nodes to a cluster.

cd cookbooks
git clone https://github.com/mwrock/couchbase -b multi-node

Build the box

Now run:

vagrant up

If you are using a hypervisor other than virtualbox, make sure to either:

  • Set the VAGRANT_DEFAULT_PROVIDER environment variable to the provider you want to use
  • Specify it in the --provider argument to vagrant up

In a few minutes the box should be ready.

Add your server key if using a server

If you use a chef server, copy your private key used to authenticate to the .chef directory in the root of the repo. The box provisioning should have created this directory. This file should be named {user_name}.pem. You may of course edit the knife.rb file if you prefer to use a different name. Once generated, the knife.rb file will never be overwritten when provisioning the box, and as mentioned previously, it is in the .gitignore file and thus excluded from source control.

Log on to the box and navigate to the couchbase cookbook

vagrant ssh
cd cookbooks/couchbase

Test a kitchen suite

We wont go through the whole multi node test cycle. Refer to my post on that topic if you are interested in running through that because in this environment you can without a problem. 

First lets run our chefspecs just to make sure unit tests are happy:

vagrant /chef-repo $ rake chefspec[couchbase]

2 deprecation warnings total

Finished in 0.90515 seconds (files took 2.34 seconds to load)
243 examples, 0 failures

vagrant /chef-repo $

Looks good. Now lets see what kitchen suites we have available. There are quite a few. Here is a portion of the suites at the top of the list:

vagrant /chef-repo/cookbooks/couchbase $ kitchen list
Instance                          Driver   Provisioner  Verifier  Transport  Last Action
server-community-debian-76        Docker   Nodes        Busser    Ssh        <Not Created>
server-community-ubuntu-1204      Docker   Nodes        Busser    Ssh        <Not Created>
server-community-centos-65        Docker   Nodes        Busser    Ssh        <Not Created>
server-community-windows-2012R2   Vagrant  Nodes        Busser    Winrm      <Not Created>
second-node-debian-76             Docker   Nodes        Busser    Ssh        <Not Created>
second-node-ubuntu-1204           Docker   Nodes        Busser    Ssh        <Not Created>

We will run the server-community-ubuntu-1204 suite:

kitchen verify server-community-ubuntu-1204

You will likely see a lot of text and this may take several minutes to complete at least the first time as the base ubuntu docker image is downloading. If you are using vagrant-cachier, the next time will be much faster. Hopefully this should end in a successful converge and serverspec tests...yup:

       Finished in 0.13151 seconds (files took 0.54705 seconds to load)
       13 examples, 0 failures

       Finished verifying <server-community-ubuntu-1204> (0m5.89s).
-----> Kitchen is finished. (5m1.32s)

Add the cookbook to our server

First we'll do a berks vendor to grab all dependencies:

vagrant /chef-repo/cookbooks/couchbase $ berks vendor /tmp/couchbase-vendor
Resolving cookbook dependencies...
Fetching 'couchbase' from source at .
Fetching 'couchbase-tests' from source at test/integration/cookbooks/couchbase-tests
Using apt (2.7.0)
Using chef-sugar (3.1.0)
Using chef_handler (1.1.6)
Using couchbase (1.3.0) from source at .
Using couchbase-tests (0.1.0) from source at test/integration/cookbooks/couchbase-tests
Using export-node (1.0.1)
Using minitest-handler (1.3.2)
Using openssl (4.0.0)
Using windows (1.36.6)
Using yum (3.6.0)
Vendoring apt (2.7.0) to /tmp/couchbase-vendor/apt
Vendoring chef-sugar (3.1.0) to /tmp/couchbase-vendor/chef-sugar
Vendoring chef_handler (1.1.6) to /tmp/couchbase-vendor/chef_handler
Vendoring couchbase (1.3.0) to /tmp/couchbase-vendor/couchbase
Vendoring couchbase-tests (0.1.0) to /tmp/couchbase-vendor/couchbase-tests
Vendoring export-node (1.0.1) to /tmp/couchbase-vendor/export-node
Vendoring minitest-handler (1.3.2) to /tmp/couchbase-vendor/minitest-handler
Vendoring openssl (4.0.0) to /tmp/couchbase-vendor/openssl
Vendoring windows (1.36.6) to /tmp/couchbase-vendor/windows
Vendoring yum (3.6.0) to /tmp/couchbase-vendor/yum

Now we will upload to our server. Mine is my personal, free hosted chef server:

vagrant /chef-repo/cookbooks/couchbase $ knife cookbook upload -a -o /tmp/couch
Uploading apt            [2.7.0]
Uploading chef-sugar     [3.1.0]
Uploading chef_handler   [1.1.6]
Uploading couchbase      [1.3.0]
Uploading couchbase-tests [0.1.0]
Uploading export-node    [1.0.1]
Uploading minitest-handler [1.3.2]
Uploading openssl        [4.0.0]
Uploading windows        [1.36.6]
Uploading yum            [3.6.0]
Uploaded all cookbooks.

Rinse and repeat

Now I am free to destroy this box and my synced repo folder will remain in tact. We do this all the time. Boxes get stale as other team members are reving shared internal gems or you are experimenting with alternate configurations and want to just start over.

There is something to be said for the freedom that comes with knowing you can walk on a tight rope since there is a net just beneath you to catch your fall (except when there's not).