Monday, April 13, 2015

Vagrant, Packer, and Boxcutter FTW: Create a windows build environment with one command

A quick intro to vagrant, packer, and boxcutter, which are all projects under the Hashicorp umbrella.  From reading the project websites it's not immediately obvious how all of these relate and compliment each other, they are all vm-related after all.  I'll introduce each and then give an end-to-end example.

Vagrant

Firstly vagrant: it allows you to specify your build environment in a way that is completely repeatable by anyone in the world.  No more "builds fine for me".  You can test in exactly the same environment, with the same versions of dependencies, installed by the same scripts.

So what's special about that? Why not just have a base vm and an install script that sets everything up?  For a long time we essentially did that but a bit worse: we had a server with a set of build VMs on it.  Everyone who wanted to use the VMs would add their ssh key, and copy a giant ssh_config which set up all the port forwards needed to talk to the VMs and build the project.  We never quite got around to automating the dependency installation, so whenever a dependency needed updating we'd need to ssh in and upgrade each VM manually.  The server lived in one timezone, but developers were also on the other side of the world so copying chunks of data over for building was slow and timeouts were fairly common.

With vagrant we saw the following advantages:

  • Bringing up a new vm is fast, so there's no need to keep them around and potentially contaminate your new build with previous build products.  If you always provision a new vm to build then maintaining dependencies is as simple as updating the provisioning scripts.
  • Port conflicts for multiple VMs are managed automatically, no need for the big ssh_config
  • Builds are performed on local VMs, so no waiting on network copies to servers on the other side of the world. Shared folders provide a simple way to move data in and out of the VM.
  • Testing additional operating systems and architectures is simple thanks to the atlas catalog of vagrant boxes.
  • Updating dependencies across multiple architectures and operating systems is as simple as modifying the provisioning script.  While this isn't technically an advantage of vagrant, it encourages this kind of automation.
  • Instead of describing how to set up a build environment in pages of documentation, you can add a Vagrantfile and some scripts to your project and reduce all that documentation to a single command like 'make'.
  • We don't need to store the virtual machines ourselves, just upload a base build box to the catalog.  When people build our project vagrant will fetch the VM and check the hash of the downloaded VM matches what we specify in our Vagrantfile (or at least it will soon).
OK that sounds good, but I want to make my own base VMs and it's tedious.  Enter packer.

Packer

So vagrant helps you automate provisioning and running a build environment working from a base VM.  Packer helps you with building the base VM in the first place which opens up a lot of possibilities for testing and automation.  For the purposes of this post, packer is just going to help us avoid the tedious GUI click fest of installing a windows build VM and getting it into a usable state.

Since packer basically eats a JSON config and turns it into a virtual machine, you expect to find an example of a packer config to follow right? You're not the first person to want a Windows 7 VM.  Enter boxcutter.

Boxcutter

Boxcutter is basically a giant library of packer configs that generate almost any VM you can possibly think of.  For this post we're going to use the windows boxcutter repo.

First install vagrant, packer, and whatever virtualisation software you plan to use (e.g. virtualbox or vmware: make sure you have the latest version) then get the boxcutter repo:
$ git clone https://github.com/boxcutter/windows.git boxcutter-windows
Then get a windows VM (this took around 45min for me, but is a spectacularly less work than doing it yourself):
$ make virtualbox/eval-win7x64-enterprise
The boxcutter scripts include all the tricks for minimising disk usage (doing a defrag, zero-ing free disk space etc.), so this is about as small as it can possibly be:
$ ls -lh box/virtualbox/eval-win7x64-enterprise-nocm-1.0.4.box 
-rw-r----- 1 user group 3.2G Apr 13 14:31 box/virtualbox/eval-win7x64-enterprise-nocm-1.0.4.box
Add it into vagrant:
$ vagrant box add box/virtualbox/eval-win7x64-enterprise-nocm-1.0.4.box --name eval-win7x64
Reference it in your Vagrantfile something like this:
  config.vm.define "eval-win7x64" do |box|
    box.vm.box = "eval-win7x64"
    box.vm.guest = :windows
    box.vm.communicator = "winrm"
  end
Fire it up:
$ vagrant up eval-win7x64
Log in:
$ vagrant ssh eval-win7x64
Last login: Mon Apr 13 14:19:31 2015 from 10.0.2.2
Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\vagrant>
Troubleshooting

Note if you see errors like these:
==> virtualbox-iso: Provisioning with shell script: script/vagrant.bat
    virtualbox-iso: 'C:/Windows/Temp/script.bat' is not recognized as an internal or external command,
    virtualbox-iso:
    virtualbox-iso: operable program or batch file.
==> virtualbox-iso: Unregistering and deleting virtual machine...
==> virtualbox-iso: Deleting output directory...
Build 'virtualbox-iso' errored: Script exited with non-zero exit status: 1
or
==> virtualbox-iso: Error detaching ISO: VBoxManage error: VBoxManage: error: Assertion failed: [SUCCEEDED(rc)] at '/build/buildd/virtualbox-4.3.10-dfsg/src/VBox/Main/src-server/MachineImpl.cpp' (10875) in nsresult Machine::saveStorageControllers(settings::Storage&).
==> virtualbox-iso: VBoxManage: error: COM RC = E_ACCESSDENIED (0x80070005).
==> virtualbox-iso: VBoxManage: error: Please contact the product vendor!
==> virtualbox-iso: VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component SessionMachine, interface IMachine, callee nsISupports
==> virtualbox-iso: VBoxManage: error: Context: "SaveSettings()" at line 888 of file VBoxManageStorageController.cpp
==> virtualbox-iso: Unregistering and deleting virtual machine...
==> virtualbox-iso: Deleting output directory...
Build 'virtualbox-iso' errored: Error detaching ISO: VBoxManage error: VBoxManage: error: Assertion failed: [SUCCEEDED(rc)] at '/build/buildd/virtualbox-4.3.10-dfsg/src/VBox/Main/src-server/MachineImpl.cpp' (10875) in nsresult Machine::saveStorageControllers(settings::Storage&).
VBoxManage: error: COM RC = E_ACCESSDENIED (0x80070005).
VBoxManage: error: Please contact the product vendor!
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component SessionMachine, interface IMachine, callee nsISupports
VBoxManage: error: Context: "SaveSettings()" at line 888 of file VBoxManageStorageController.cpp
Then you need to upgrade your virtualbox. I saw these when using the virtualbox shipped with ubuntu.

1 comment:

Samson Kolge said...

I am trying this to create a windows box but I am getting the below error:

virtualbox-iso: Minimum = 0ms, Maximum = 1ms, Average = 0ms
virtualbox-iso: ==> Script exiting with errorlevel 0
==> virtualbox-iso: Provisioning with shell script: script/vmtool.bat
virtualbox-iso: ==> Creating "C:\Users\vagrant\AppData\Local\Temp\sevenzip"
virtualbox-iso: ==> Downloading "http://www.7-zip.org/a/7z1600-x64.msi" to "C:\Users\vagrant\AppData\Local\Temp\sevenzip\7z1600-x64.msi"
virtualbox-iso: ==> Installing "C:\Users\vagrant\AppData\Local\Temp\sevenzip\7z1600-x64.msi"
virtualbox-iso: 2016-08-19 13:47:28 URL:http://www.7-zip.org/a/7z1600-x64.msi [8127/8127] -> "C:/Users/vagrant/AppData/Local/Temp/sevenzip/7z1600-x64.msi" [1]
virtualbox-iso: This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer package.
virtualbox-iso: ==> WARNING: Error 1620 was returned by: msiexec /qb /i "C:\Users\vagrant\AppData\Local\Temp\sevenzip\7z1600-x64.msi"
virtualbox-iso: ==> ERROR: Directory not found: "C:\Program Files\7-Zip"
virtualbox-iso: ==> Script exiting with errorlevel 1
virtualbox-iso:
virtualbox-iso: Pinging 127.0.0.1 with 32 bytes of data:
virtualbox-iso: Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
virtualbox-iso: Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
virtualbox-iso: Reply from 127.0.0.1: bytes=32 time=1ms TTL=128
virtualbox-iso: Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
virtualbox-iso:
virtualbox-iso: Ping statistics for 127.0.0.1:
virtualbox-iso: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
virtualbox-iso: Approximate round trip times in milli-seconds:
virtualbox-iso: Minimum = 0ms, Maximum = 1ms, Average = 0ms
==> virtualbox-iso: Unregistering and deleting virtual machine...
==> virtualbox-iso: Deleting output directory...
Build 'virtualbox-iso' errored: Script exited with non-zero exit status: 1

==> Some builds didn't complete successfully and had errors:
--> virtualbox-iso: Script exited with non-zero exit status: 1

==> Builds finished but no artifacts were created.
make: *** [box/virtualbox/eval-win2012r2-standard-nocm-1.0.4.box] Error 1


Have you come across this before?