Chef runs from your laptop via ssh using chef-provisioning-ssh
When comparing configuration management systems, one of the biggest selling points of ansible is how easy it is to get started in a small environment - you type up a recipe/playbook on your laptop, run ansible, and it connects via SSH to your machines to configure them. This blog post shows you how to do the same thing with chef using chef local mode, chef-provisioning and chef-provisioning-ssh. This setup can be very useful if you have just a couple of machines, don’t want to set up a chef server until you grow your infrastructure, and want to orchestrate everything from your laptop.
Steps
If you haven’t already, install the latest ChefDK, which will
install chef, chef-provisioning and lots of other goodies. It doesn’t however
install chef-provisioning-ssh
, so you’ll have to do that separately:
chef gem install chef-provisioning-ssh
Next, create your chef repository as normal. I use the chef generate
commands here to speed things up a little:
chef generate repo chef-repo
cd chef-repo/cookbooks
chef generate cookbook my-cookbook
echo 'log "Hello world"' >> cookbooks/my-cookbook/recipes/default.rb
cd ..
Next, create a file called site.rb
in the root of the chef repository. This
is the script you will run to run chef across your infrastructure. In this
example, we have just one machine:
#!/usr/bin/env chef-client -z
require 'chef/provisioning/ssh_driver'
with_chef_local_server({
:chef_repo_path => File.dirname(__FILE__),
:cookbook_path => ["#{File.dirname(__FILE__)}/cookbooks",
"#{File.dirname(__FILE__)}/berks-cookbooks"]
})
with_driver 'ssh'
machine 'mymachine' do
converge true
recipe 'my-cookbook'
#role 'base'
machine_options :transport_options => {
:host => 'mymachine.int.example.com',
:username => 'mark',
:ssh_options => {
:use_agent => true
}
}
end
Let’s break this down:
#!/usr/bin/env chef-client -z
The shebang line here simply lets you run the script directly using
./site.rb
and it will chef-client in local mode. This works because you can
pass a recipe file directly to chef-client -z
or chef-apply
and it will be
run.
require 'chef/provisioning/ssh_driver'
This is just a ruby require to load chef-provisioning-ssh
.
with_chef_local_server({
:chef_repo_path => File.dirname(__FILE__),
:cookbook_path => ["#{File.dirname(__FILE__)}/cookbooks",
"#{File.dirname(__FILE__)}/berks-cookbooks"]
})
This tells chef-provisioning to use chef local mode when provisioning the server, with the chef repository in the current directory. This is what lets you provision the remote machine with just the configuration repository located on your laptop.
The cookbook_path
parameter is optional, but I added an additional
berks-cookbooks
path for all cookbooks installed via berkshelf. I run berks
vendor berks-cookbooks
to install any community cookbooks or cookbooks with
their own repository to a separate directory.
with_driver 'ssh'
Use the ssh driver for all subsequent connections.
machine 'mymachine' do
converge true
Now we are into the machine definition itself. You have one of these for each machine you want to provision, and if desired you can use regular ruby to provision many machines at once using loops.
The converge true
line says to run chef every time we run the site.rb
script. Chef provisioning is intended for the initial bootstrapping of a
machine, and the machine would take over its own running of chef as a daemon
or via cron. Without this option, chef-provisioning would only run chef on the
initial run when the machine is first provisioned.
recipe 'my-cookbook'
#role 'base'
Specify the run list for your machine here. I specified the simple recipe
created earlier for this example, but you would probably use roles for a
real installation as in the commented out line. Use multiple role
or
recipe
lines as needed.
machine_options :transport_options => {
:host => 'mymachine.int.example.com',
:username => 'mark',
:ssh_options => {
:use_agent => true
}
}
Next is the various ssh options for the machine. There are lots of options you can use, and they are documented in the chef-provisioning docs, but What I’ve specified here is probably the minimum you will want to set - the machine’s hostname, the username to connect as, and instructing it to use the ssh agent. You can use password based authentication here too, but that would mean hard coding the password into the recipe, which is probably a bad idea.
Once you have your site.rb created, chmod +x site.rb
to make it executable,
and then run it:
$ ./site.rb
Starting Chef Client, version 12.0.3
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Compiling Cookbooks...
[2015-05-09T00:03:35-04:00] WARN: Node laptop has an empty run list.
Converging 1 resources
Recipe: @recipe_files::/Users/mark/git/chef-repo/site.rb
* machine[mymachine] action converge
[mymachine] Starting Chef Client, version 12.2.0
resolving cookbooks for run list: ["mh-base::test"]
Synchronizing Cookbooks:
- my-cookbook
Compiling Cookbooks...
Converging 1 resources
Recipe: my-cookbook::default
* log[Hello world] action write
Running handlers:
Running handlers complete
Chef Client finished, 1/1 resources updated in 5.050801491 seconds
- run 'chef-client -l auto' on mymachine
Running handlers:
Running handlers complete
Chef Client finished, 1/1 resources updated in 20.512658 seconds
And there you have it. Chef runs orchestrated entirely from your laptop. Chef-provisioning will automatically use sudo, so as long as you have key based ssh access, and your user on the remote machine has sudo access, things will just work.
Caveats
There are a few things to be aware of with this setup, mostly surrounding the fact that there is a chef-zero server running under the hood, and the fact that it keeps some saved state.
First, if you change how you connect to the server, chef-provisioning will
continue to try to use the old cached credentials to connect. This type of
situation is common if you initially bootstrap a machine using the pi
or
ubuntu
users, and then delete them and connect using a regular user
instead.
To fix this, delete ~/.chef/provisioning/ssh/mymachine.json
, and
chef-provisioning will use the new connection parameters on the next run.
Similarly, any modifications to the run list in site.rb are additive. Chef-provisioning doesn’t wipe away any existing run list on the node when it runs. This means that if you want to remove a run list entry, you should do it with knife from inside the chef repository once you have fixed site.rb:
knife node run_list remove mymachine 'recipe[my-cookbook]' -z
This is exactly the same as regular knife commands, but you add the -z
option. I find it easiest to just put it on the end of the command, as you get
an error if the option appears first because knife is expecting to see a
command there.
What’s going on here?
Chef-provisioning is intended to let you quickly get machines up and running entirely using chef. It essentially does four things:
- Create a new machine (e.g. in ec2 or vagrant)
- Connect to it via ssh
- Install chef on the new machine
- Run chef on the server
Chef-provisioning-ssh is a driver for chef-provisioning that lets you configure machines for ssh, which means it skips step one above - the machine is already created, so nothing needs to be done. Conveniently, the other three steps are exactly what’s needed to complete a chef run from scratch, and that’s exactly what we abuse chef provisioning to do.
The next part of the magic is chef-zero, or chef local mode. This lets you start a chef server on your laptop pointing to a local repository on disk, without any prior setup, and the server goes away once the chef run is complete. Chef-provisioning also sets up port forwarding over ssh so that the remote server can talk to the chef server on your laptop even if there is no direct connection back.
The beauty of this approach is, if your infrastructure grows large enough to require a chef server, you just upload the contents of the repository to the chef server, update the client config on the server (using chef of course), and you’re up and running on a real chef server with no other changes to your clients.
While there are a few issues that need to be worked out (and which I’m working on), this approach is serving me well for my home setup, and it’s incredibly simple to get started with.