Posts Tagged Hosted Chef

Create Multi-VM Environments Using Vagrant, Chef, and JSON

Create and manage ‘multi-machine’ environments with Vagrant, using JSON configuration files. Allow increased portability across hosts, environments, and organizations. 

Diagram of VM Architecture3

Introduction

As their website says, Vagrant has made it very easy to ‘create and configure lightweight, reproducible, and portable development environments.’ Based on Ruby, the elegantly simple open-source programming language, Vagrant requires a minimal learning curve to get up and running.

In this post, we will create what Vagrant refers to as a ‘multi-machine’ environment. We will provision three virtual machines (VMs). The VMs will mirror a typical three-tier architected environment, with separate web, application, and database servers.

We will move all the VM-specific information from the Vagrantfile to a separate JSON format configuration file. There are a few advantages to moving the configuration information to separate file. First, we can configure any number VMs, while keeping the Vagrantfile exactly the same. Secondly and more importantly, we can re-use the same Vagrantfile to build different VMs on another host machine.

Although certainly not required, I am also using Chef in this example. More specifically, I am using Hosted Chef to further configure the VMs. Like the VM-specific information above, I have also moved the Chef-specific information to a separate JSON configuration file. We can now use the same Vagrantfile within another Chef Environment, or even within another Chef Organization, using an alternate configuration files. If you are not a Chef user, you can disregard that part of the configuration code. Alternately, you can substitute the Chef configuration code for Puppet, if that is your configuration automation tool of choice.

The only items we will not remove from the Vagrantfile are the Vagrant Box and synced folder configurations. These items could also be moved to a separate configuration file, making the Vagrantfile even more generic and portable.

The Code

Below is the VM-specific JSON configuration file, containing all the individual configuration information necessary for Vagrant to build the three VMs: ‘apps’, dbs’, and ‘web’. Each child ‘node’ in the parent ‘nodes’ object contains key/value pairs for VM names, IP addresses, forwarding ports, host names, and memory settings. To add another VM, you would simply add another ‘node’ object.

{
"nodes": {
"apps": {
":node": "ApplicationServer-201",
":ip": "192.168.33.21",
":host": "apps.server-201",
"ports": [
{
":host": 2201,
":guest": 22,
":id": "ssh"
},
{
":host": 7709,
":guest": 7709,
":id": "wls-listen"
}
],
":memory": 2048
},
"dbs": {
":node": "DatabaseServer-301",
":ip": "192.168.33.31",
":host": "dbs.server-301",
"ports": [
{
":host": 2202,
":guest": 22,
":id": "ssh"
},
{
":host": 1529,
":guest": 1529,
":id": "xe-db"
},
{
":host": 8380,
":guest": 8380,
":id": "xe-listen"
}
],
":memory": 2048
},
"web": {
":node": "WebServer-401",
":ip": "192.168.33.41",
":host": "web.server-401",
"ports": [
{
":host": 2203,
":guest": 22,
":id": "ssh"
},
{
":host": 4756,
":guest": 4756,
":id": "apache"
}
],
":memory": 1024
}
}
}
view raw nodes.json hosted with ❤ by GitHub

Next, is the Chef-specific JSON configuration file, containing Chef configuration information common to all the VMs.

{
"chef": {
":chef_server_url": "https://api.opscode.com/organizations/my-organization",
":client_key_path": "/etc/chef/my-client.pem",
":environment": "my-environment",
":provisioning_path": "/etc/chef",
":validation_client_name": "my-client",
":validation_key_path": "~/.chef/my-client.pem"
}
}
view raw chef.json hosted with ❤ by GitHub

Lastly, the Vagrantfile, which loads both configuration files. The Vagrantfile instructs Vagrant to loop through all nodes in the nodes.json file, provisioning VMs for each node. Vagrant then uses the chef.json file to further configure the VMs.

The environment and node configuration items in the chef.json reference an actual Chef Environment and Chef Nodes. They are both part of a Chef Organization, which is configured within a Hosted Chef account.

# -*- mode: ruby -*-
# vi: set ft=ruby :
# Multi-VM Configuration: Builds Web, Application, and Database Servers using JSON config file
# Configures VMs based on Hosted Chef Server defined Environment and Node (vs. Roles)
# Author: Gary A. Stafford
# read vm and chef configurations from JSON files
nodes_config = (JSON.parse(File.read("nodes.json")))['nodes']
chef_config = (JSON.parse(File.read("chef.json")))['chef']
VAGRANTFILE_API_VERSION = "2"
Vagrant.require_plugin "vagrant-omnibus"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "vagrant-oracle-vm-saucy64"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/saucy/current/saucy-server-cloudimg-amd64-vagrant-disk1.box"
config.omnibus.chef_version = :latest
nodes_config.each do |node|
node_name = node[0] # name of node
node_values = node[1] # content of node
config.vm.define node_name do |config|
# configures all forwarding ports in JSON array
ports = node_values['ports']
ports.each do |port|
config.vm.network :forwarded_port,
host: port[':host'],
guest: port[':guest'],
id: port[':id']
end
config.vm.hostname = node_values[':node']
config.vm.network :private_network, ip: node_values[':ip']
# syncs local repository of large third-party installer files (quicker than downloading each time)
config.vm.synced_folder "#{ENV['HOME']}/Documents/git_repos/chef-artifacts", "/vagrant"
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", node_values[':memory']]
vb.customize ["modifyvm", :id, "--name", node_values[':node']]
end
# chef configuration section
config.vm.provision :chef_client do |chef|
chef.environment = chef_config[':environment']
chef.provisioning_path = chef_config[':provisioning_path']
chef.chef_server_url = chef_config[':chef_server_url']
chef.validation_key_path = chef_config[':validation_key_path']
chef.node_name = node_values[':node']
chef.validation_client_name = chef_config[':validation_client_name']
chef.client_key_path = chef_config[':client_key_path']
end
end
end
end
view raw Vagrantfile.rb hosted with ❤ by GitHub

Each VM has a varying number of ports it needs to configue and forward. To accomplish this, the Vagrantfile not only loops through the each node, it also loops through each port configuration object it finds within the node object. Shown below is the Database Server VM within VirtualBox, containing three forwarding ports.

VirtualBox Port Forwarding Rules

VirtualBox Port Forwarding Rules

In addition to the gists above, this repository on GitHub contains a complete copy of all the code used in the post.

The Results

Running the ‘vagrant up’ command will provision all three individually configured VMs. Once created and running in VirtualBox, Chef further configures the VMs with the necessary settings and applications specific to each server’s purposes. You can just as easily create 10, 100, or 1,000 VMs using this same process.

VirtualBox View of Multiple Virtual Machines

VirtualBox View of Multiple Virtual Machines

.

Virtual Media Manager View of VMs

Virtual Media Manager View of VMs

Helpful Links

  • Dustin Collins’ ‘Multi-VM Vagrant the DRY way’ Blog Post (link)
  • Red Badger’s ‘Automating your Infrastructure with Vagrant & Chef – From Development to the Cloud’ Blog Post (link)
  • David Lutz’s Multi-Machine Vagrantfile GitHub Gist (link)
  • Kevin Jackson’s Multi-Machine Vagrantfile GitHub Gist (link)

, , , , , , , , , ,

4 Comments