Comeback post: using Chef search with chef-zero and multiple Vagrant machines

I haven’t posted in quite a while and after a couple of requests, I decided to make a comeback and continue sharing some knowledge here.

Over the recent months, I’ve ran into Chef cookbooks that use the search feature, such as the nrpe cookbook. Developing and testing a wrapper cookbook locally works fine… up to a certain point.

When using chef-zero provisioning in Vagrant an in-memory chef server is launched on the Vagrant instance. This becomes a problem when you want to have a multi-host Vagrant setup (for example a client-server situation, like in the case of the nrpe cookbook) because each host would have its own in-memory server.

Because of this, some people have been forced to use some fragile solutions like standing up a secondary chef server for testing or even more dangerous, just not testing the cookbooks locally.

There is no out-of-the-box support for this in Vagrant, but it can be achieved by cleverly using a couple of features. I’ll describe the key elements in the post, but feel free to contact me if you feel I missed something or if you have questions.

The first thing we need is the Vagrantfile:

Vagrant.configure("2") do |config|

  config.vm.define "chef" do |chef|
    chef.vm.provision "bootstrap", type: "shell", path: "chef-bootstrap.sh", privileged: false
    chef.vm.provision "update", type: "shell", path: "chef-zero-update.sh", privileged: false
    chef.vm.network "private_network", ip: "10.0.1.2"
    chef.vm.hostname = "chef.vagrant.dev"
  end

  config.vm.define "server" do |server|
    server.vm.network "private_network", ip: "10.0.1.3"
    server.vm.provision "chef_client" do |chef|
      chef.chef_server_url = "http://10.0.1.2:8889"
      chef.validation_key_path = "dummy.pem"
      chef.validation_client_name = "dummy" 
      chef.node_name = "nagios-server"
      chef.run_list = "role[nagios-server]"
      chef.environment = "dev"
      chef.delete_client = true
      chef.delete_node = true
    end
  end

  config.vm.define "client" do |client|
    client.vm.network "private_network", ip: "10.0.1.4"
    client.vm.provision "chef_client" do |chef|
      chef.chef_server_url = "http://10.0.1.2:8889"
      chef.validation_key_path = "dummy.pem"
      chef.validation_client_name = "dummy" 
      chef.node_name = "nagios-client"
      chef.run_list = "role[nagios-client]"
      chef.environment = "dev"
      chef.delete_client = true
      chef.delete_node = true
    end
  end
end

If you notice, we’re not really using chef-zero on the client or server machines, but rather standing up a single chef-zero server in one Vagrant host, that the other hosts are going to talk to as if it was a full-fledged Chef server.

Here are the contents of chef-bootstrap.sh:

#!/bin/bash

curl -sSL https://rvm.io/mpapis.asc | gpg --import - 2>&1
curl -sSL https://get.rvm.io | bash -s stable --ruby=2.2.1 2>&1
source /home/vagrant/.rvm/scripts/rvm
gem install librarian-chef

chef-zero --host 10.0.1.2 --daemon
cd /vagrant
librarian-chef config tmp /tmp --global
librarian-chef install
knife upload /. --chef-repo-path /vagrant/ --server-url http://10.0.1.2:8889 --key /vagrant/dummy.pem --user dummy

I’m using librarian-chef by choice but you can use something else like Berkshelf.

The last line of this script is the key. It’s using knife as a client to upload the cookbook to the server (including /cookbooks, data_bags and roles). Because chef-zero uses dummy authentication, we need to provide a dummy key.

The second Vagrant provisioner in the chef server machine is another script called chef-zero-update.sh, used to update the server if there were any changes to the cookbook. Here are its contents:

#!/bin/bash

cd /vagrant
knife upload /data_bags --chef-repo-path /vagrant/ --server-url http://10.0.1.2:8889 --key /vagrant/dummy.pem --user dummy 2>&1
knife upload /roles --chef-repo-path /vagrant/ --server-url http://10.0.1.2:8889 --key /vagrant/dummy.pem --user dummy 2>&1
knife upload /environments --chef-repo-path /vagrant/ --server-url http://10.0.1.2:8889 --key /vagrant/dummy.pem --user dummy 2>&1
knife cookbook upload my-cookbook --cookbook-path /vagrant/cookbooks --server-url http://10.0.1.2:8889 --key /vagrant/dummy.pem --user dummy 2>&1

Needless to say, you have to spin up and provision your chef server first, before your server or client.

Last but not least, up until recently, Vagrant would make the call to delete the chef node and client from within the host (rather than within the guest), so as of this writing, it’s likely that you’ll need a knife.rb file with the following contents alongside your Vagrantfile:

node_name                'dummy'
validation_client_name   'dummy'
validation_key           'dummy.pem'
chef_server_url          'http://10.0.1.2:8889'

But that’s about it! Now you have two vagrant machines who think they’re being provisioned by a real chef server when they’re actually being provisioned by a tiny chef-zero server.

A couple of things to keep in mind:

  • Depending on your version of Vagrant, it’s possible that you might get a conflict if you’re trying to use knife commands with your live (production) chef server. If that’s the case, just make sure you use the --config switch to point it to your default knife configuration file (most likely under ~/.chef/knife.rb.
  • If you make any changes to the cookbook that need to be re-tested, make sure you update the chef server and re-provision the necessary boxes. The process varies depending on your cookbook management system (librarian-chef in this case), but it helps to have scripts you can quickly call from the command line.
  • In my experience, sometimes Vagrant didn’t quite work correctly and would keep a zombie process after the initial provisioning and I would have to kill it and start over. I couldn’t really diagnose what the problem was but it didn’t happen frequently.
  • Multi-VM vagrant setups can quickly consume your host resources so make sure you configure your VMs correctly so you don’t run out of memory!
Advertisements