Configuring Vagrant using Puppet with RVM and MySQL for Rails development

We're a small team here at Phase Shift. One of the biggest challenges we face is standardizing our development environment across different platforms. We have several Windows, OSX and Ubuntu machines in use as development machines and our usual production environment is Linux based. Ideally, each development machine would be set up with the exact same software and configuration as our production machines, but in practice, this is almost impossible. Enter hardware virtualization.

We've used VirtualBox in the past to setup environments on development machines. This worked, but was clunky and difficult to maintain consistency in software versions across each developer's instance. We thought of using custom scripts to keep things in synch, but never got good results. After reading up on Vagrant, we knew this was the setup we've been after.

How this will work

Once a developer has Vagrant and VirtualBox setup on their machine, they will download the project's source and run vagrant up. That's really it! Vagrant will run a VM instance with the project loaded and all dependencies installed and configured. The developer can make changes to the code locally and have those changes reflected instantly within the VM. Port forwarding will allow access to the server running on the VM through a local browser. Access to the VM is through SSH, so any SSH client will work. Sounds great? All we need to do is a little setup and configuration at the beginning to get the project ready.

What we need installed

Our typical new Rails application is running on Ruby 1.9.3 and is using MySQL as the datastore. Some applications may require other software such as Memcached, Redis, or MongoDB. For this setup, we're using RVM to manage our ruby installation. Once this VM is setup, it will have RVM, Ruby 1.9.3, Rails, and MySQL installed and running. Other packages can be added in easily, as you'll see below.

NOTE: We use a custom Vagrant box built on Ubuntu 11.10, but this setup should work just fine on the default Vagrant box - lucid32. For other custom boxes, checkout Vagrantbox.es.

Let's get started

First, create a new directory to put this new project. Next, install Vagrant per the setup instructions, up to the point of running vagrant up. We're going to define some custom Puppet settings to get the items we need installed before continuing.

Create a puppet directory directly under your project. We'll put all of our puppet setup scripts here. Your directory structure should look like this:

your_project/
  puppet/
  Vagrantfile

Open up your project's Vagrantfile and add the following configuration for Puppet and Rails:

config.vm.forward_port 3000, 3000

config.vm.provision :puppet do |puppet|
  puppet.manifests_path = "puppet/manifests"
  puppet.module_path    = "puppet/modules"
  puppet.manifest_file  = "development.pp"
end

This tells Vagrant to check these directories for Puppet configuration when initializing up our instance. We're also forwarding port 3000 since rails s starts on this port.

Defining your Puppet Configuration

We'll be using RVM to manage our ruby installations within the VM instance. Brandon Turner has done all of the hard work to get Puppet and RVM working with his project puppet-rvm. We'll be using this for our setup.

You'll need to put puppet-rvm into the puppet/modules directory as rvm:

git clone git://github.com/blt04/puppet-rvm.git puppet/modules/rvm

Next, in the puppet/manifests directory, make a new file named development.pp. This file will define all of our Puppet configuration. Paste the following into the file:

# development.pp
stage { 'req-install': before => Stage['rvm-install'] }

class requirements {
  group { "puppet": ensure => "present", }
  exec { "apt-update":
    command => "/usr/bin/apt-get -y update"
  }

  package {
    ["mysql-client", "mysql-server", "libmysqlclient-dev"]: 
      ensure => installed, require => Exec['apt-update']
  }
}

class installrvm {
  include rvm
  rvm::system_user { vagrant: ; }

  if $rvm_installed == "true" {
    rvm_system_ruby {
      'ruby-1.9.3-p0':
        ensure => 'present';
    }
  }
}

class doinstall {
  class { requirements:, stage => "req-install" }
  include installrvm
}

include doinstall

There's a lot going on here. Let's explain what each part does:

stage { 'req-install': before => Stage['rvm-install'] }

The puppet-rvm package uses Puppet run stages to set itself as the first package to run. We need to run apt-get update and install our packages before this happens. So, we are creating our own run stage, and running ours before puppet-rvm's.

class requirements {
  group { "puppet": ensure => "present", }
  exec { "apt-update":
    command => "/usr/bin/apt-get -y update"
  }

  package {
    ["mysql-client", "mysql-server", "libmysqlclient-dev"]: 
      ensure => installed, require => Exec['apt-update']
  }
}

This is where we define the packages we need to install. You can see we define a list of packages, including mysql-client, and we require that apt-get update is run before installation. Also, we're ensuring that a group puppet is present on the system. If you need other packages, just add them to the list.

class installrvm {
  include rvm
  rvm::system_user { vagrant: ; }

  if $rvm_installed == "true" {
    rvm_system_ruby {
      'ruby-1.9.3-p0':
        ensure => 'present';
    }
  }
}

Here, we are installing RVM as a system install, but are not setting it as the system default. Vagrant has a "system" ruby installed by default, and we want to make sure this remains the default. We are also adding the vagrant user as an RVM system user to avoid needing rvm-sudo when running ruby executables.

class doinstall {
  class { requirements:, stage => "req-install" }
  include installrvm
}

include doinstall

Finally, we hook our requirements setup into our custom Puppet run stage and install both the requirements and RVM setups we defined. The final line runs our setup.

Running Vagrant for the first time

For reference, your project directory should now look like this:

your_project/
  puppet/
    manifests/
      development.pp
    modules/
      rvm/
        ...
  Vagrantfile

Now you can run vagrant up and watch as Vagrant sets up the VM instance and installs all of our pre-requisites. This may take some time depending on the speed of your system.

Once the VM is up and running, follow the Vagrant SSH instructions to SSH into the VM. Once in the VM, do the following:

cd /vagrant
rvm use 1.9.3
gem install rails
rails new .

This will install rails and setup a new application within the project directory. A good idea here is to use an .rvmrc file to automatically use the version of ruby we want as well as a gemset for our application:

# .rvmrc
rvm use 1.9.3@your_project --create

Finally, add this project into your favorite source control and you're good to go!

Links

blog comments powered by Disqus
Posted:

Tags: