Skip to content

Instantly share code, notes, and snippets.

@dragon788
Last active November 26, 2019 18:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dragon788/d32c9fd6b504eef118d418e680a2bc29 to your computer and use it in GitHub Desktop.
Save dragon788/d32c9fd6b504eef118d418e680a2bc29 to your computer and use it in GitHub Desktop.
Cheap Windows VM alternate version

Consistent, Repeatable, Scriptable

If you've ever heard (or uttered), the phrase "but it works on my machine", then you have experienced one of the most common and pervasive challenges of distributed development. Since you are here you probably have a desire to overcome this common challenge of distributed development, ie having a consistent build environment that works the same way on a developer's machine as it does in the build system (Azure DevOps/Jenkins/GitLab CI/etc).

If the above paragraph has you convinced, TL;DR to the magic!

There have been many ways to work towards repeatable builds, here are a few.

  • CocoaPods (iOS dependency package management)
  • Nuget packages (.NET dependencies)
  • package-lock.json (npm/yarn for Javascript dependencies)
  • requirements.txt or Pipfile (Python dependencies)
  • ad nauseam repetition for every language out there (Go/Ruby/Elixir/Haskell/etc)
  • Dockerfile (allows combining multiple of the above into a project specific build/development environment
  • Vagrantfile (like Dockerfile, but utilizes a full VM for more isolation from host changes)

The last two are the most powerful/useful as they can be used to validate and prove the repeatability of all the previous methods.

We'll start with Vagrant as it is has less sharp edges to poke ourselves with.

Vagrant

The basic requirements are Vagrant (available from vagrantup.com or for easier automation of the process, through your system's package manager, ie Homebrew (https://brew.sh for macOS or https://linuxbrew.sh for Linux) or Chocolatey (https://chocolatey.org for Windows) and then a virtualization tool like Virtualbox (from https://virtualbox.org or see any of the above package management tools), Parallels (requires paid license, get from https://parallels.org for macOS), VMware Workstation/Fusion (requires paid license from https://vmware.com/desktop as well as a paid Vagrant plugin from https://vagrantup.com/vmware), or Hyper-V (available in Windows 10).

Unless you have some special requirements for extreme performance or already have a license for Parallels/VMware you can use Virtualbox quite successfully.

TL;DR brew cask install vagrant virtualbox or choco install -y vagrant virtualbox or for Linux apt install virtualbox and grab the AppImage from https://vagrantup.com and put it somewhere.

Vagrant basics

There are two main sources for boxes:

  • You can use an existing prebuilt box from the Vagrant site though some of the boxes may be hosted externally (ie Microsoft hosts their own images, Ubuntu hosts their own images), but many others uploaded their boxes directly to the service before Hashicorp started charging for that privilege or are exempt for paying due to being an open source project or similar.
  • You can build your own image from a template using Packer, and really this is MUCH easier than it sounds.
    • This the best way to get a fully up to date VM and is actually the best legal way to get a VM of certain operating systems like Windows or macOS or Red Hat Enterprise Linux.
    • "Hosting" a box you've built is pretty easy, you can simply reference the direct URL to the box on a storage service (S3/Azure Blob Storage/etc), but for the nicest user experience you can generate a metadata.json that describes a bit more about your box like which providers (virtualization tools) it supports, and what "version" of the box it is, whether that is the date it was last built and/or patched or the major/minor operating system version contained in the VM (having both bits can be even more handy).

Once you've picked a box like the Chef Bento Ubuntu 18.04 (or built one), you can cd to the directory for your project and then either vagrant init bento/ubuntu-18.04 or if you've built your box you will need to vagrant add --name MyBox /path/to/your/MyBox.box and then vagrant init MyBox.

Before you run vagrant up you should examine the Vagrantfile generated by the vagrant init as there are a TON of comments, not all of them are applicable, and for those who don't use the command line a lot, you will probably want to uncomment the vm.provider virtualbox do |vb| and the matching end line and then uncomment the vb.gui line and change it to true if you want to interact with the GUI inside the VM instead of via CLI using SSH. Whether there is a GUI to interact with depends a lot on the scripts that went into building the box, the Bento Ubuntu for example probably doesn't have a GUI, but if you build a Windows template you pretty much can't not have a GUI unless you are doing something special like Nanoserver or Server Core. You will also see that you can increase the amount of RAM/cores that the VM can use, but not the storage. Most VMs use a dynamically growing virtual drive that has a specific cap on the maximum size based on the template used to build it, but 60GB is pretty common. Once you've made your desired modifications you can run vagrant up. This takes a while whether you are pulling down a public box or built your own, though the downloading tends takes a lot longer than the simple extraction into your workspace that happens after you've built your own.

So what is the advantage of this over installing the hundreds (thousands?) of packages required to build your project on your host machine?

If you were paying attention when looking in the Vagrantfile, you may have noticed a section like vm.provision, this defines a script or command that is run once after the initial vagrant up, but can also be called explicitly (hopefully it is idempotent in that case), with vagrant reload --provision or if you have run vagrant halt you can run vagrant up --provision. This allows you to automate setting up your application's development environment and if something starts getting really weird and you are having trouble figuring out why, you can run vagrant destroy and then vagrant up to start over from scratch and see if one of the dependencies didn't install or if it was some change you had made after the initial setup that broke things. There is also a snapshot command built into Vagrant that allows you to make a checkpoint that you can revert back to in case you are going to do something that you know might be destructive, like an upgrade of ALL 1,358 npm packages that your project has pulled in.

Vagrant Build Your Own Adventure Box

So you tried some of the public boxes but they didn't quite do what you wanted, or you needed something different and trying to change that box broke everything or required running a provision script that basically rebuilt the world? As mentioned before it is pretty easy to build a box yourself without a lot of drama.

Again there are a couple options, but the two most commonly used are:

  • vagrant package which takes an existing box that you have made some customizations to (hopefully repeatably) but now you want to output a new box so your team doesn't have to wait 2 hours for all your custom stuff to run.
  • packer build --only virtualbox-iso mybox.json which uses the Packer tool and a template file to install the operating system and configure it to your liking, though it is also possible to reuse an existing box in this scenario as well.

Packer

Packer is an extremely useful tool also built by the folks at Hashicorp, and it makes building a consistent base image easy and repeatable, and it supports outputting for all the providers mentioned before, but it can also build and deploy an AWS AMI, an Azure VM, a Rackspace VM, or many many other types of images (including Docker ones...).

You can install it like Vagrant, by using your favorite package manager, brew install packer or choco install -y packer.

Once you have it installed you may want to find some existing templates to draw inspiration from. The best type of template is one that is popular and well maintained, ie has code commits in the last 2-3 months, because in software as in life, the pursuit of perfection is neverending.

The best examples I've come across have been the Chef Bento project (where Chef builds VMs of all the client operating systems that their configuration management software supports for continuous testing), and the Boxcutter project (which sadly has greatly slowed in development/improvements, but still has a few good tricks up their sleeves in their scripts). There are some other good examples on GitHub if you search packer template SomeOS but sometimes you'll run into weird issues depending on whether they were trying to work around issues in tooling that has since been patched. For Windows mwrock aka Matt Wrock has some good blog posts and templates, but he hasn't always updated them for the latest releases of Windows 10 or Windows Server because he moved out of the front lines of building/testing into a different role.

The Magic

A basic example of building your own Windows 10 Enterprise evaluation VM using the things I've explained above (good for 90 days before you have to recreate and/or rebuild with vagrant destroy and vagrant up, saving your data elsewhere will be covered later).

To recap what you TL;DR'd past, you will need vagrant, virtualbox, and packer installed. macOS having installed Homebrew from https://brew.sh brew cask install vagrant virtualbox ; brew install packer Windows having installed Chocolatey from https://chocolatey.org and closed and reopened an "elevated" Powershell prompt (right click and 'Run as Administrator') choco install -y vagrant virtualbox packer

cd $HOME
mkdir projects
cd projects
git clone https://github.com/chef/bento chef-bento-packer-templates
cd chef-bento-packer-templates
cd packer_templates
cd windows
packer build --only virtualbox-iso windows-10.json
# This takes a LONG time, but a recently merged fix should at least allow it to complete
# After the build is complete there should be a windows-10-virtualbox.box in this directory
vagrant box add --name Win10EntEval windows-10-virtualbox.box
cd $HOME/projects
vagrant init Win10EntEval
vagrant up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment