I've been using at Vagrant at work lately to make it easier to switch between environments on projects, and to help out when testing chef recipes.
These instructions are here to help me remember how I went about building the base machines. This post will almost change in the next week or so.
If you don't know what the tool Vagrant is, these instructions won't mean much without watching this screencast demonstrating what Vagrant does, and why you might want to use it. I'd suggest taking the 15 minutes or so to watch it before reading on if you're unfamiliar with the project, as by necessity, they focus on creating a base virtual machine to begin building your own recipes on, and don't explain how to use Chef, or give much background.
They draw heavily on a gist by the excellently named Zellyn Hunter, with some instructions from the relevant page on the opscode wiki.
For simplicity, they favour using yum where possible for managing packages because a) it forms the basis of so many recipes when writing cookbooks in chef when you use any RedHat based flavour of Linux, and b) it should stop this guide going obsolete so quickly.
Still with us? Lets begin.
Create a bare virtual machine
First of all, we need a virtual machine as a base. We're making a CentOS based box here, but there are also some pre-made Ubuntu based boxes available as well online linked to in the Vagrant Google Group.
In Virtual box, create a new virtual machine, giving it a descriptive name that fits the guidelines on the vagrant page. We're making a 64 bit system here, so we're need to select this in virtual box:
Create a new VirtualBox machine
- Name: vagrant-centos64
- Operating System: Linux
- Version: Red Hat
Because we're eventually making a .box file that we're want others to be able to download, we need to take this into account in the resources allocated to this virtual machine, so we're going to make a dynamic hard disk that scales up to a large size, but when empty doesn't take up much space, and we'll keep the memory allocated to this machine fairly modest too. Because we have no need for graphics, or USB ports we'll leave them out of our virtual machine as well.
The specs should look something like this:
- Base Memory Size: 360 MB
- Dynamically expanding storage 40 GB
- 40 GB
- Disabled Audio
- Disabled USB
Put an OS on the virtual machine
To install the OS, we need to do two things a) make sure the architecture for the install media matches that of the virtual machines and b) make sure our net install we can download the rest of the software needed to build the machine.
First we'll fetch the relevant iso image to mount like a virtual boot disc on our virtual machine:
wget http://mirrors.kernel.org/centos/5.4/isos/x86_64/CentOS-5.4-x86_64-netinstall.iso
We're using a net install to begin with here, because the standard images available for us are too large to download over broadband if you share a connection in an office or and you don't have some direct fibre-optic cable or similarly zippy connection. You'll notice this image is the install image for a 64bit architecture - the process is the same is you're looking for a 32 bit based system instead, just install fetch the 32bit install iso instead.
Next we need to make we can connect to the internet and pull down the other binary files. You should have NAT (Network Address Translation) set in virtualbox, which lets your host machine act like a router.
So make sure you have ipv4 enabled, (but ipv6 switched off), and all you should need to do is have DHCP set as the way to gain a connection, and you should be able to connect to the internet to download software.
Now run when you boot the VM, choose linux-text mode, and when asked about formatting the drive, select the option to initialise the drive with a standard linux filesystem. When you're asked about and when asked about the installation process, choose the HTTP option, disable ipv6, and enter the following details to pull down the kernel:
- HTTP Setup:
- Web site name: mirrors.kernel.org
- CentOS directory: centos/5.4/os/i386
When we're given the option to select the network, we need to have a fully qualified domain name (i.e. vagrant-centos-5-4.local, not just vagrant-centos-5-4)
We'll also want to remove as much software as possible by default, so we want to customize software installation, and make sure the following is unselected when we do:
- Dial-up networking
- Editors
- Text-based internet
- Base
Now typing things into a virtual machine terminal without copy and paste gets old quickly, so lets set up port-forwarding so we can ssh into our newly created Virtualbox. The simplest way to do this is using the following invocations on the command line. We're going to make any connections to our own host machine (i.e. our laptop) on port 2222, bounce to port 22 on the guest virtual machine, and specify that this is a TCP connection.
VBoxManage setextradata <guestname> "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/HostPort" 2222 VBoxManage setextradata <guestname> "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/GuestPort" 22 VBoxManage setextradata <guestname> "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/Protocol" TCP
Install Chef, and the Guest Additions
Now that we have a working machine, we need to set it up to a) work with our file system so changes on our host box are reflected on it, and b) get chef on it, so we can move into config-management nirvana. First all lets add a few handy packages first though:
# useful for every case yum install curl ftp rsync sudo time wget which # these are needed for building Virtualbox Additions yum install gcc bzip2 make kernel-devel-`uname -r`
With the software needed, it's worth clearing off some packages that we don't need;
yum erase wireless-tools gtk2 libX11 hicolor-icon-theme avahi freetype bitstream-vera-fonts
Now that we have the prerequisites, we can now install chef, and and setup the two-way shared folders setup.
Shared folders
From the virtual box app, select Devices -> Install Guest Additions... from the menubar to make available the Virtual box disk image; we can then mount it, and depending on the architecture we're using, run the relevant guest additions script:
# mount the Guest additions image thats available in dev/cdrom as a read-only disk on the mount point /mnt, using the iso9660 standard, mount -o ro -t iso9660 /dev/cdrom /mnt sh /mnt/VBoxLinuxAdditions-amd64.run # this may be VBoxLinuxAdditions-x86.run, if you're making a 32bit VM instead
If you're experiencing sluggish performance with shared folders, it's worth checking that you have the latest version of Virtualbox' guest editions installed. Calling the sh /mnt/VBoxLinuxAdditions-amd64.run command will perform a clean installation of the extensions, which can help (we're still struggling with this issue, so if you have any pointers let us know in the comments!)
Install chef
We'll need to add a couple of new sources before we can install chef, from the ELFF and EPEL yum repos where more recent software packages are made available:
rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm rpm -Uvh http://download.elff.bravenet.com/5/i386/elff-release-5-3.noarch.rpm
Now at this point we can now add chef. The -y means we don't have to manually okay it when it comes through. Feel free to ignore it.
yum install chef -y
Create the Vagrant User
The convention with vagrant is to create a group for our vagrant user, to allow any users in that group to call commands with sudo without needing passwords (as you can imagine, this makes calling remote ssh commands much simpler):
groupadd admin
useradd -G admin vagrant
passwd vagrant
# choose "vagrant" as the passwordUpdate visudo to for vagrant user
As mentioned before, we need to make a few changes to the visudo table, to allow the vagrant user to perform chef runs. In short, we need to update the PATH attribute, relax the ssh daemon's requirements before it'll accept commands sent over ssh, and create a special insecure key that grants access without needing passwords.
First of all, comment the line saying Defaults requiretty in the visudo file - this option makes having an interactive teletype terminal a requirement for sudo commands to be called, so remote ssh commands that use sudo won't work. Switching off lets us call these commands remotely.
# Defaults requirettyNext tweak the Defaults env_keep entry, to add PATH, so when we switch to switch user, we still keep the same paths, like having the correct version of Ruby when calling ohai the tool that chef uses to check your system's setup against the desired one specified in your cookbooks:
Defaults env_keep = "COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR \
LS_COLORS MAIL PS1 PS2 QTDIR USERNAME \
LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION \
LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC \
LC_PAPER LC_TELEPHONE LC_TIME LC_ALL LANGUAGE LINGUAS \
_XKB_CHARSET XAUTHORITY PATH" #make sure path is hereThere's more on this here on stackoverflow
We're almost done now - we just need to supplement the PATH variable, to make admin simpler, by making commands like ifconfig available without needing to type the full path to them. You do this by adding this line to the .bashrc file for the vagrant user:
export PATH=$PATH:/usr/sbin:/sbin
Next log out, then log back in in as the vagrant user to sort out the key based access. We're going to add an insecure key that will let anyone log in to this box, so bear that in mind if you're using this on any private boxes:
mkdir .ssh chmod 755 .ssh curl http://github.com/mitchellh/vagrant/raw/master/keys/vagrant.pub > .ssh/authorized_keys chmod 644 .ssh/authorized_keys
Clean up after ourselves, and prepare for packaging the .box file
We'd like to keep the packaged VM size small if we can, so the next step is to clean up after ourselves somewhat with yum like so:
sudo yum clean headers packages dbcache expire-cacheThat's all the work done in the virtual machine now. Now we need to package it up with a Vagrantfile, which lists any custom adjustments, which is where we list the port forwarding instructions using the Vagrant DSL. First we create a directory, and create the Vagrantfile, somewhat like we would with a git project:
mkdir centos-vagrant-64 cd centos-vagrant-64 vagrant init
Next we add edit the Vagrantfile, to add the ssh port, so we can actually log into it:
Vagrant::Config.run do |config| # Forward the SSH port. The 'forward_port_key' should match the # name of the forwarded port. config.ssh.forwarded_port_key = "ssh" config.vm.forward_port("ssh", 22, 2222) end
Now that we've prepared both the Virtual Machine, and the host environment, we can then package it up, to create a sharable virtual machine. If the virtual machine we created using virtualbox was called centos-vagrant-64, we'd pass that in like so:
vagrant package --base my_base_box --include Vagrantfile
We'll end up with a large compress file named package.box, which will be our virtual machine, all package up for sharing.
We can load this into our list of virtual machines available to vagrant like so, by passing in a desired name, and the path to the file. We can also to this with boxes across the internet, as long as they end with the .box extension, and and they're formatted to according to the guidelines in Vagrant.
vagrant add vagrant-centos5.4-x64 ./package.box vagrant add precompiled-vagrant-32 http://remote.server/with/pre-made-boxes/vagrant-centos5.4-i32
Using these boxes:
Now that we have these boxes available to us, a project only needs to refer to them in their Vagrantfile like so:
config.vm.box = "precompiled-vagrant-32"
And you'll be able to develop using the same environment as the other developers.
Customizing this boxes
We can update vagrant boxes using this DSL, to update memory, number of virtual cpu's, or to forward more ports if need be. If we want to give this machine more resources, because it's running too slow, we can up the memory and cpu count:
config.vm.customize do |vm| vm.memory_size = 2048 vm.cpu_count = 2 end
If we we're working with the web, we'll want to add another forwarded port, but remember to update the firewall rules too:
Vagrant::Config.run do |config| # Forward apache config.vm.forward_port("web", 80, 8080) end
Now what?
This should give a decent grounding on how to prepare virtual machines for sharing with others, but be sure to check the extensive documentation on the Vagrant website.
If you're interested in giving Vagrant a spin, and you're using CentOS, you might want to try building a system using these boxes

Avoiding that Face Palm moment with Logrotate
A runaway log file brought down on a previously built Rails app I was involved in building recently, and the solution to the problem here is so simple and easy to implement that a) I feel like a total dunce for not having this setup here in the first place b) it's almost churlish not to list it here, for reference for someone else, in the hope that it saves them feeling this stupid themselves in the future, (oh, and keeps the site they're working on up...).
If you're not using Rail's own to rotate the log files it generates, it really is good idea to make sure any log files it does make are being rotated, to make sure you don't get caught out when that innocuous seeming
development.logfile from a few months back goes live ends up bringing down your site because it's since grown from a 6k file to a 7 gigiabyte one, and eaten all the space on your server.Making sure this doesn't happen is a pretty simple process:
Okay lets run through this in more detail.
Find the offending logfiles
The first step here is to find where the logs are being created. This here is the cause of the problem on a lot of boxes running Rails apps, because if you're using Capistrano to deploy an app, and you're using a stock Passenger config then your logs will end up in somewhere that the logrotate daemon, the program that dutifully goes around compressing and sorting logfiles on your server, won't know where to look for by default.
Normally, you can expect to find these quietly ballooning log files somewhere like
/home/deploy/app/shared/log/, or/rails/deploy/appname.production/shared/log/.Create the new logrotate entry
Now that we know where the logs are that keep eating space, we can tell logrotate about them, to make sure they won't grow too large. Create a text file named
railsapp(or whatever makes the most sense to you) in/etc/logrotate.d/, the default place to leave instructions for logrotate to follow:Looking at that line by line:
dailycalls this script dailymissingokmeans it's okay if we're missing some log files, we'll still work without stoppingrotate 30means keep the last 30 days of logscompressyup, compress the logs (using gzip by default)delaycompressmeans "wait til the next day before compressing this file, just incase there are still be processes writing to this logfile"sharedscriptsmeans only call the nextpostrotatescript once for all the files that match the pattern above, instead of restarting for each filepostrotate...endscript- this script here fires after a rotate, to restart the passenger server, so that future processes log to the fresh, empty logfileIf you want to learn more, this article by Jesse Andrews on how he uses it is an absolute gem.
Trigger the logrotate daemon
Now that we have a logrotate entry, lets check it if works now rather than going to bed and finding out we had mistyped the path, by forcing a log rotate with this command (note the
-fflag):Get on with your life
If that's worked, then huzzah! That should be one less thing to worry about when looking after a webapp - though the usual "do some real homework before putting absolute faith in and deploying on a production system" disclaimers apply. As ever with Linux, be sure to read the man pages before use.
nb. While it's true that Rails is actually smart enough to rotate its own logs if you remember to configure it to behave this way, learning how to use logrotate like this means we can use this on other apps too, without being too tied to a particular framework. Handy when you need it for a Merb, Sinatra, Django, or even Node.js project