Friday, March 3, 2017

Rapid Chef cookbook development with your desktop

1 Introduction

The purpose of this document is introducing a setup for rapid Chef Cookbooks development with Vagrant.

Vagrant is a tool that allow us to create development environment on our desktop. We can use this tool quickly create multiple VM on our desktop and start coding and testing Chef cookbooks.

The following diagram shows the overall Chef cookbook development effort with Vagrant and Chef-solo


  1. Developer create a Vagrant configuration file called “Vagrantfile”.
  2. When developer need a new VM, he/she runs the command “vagrant up”. Vagrant will call VirtualBox
  3. VirtualBox create a Virtual Machine
  4. The bootstrap script defined in the VagrantFile will be kickoff by Vagrant
  5. The bootstrap script install Chef on the VM
  6. Developer login to the VM to develop and test Chef cookbooks.

2 Create VM with Vagrant and VirtualBox

2.1 Install VirtualBox and Vagrant

First of all, you will need to download and install VirtualBox and Vagrant. This video gives a good introduction on how to install and use Vagrant and VirtualBox.

You can start download and installation with the following links:

2.2 Create VM

2.2.1 Create vagrantfile

A vagrantfile is a configuration file used by the Vagrant on how to install and configure virtual machines.
First, create to a directory to host your vagrant environment, I use ~/lab as example
$ mkdir ~/lab

Create a file call vagrantfile
$ cd ~/lab
$ vim vagrantfile

Enter the following in the Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# The following define a bootstrap script to be run when the VM starts up
$script = <<SCRIPT
echo "192.168.10.50 server1.example.com" >> /etc/hosts
echo "192.168.10.51 client1.exampl.com" >> /etc/hosts
curl -sSL https://www.opscode.com/chef/install.sh | sudo bash
SCRIPT

# The following define the VM information in an array
servers=[
 {
    :hostname => "server1",
    :ip => "192.168.10.50",
    :box => "bento/centos-6.7",
    :ram => 1024,
    :cpu => 1,
    :port_guest => 5601,
    :port_host =>5601
 },
 {
    :hostname => "client1",
    :ip => "192.168.10.51",
    :box => "bento/centos-6.7",
    :ram => 1024,
    :cpu => 1,
    :port_guest => 0,
    :port_host => 0
 }
]

# vagrant configuration
Vagrant.configure(2) do |config|
    servers.each do |machine|
       config.vm.define machine[:hostname] do |node|
           node.vm.box = machine[:box]
           node.vm.hostname = machine[:hostname]
           node.vm.network "private_network", ip: machine[:ip]
           if machine[:port_guest] != 0
              node.vm.network "forwarded_port", guest: machine[:port_guest], host:machine[:port_host]
           end
           node.vm.provision :shell, inline: $script
           node.vm.provider "virtualbox" do |vb|
               vb.customize ["modifyvm", :id, "--memory", machine[:ram]]
           end
       end
  end

In the above example, it tells vagrants to:
  • Create two virtual machines with Centos 6.7 with specific IP address, memory size and number of CPU.
  • The first VM has a name called “server1” and the second VM has the name “client1”
  • It include a bootstrap script to install chef when the VM starts up
  • The port 5061 on the first VM will map to the port 5601 on your desktop

You can change the configuration by editing the vagrant file to fit your need.

2.2.2 Start the virtual machines

To start the virtual machine, cd to the destination folder and run “vagrant up”
$ cd ~/lab
$ vagrant up

Once the VMs had been created and running, you can check the status by running “vagrant status”

$ vagrant status
Current machine states:

server1                      running (virtualbox)
client1                   running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

Now, you can ssh to the VM by running “vagrant ssh <vm name>”, for example
$ vagrant ssh server1
Last login: Thu Jan 26 14:24:04 2017 from 10.0.2.2
[vagrant@server1 ~]$

3 Chef cookbook development with chef-solo

3.1 Using the /vagrant directory to store Chef repository

The /vagrant directory on the VM is a synced folder. What it means is Vagrant will share your project directory (the directory with the Vagrantfile) to /vagrant on the VM.

By using /vagrant to store the Chef repository, we can destroy the VM and recreate it without worrying losting the work we had done.

3.2 Create a Chef directory structures

After login to your VM, run the following command to create Chef repository called “chefsolo” under the /vagrant directory

$ cd /vagrant
$ wget http://github.com/opscode/chef-repo/tarball/master
$ tar -zxf master
$ mv opscode-chef-repo* chefsolo
$ rm master

The chef-repo has the following directory structure
$ tree chefsolo
chefsolo
├── chefignore
├── cookbooks
│   └── README.md
├── data_bags
│   └── README.md
├── environments
│   └── README.md
├── LICENSE
├── README.md
└── roles
    └── README.md

3.3 Create Chef cookbooks

We want to create the Chef cookbooks now. In this example, I want to create a Chef cookbook called “helloword” for server1 with the following:

$ cd /vagrant/chefsolo/cookbooks
$ tree helloword
helloword
├── attributes
├── files
│   └── default
├── metadata.rb
├── README.md
├── recipes
│   └── default.rb
└── templates
    └── default

Create bookbook directory, for example helloword in this example:
$ mkdir -p /vagrant/chefsolo/cookbooks/helloword/attributes
$ mkdir -p  /vagrant/chefsolo/cookbooks/helloword/files/default
$ mkdir -p  /vagrant/chefsolo/cookbooks/helloword/templates/default

Edit the file /vagrant/chefsolo/cookbooks/helloword/README.md with the following:
### This cookbook install elastic stack (server1) on a single host
### Installed modules are:
### -  Helloworld

### Cookbooks:
- helloword

## Recipes
- helloword::default

Create  /vagrant/chefsolo/cookbooks/helloword/metadata.rb file
name             "helloword"
maintainer       "Wayne Yuen"
maintainer_email "wyuen@example.com"
license          "All rights reserved"
description      "install a simple cookbook"
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          "0.1.0"
recipe           "helloword", "default"

Create cookbook recipe /vagrant/chefsolo/cookbooks/helloworld/recipes/default.rb
file "/tmp/helloworld.txt" do
 owner "vagrant"
 group "vagrant"
 mode 00544
 action :create
 content "Hello, Implementor!"
end'

3.4 Create configuration file for chef-solo execution

3.4.1 Create a Chef role file

The following create a role called  /vagrant/chefsolo/roles/hello.json
{
    "name": "hello",
    "default_attributes": {},
    "override_attributes": {},
    "json_class": "Chef::Role",
    "description": "",
    "chef_type": "role",
    "run_list": [
       "recipe[helloword::default]"
    ]
}

3.4.2 Create a solo.rb file

We need to create a file call  /vagrant/chefsolo/hello_solo.rb file to tell chef-solo where the chef files located
cookbook_path "/vagrant/chefsolo/cookbooks"
role_path "/vagrant/chefsolo/roles"
data_bag_path "/vagrant/chefsolo/data_bags"
verbose_logging true
environment_path "/vagrant/chefsolo/environments"
environment "dev"
json_attribs "/vagrant/hello_node.json"

3.4.3 Create a node json file

We need to create a  /vagrant/chefsolo/hello_node.json file to tell chef-solo which Chef role we want to install on the node
{
         "name": "localhost",
         "automatic": {},
         "normal": {},
         "chef_type": "node",
         "default": {},
         "override": {},
         "run_list": [
             "role[hello]"
         ]
}

3.5 Testing the Chef cookbook with chef-solo

You can test the Chef cookbook by running chef solo on the node
$ sudo chef-solo -c  /vagrant/chefsolo/hello_node.json

We use the -c option to explicitly tell chef-solo where to read its configuration.

You should see a neat stream of messages from Chef as it works:
$ sudo chef-solo -c  /vagrant/chefsolo/hello_node.json
[2013-01-02T17:51:09+00:00] INFO: *** Chef 10.16.4 ***
[2013-01-02T17:51:10+00:00] INFO: Setting the run_list to ["recipe[helloworld]"] from JSON
[2013-01-02T17:51:10+00:00] INFO: Run List is [recipe[helloworld]]
[2013-01-02T17:51:10+00:00] INFO: Run List expands to [helloworld]
[2013-01-02T17:51:10+00:00] INFO: Starting Chef Run for ip-10-203-63-20.ec2.internal
[2013-01-02T17:51:10+00:00] INFO: Running start handlers
[2013-01-02T17:51:10+00:00] INFO: Start handlers complete.
[2013-01-02T17:51:10+00:00] INFO: Processing file[/tmp/helloworld.txt] action create (helloworld::default line 2)
[2013-01-02T17:51:10+00:00] INFO: entered create
[2013-01-02T17:51:10+00:00] INFO: file[/tmp/helloworld.txt] owner changed to 1000
[2013-01-02T17:51:10+00:00] INFO: file[/tmp/helloworld.txt] group changed to 1000
[2013-01-02T17:51:10+00:00] INFO: file[/tmp/helloworld.txt] mode changed to 544
[2013-01-02T17:51:10+00:00] INFO: file[/tmp/helloworld.txt] created file /tmp/helloworld.txt
[2013-01-02T17:51:10+00:00] INFO: Chef Run complete in 0.191596 seconds
[2013-01-02T17:51:10+00:00] INFO: Running report handlers
[2013-01-02T17:51:10+00:00] INFO: Report handlers complete

if you run cat /tmp/helloworld.txt you should see a tiny greeting. Success!

3.6 Final Chef cookbooks testing

For a final Chef cookbook testing, you properly want to run Chef on a machine that Chef never had run before. With Vagrant, it is simple, just destroy the VM and re-create it with the following command on your desktop:

$ cd ~/lab
$ vagrant destroy
$ vagrant up

After the VM has been re-created, login to the box with
$ vagrant ssh server1

And run the “chef-solo” command.
$ sudo chef-solo -c  /vagrant/chefsolo/hello_node.json

3.7 Push the cookbook to CodeCommit

Now you have complete your Chef cookbook development, you can push the cookbook to a repository, like GIT, from your desktop. 

Since the cookbook is stored on the vagrant working directory. It can be access from your VM or desktop. For this example, the share directory is ~/lab/chefsolo/cookbooks

4 Appendix

4.1 Vagrant commands:

You can use Vagrant to provision VM with the following commands:
$ vagrant box             manages boxes: installation, removal, etc.
$ vagrant connect         connect to a remotely shared Vagrant environment
$ vagrant destroy         stops and deletes all traces of the vagrant machine
$ vagrant global-status   outputs status Vagrant environments for this user
$ vagrant halt            stops the vagrant machine
$ vagrant help            shows the help for a subcommand
$ vagrant init            initializes a new Vagrant environment by creating a Vagrantfile
$ vagrant login           log in to HashiCorp's Atlas
$ vagrant package     packages a running vagrant environment into a box
$ vagrant plugin          manages plugins: install, uninstall, update, etc.
$ vagrant port            displays information about guest port mappings
$ vagrant powershell  connects to machine via powershell remoting
$ vagrant provision       provisions the vagrant machine
$ vagrant push            deploys code in this environment to a configured destination
$ vagrant rdp             connects to machine via RDP
$ vagrant reload          restarts vagrant machine, loads new Vagrantfile configuration
$ vagrant resume          resume a suspended vagrant machine
$ vagrant share           share your Vagrant environment with anyone in the world
$ vagrant snapshot    manages snapshots: saving, restoring, etc.
$ vagrant ssh             connects to machine via SSH
$ vagrant ssh-config  outputs OpenSSH valid configuration to connect to the machine
$ vagrant status          outputs status of the vagrant machine
$ vagrant suspend     suspends the machine
$ vagrant up              starts and provisions the vagrant environment
$ vagrant vbguest
$ vagrant version         prints current and latest Vagrant version

4.2 Reference


2 comments:

  1. Thank you for sharing wonderful information with us to get some idea about that content. check it once through Devops Online Training

    ReplyDelete
  2. Thank you for your guide to with upgrade information about AWS keep update at
    AWS Online Course


    ReplyDelete