Chef Provisioning: Infrastructure As Code

Chef Provisioning is now a Release Candidate, included in the [ChefDK](https://downloads.getchef.com/chef-dk/) version 0.3.4! This powerful new Chef featureset lets you idempotently create and converge machines, images, load balancers and other infrastructure, no matter where they are: cloud, bare metal, virtual machines, or containers. This is the next step in configuration management: Infrastructure as Code.

Chef already does a bang-up job describing and automating the software on individual machines in your clustered application. Chef Provisioning harnesses the simplicity and power of Chef to go one step further: to describe and automate the *whole cluster* with Chef, soup to nuts, hardware to network to software. This is the promise of Infrastructure as Code: when you write your cluster configuration down as code, suddenly your clusters become testable, repeatable, self-healing, idempotent and, most importantly, *easy to understand.*

Some of the features of Chef Provisioning:

* Describe your application cluster with a set of `machine` resources
* Deploy many copies of your application cluster (test, integration, production …)
* Spread your cluster across different clouds and machines for redundancy and availability
* Orchestrate your deployments, making sure (for example) the database primary comes up before any secondaries
* Speed up your deployments by parallelizing machines with `machine_batch`
* Standardize your fleet, and speed up rollouts by creating images without losing the power to patch, using `machine_image`
* Scale your services effortlessly with `load_balancer` and the `machine` resource.

Machines: Dead Simple
———————

This is the recipe to deploy a database machine with mysql on it. Stick this in a file if you want to follow along.

[code language=”ruby”]# mycluster.rb
require ‘chef/provisioning’
machine ‘db’ do
recipe ‘mysql’
end
[/code]

To run this, we’ll need to install Chef and Provisioning, set `CHEF_DRIVER` to our cloud provider, and run the recipe. That’s all there is to it.

Chef Provisioning release candidate is included with the ChefDK! So [install that first](https://downloads.getchef.com/chef-dk/).

#### Pick a Cloud, Any Cloud

To provision machines, you have to decide where you will put them. Pick your favorite supported cloud, virtual machine, container or bare metal driver, and set CHEF_DRIVER environment variable to it:

I picked `aws`, which by default will create machines in your default AWS account in `~/.aws/config`.

[code language=”bash”]export CHEF\_DRIVER=aws # on Unix
set CHEF\_DRIVER=aws # on Windows
[/code]

#### Run That Recipe

Finally, you run the recipe:

`chef-client -z mycluster.rb`

Marvel at the little green text as it brings up the machine! Wonder as it installs Chef from afar! Be amazed as Chef converges and mysql boots up on your new server!

That’s it.

Three Machines Are Better Than One
———————————-

You might be thinking, “man, that’s awesome, but that was one machine and it took me like 3 minutes. What if I want to make a bunch of machines?” Good question! The answer is `machine_batch`, and parallelization. Let’s edit that recipe and add two more web machines:

[code language=”ruby”]# mycluster.rb
require ‘chef/provisioning’
machine_batch do
machine ‘db’ do
recipe ‘mysql’
end
# Create 2 web machines
1.upto(2) do |i|
machine "web#{i}" do
recipe ‘apache2’
end
end
end
[/code]

We did two things here:

– We put our three machines in `machine_batch`. This will cause all the machines to be provisioned *in parallel*. So if one machine takes three minutes, 3 machines will take … three minutes.
– We used a loop to create more than one machine! This is why Provisioning is defined as a part of Chef–the full power of the language, the Chef DSL and all recipes and resources are available to you. If you wanted 10 web machines, you could change the `2` to `10` and run the recipe.

When you run this:

`chef-client -z mycluster.rb`

You will notice that the `db` machine says “up to date” instead of getting created! This is because like all Chef resources, `machine` is idempotent. It knows the db machine is already there and configured the way we want, so it doesn’t do anything.

Take An Image, It’ll Last Longer
——————————–

Another powerful tool in the Provisioning quiver is `machine_image`. This allows you to create an image that is pre-provisioned with some Chef recipes–meaning that you don’t have to install apache2 on every machine, every time. Let’s take a look at that in action:

[code language=”ruby”]# mycluster.rb
machine\_image ‘web\_image’ do
recipe ‘apache2’
end
machine ‘another\_web\_machine’ do
from_image ‘web\_image’
end
[/code]

Run this:

`chef-client -z mycluster.rb`

And you will see an image created by creating a machine, saving the image and then destroying the machine; and then you will see `another_web_machine` get created, *without* having to download and install Apache.

Load Balancers
————–

The network is as big a part of your cluster story as the machines, and Chef provisioning helps with that with the `load_balancer` resource. Let’s modify our first recipe:

[code language=”ruby”]# mycluster.rb

load\_balancer ‘web’ do
machines %w(web1 web2)
end
[/code]

Run the recipe:

`chef-client -z mycluster.rb`

And you get a load balancer with both of your web servers in it!

Drivers
——-

Chef Provisioning has an extensible driver system that works with many clouds, Virtual Machines, containers and even bare metal. Here is a non-exhaustive list:

**Clouds:**

– [aws](https://github.com/opscode/chef-provisioning-aws): Amazon Web Services. Includes support for load balancers, machines and images, as well as resources for managing a growing number of things including SQS and security groups.
– [azure](https://github.com/opscode/chef-provisioning-azure): Microsoft Azure. Includes support for machines and images, with more to come.
– [fog](https://github.com/opscode/chef-provisioning-fog): Support for machines and images on many cloud drivers, including:
– AWS
– CloudStack
– OpenStack
– Joyent
– Rackspace
– DigitalOcean

**Containers:**

– [docker](https://github.com/opscode/chef-provisioning-docker)
– [lxc](https://github.com/opscode/chef-provisioning-lxc)

**Virtual Machines:**

– [vagrant](https://github.com/opscode/chef-provisioning-vagrant): support for Vagrant, which brings in VirtualBox, VMWare Fusion, and others.

**Bare Metal:**

– [hanlon](https://github.com/opscode/chef-provisioning-hanlon)
– [opencrowbar](https://github.com/newgoliath/chef-provisioning-crowbar)
– [vsphere](https://github.com/RallySoftware-cookbooks/chef-metal-vsphere)

Want More?
———-

This just scratches the surface of what Chef Provisioning can do–a couple of primitives and you suddenly open up a whole new world of possibility. You can find a wealth of information at [the chef-provisioning repository](https://github.com/opscode/chef-provisioning) and contains a lot of documentation and examples. That is also the place to file issues. You can join our [Gitter channel](https://gitter.im/opscode/chef-provisioning) if you’d like to chat. Happy provisioning!

Author John Keiser

John is a Principal Mad Scientist at Chef, has contributed code that will be worked around for years to nearly every piece of software Chef ships. Specific credits include chef-zero, chef-provisioning, and the ChefFS tools (knife diff, upload, and download).

  • Marek

    What happens if web server depends on db but db is not converged yet? How do I specify dependencies? Can I have dependency per recipe or per machine only?

    • John Keiser

      If you want to run them in a particular order, you can just put them in the recipe in that order (and not in a machine_batch):

      machine ‘db’ do
      recipe ‘mysql’
      end

      machine_batch do
      1.upto(2) do |i|
      machine “web#{i}” do
      recipe ‘apache2’
      end
      end
      end

      If you want to be super fancy, you can put this at the top, saving even more time:

      machine_batch do
      machine ‘db’
      machine ‘web1’
      machine ‘web2’
      end

      Which will create all three machines, without any recipes running (so that you do all the initial setup in parallel). Then the rest of the recipe will run, putting mysql and apache on them in the right order.

  • Tom

    This might sound dumb but I did the things above and I got an error:
    here is the log “`
    ubuntu@ip-10-16-16-13:~/…/first-machine$ chef-client firstmachine.rb
    [2014-11-13T20:47:59+00:00] WARN: *****************************************
    [2014-11-13T20:47:59+00:00] WARN: Did not find config file: /etc/chef/client.rb, using command line options.
    [2014-11-13T20:47:59+00:00] WARN: *****************************************
    [2014-11-13T20:47:59+00:00] WARN:

    SSL validation of HTTPS requests is disabled. HTTPS connections are still
    encrypted, but chef is not able to detect forged replies or man in the middle
    attacks.

    To fix this issue add an entry like this to your configuration file:

    “`
    # Verify all HTTPS connections (recommended)
    sslverifymode :verify_peer

    # OR, Verify only connections to chef-server
    verifyapicert true
    “`

    To check your SSL configuration, or troubleshoot errors, you can use the
    knife ssl check command like so:

    knife ssl check -c /etc/chef/client.rb

    Starting Chef Client, version 11.16.4
    Creating a new client identity for ip-10-16-16-13 using the validator key.

    ================================================================================

    Chef encountered an error attempting to create the client “ip-10-16-16-13”

    [2014-11-13T20:48:00+00:00] FATAL: Stacktrace dumped to /home/ubuntu/.chef/cache/chef-stacktrace.out
    Chef Client failed. 0 resources updated in 0.908482238 seconds
    [2014-11-13T20:48:00+00:00] ERROR: I cannot write your private key to /etc/chef/client.pem – check permissions?
    [2014-11-13T20:48:00+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
    ubuntu@ip-10-16-16-13:~/…/first-machine$ chef-client -z firstmachine.rb
    [2014-11-13T20:48:19+00:00] INFO: Starting chef-zero on host localhost, port 8889 with repository at repository at /home/ubuntu
    One version per cookbook

    [2014-11-13T20:48:19+00:00] INFO: Forking chef instance to converge…
    Starting Chef Client, version 11.16.4
    [2014-11-13T20:48:19+00:00] INFO: *** Chef 11.16.4 ***
    [2014-11-13T20:48:19+00:00] INFO: Chef-client pid: 14071
    [2014-11-13T20:48:20+00:00] INFO: Run List is []
    [2014-11-13T20:48:20+00:00] INFO: Run List expands to []
    [2014-11-13T20:48:20+00:00] INFO: Starting Chef Run for tomgh
    [2014-11-13T20:48:20+00:00] INFO: Running start handlers
    [2014-11-13T20:48:20+00:00] INFO: Start handlers complete.
    [2014-11-13T20:48:20+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: /reports/nodes/tomgh/runs
    resolving cookbooks for run list: []
    [2014-11-13T20:48:20+00:00] INFO: Loading cookbooks []
    Synchronizing Cookbooks:
    Compiling Cookbooks…
    WARN: Unresolved specs during Gem::Specification.reset:
    httpclient (>= 2.2.0.2, ~> 2.2)
    nokogiri (>= 1.4.0, ~> 1.5)
    WARN: Clearing out unresolved specs.
    Please report a bug if this causes problems.
    [2014-11-13T20:48:21+00:00] WARN: Node tomgh has an empty run list.
    Converging 1 resources
    Recipe: @recipefiles::/home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb
    * machine[db] action converge[2014-11-13T20:48:21+00:00] INFO: Processing machine[db] action converge (@recipe
    files::/home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb line 2)
    [2014-11-13T20:48:21+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: http://localhost:8889/nodes/db
    [2014-11-13T20:48:21+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: http://localhost:8889/data/chefdefault
    [2014-11-13T20:48:21+00:00] INFO: Processing aws
    keypair[chefdefault] action create (basicchefclient::block line 518)
    [2014-11-13T20:48:21+00:00] INFO: HTTP Request Returned 404 Not Found : Object not found: http://localhost:8889/data/chef_default
    [2014-11-13T20:48:22+00:00] INFO: Running queued delayed notifications before re-raising exception

    ================================================================================
    Error executing action `converge` on resource 'machine[db]'
    ================================================================================
    
    
    AWS::EC2::Errors::InvalidKeyPair::NotFound
    ------------------------------------------
    aws_key_pair[chef_default] (basic_chef_client::block line 518) had an error: AWS::EC2::Errors::InvalidKeyPair::NotFound: The key pair 'chef_default' does not exist
    
    
    Resource Declaration:
    ---------------------
    # In /home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb
    
    
      2: machine 'db' do
      3:   recipe 'mysql'
      4: end
      5:
    
    
    Compiled Resource:
    ------------------
    # Declared in /home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb:2:in `from_file'
    
    
    machine("db") do
      action :converge
      retries 0
      retry_delay 2
      guard_interpreter :default
      chef_server {:chef_server_url=>"http://localhost:8889", :options=>{:client_name=>"tomgh", :signing_key_filename=>"/home/ubuntu/.chef/tomgh.pem"}}
      driver "aws"
      cookbook_name "@recipe_files"
      recipe_name "/home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb"
      run_list_modifiers [#<Chef::RunList::RunListItem:0x00000004cab6e8 @version=nil, @type=:recipe, @name="mysql">]
    end
    

    [2014-11-13T20:48:22+00:00] INFO: Running queued delayed notifications before re-raising exception

    Running handlers:
    [2014-11-13T20:48:22+00:00] ERROR: Running exception handlers
    Running handlers complete
    [2014-11-13T20:48:22+00:00] ERROR: Exception handlers complete
    [2014-11-13T20:48:22+00:00] FATAL: Stacktrace dumped to /home/ubuntu/.chef/local-mode-cache/cache/chef-stacktrace.out
    Chef Client failed. 0 resources updated in 2.984062286 seconds
    [2014-11-13T20:48:22+00:00] ERROR: machine[db] (@recipefiles::/home/ubuntu/chef-repo/machines/first-machine/firstmachine.rb line 2) had an error: AWS::EC2::Errors::InvalidKeyPair::NotFound: awskeypair[chefdefault] (basicchefclient::block line 518) had an error: AWS::EC2::Errors::InvalidKeyPair::NotFound: The key pair ‘chef_default’ does not exist
    [2014-11-13T20:48:22+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)

    “`

    • alain chiasson

      I have the same error – have not had time to followup on it – but was able to play around using vagrant. The changes are as follows :

      export CHEF_DRIVER=aws

      cat << EOF > vagrant_ubuntu.rb

      require ‘chef/provisioning/vagrant_driver’

      vagrant_box ‘precise64’ do
      url ‘http://files.vagrantup.com/precise64.box’

      end

      withmachineoptions :vagrant_options => {
      ‘vm.box’ => ‘precise64’

      }

      EOF

      And use the command line :

      chef-client -z vagrant_linux.rb mycluster.rb

      I ‘m assuming you have a setup ChefDK, Vagrant, VirtualBox. I will also add that I did not try a load balancer.

  • kowal

    This looks very nice. I have two questions:

    1. Can cluster be tested somehow via testkitchen? What exactly should we be testing on this level? Can you recommend some approach for this?

    2. How can I provision 3 nodes in my local network (given their IPs/hostnames)? Those nodes are already created VMs, I just want to converge them. Which driver should I use in that scenario?

    thx

  • rashid noman

    Is your ageing IT infrastructure hampering your progress? Are you
    worried about losing your business data? Are you worried about failed
    computers causing loss of productivity and valuable resources? If yes,
    then 403tech IT services has got the perfect solution for all your IT
    needs.
    http://www.403tech.com/

  • tiago

    Hello, I am trying to try this with Chef, but I get the following error with the line require ‘chef/provisioning’:

    Somebody can help me? I already installed the gem install chef-provisioning. Thanks a lot.

    LoadError

    cannot load such file — chef/provisioning

    Cookbook Trace:

    /var/chef/cache/cookbooks/docker/recipes/default.rb:15:in `from_file’

    Relevant File Content:

    /var/chef/cache/cookbooks/docker/recipes/default.rb:

    require ‘chef/provisioning’

    machine ‘repoMachine’ do
    recipe ‘repos’
    end