Skip to content




Vagrant

https://www.vagrantup.com/

Vagrant enables the creation and configuration of lightweight, reproducible, and portable development environments

I use Vagrant with Hyper-V to quickly prepare a test environment.

https://developer.hashicorp.com/vagrant/docs/providers/hyperv/limitations

Two limitations to note first:

  • cannot configure nic using Vagrantfile
  • cannot specify which virtual switch to use using Vagrantfile

There must be DHCP running on LAN, and the VM created by Vagrant will configure its NIC via DHCP through the virtual switch of the host Windows machine. Which virtual switch to use will be asked when Vagrant builds each VM.

installation

https://developer.hashicorp.com/vagrant/tutorials/getting-started/getting-started-install

# search and confirm the ID and version
winget search vagrant

# install
winget install Hashicorp.Vagrant

# confirm the installation
vagrant --version

quick start

# create a directory on desired drive
Set-Location F:\
mkdir vagrant
cd vagrant
mkdir test
cd test

# init using vagrant officially recommened ubuntu image, eol in 2023
vagrant init hashicorp/bionic64

# edit Vagrantfile

Vagrantfile

It's a ruby file to configure Vagrant. vagrant init command will generate a Vagrantfile to start working with.

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  config.vm.box = "hashicorp/bionic64"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Disable the default share of the current code directory. Doing this
  # provides improved isolation between the vagrant box and your host
  # by making sure your Vagrantfile isn't accessible to the vagrant box.
  # If you use this you may want to enable additional shared subfolders as
  # shown above.
  config.vm.synced_folder ".", "/vagrant", disabled: true

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.
  #
  # Hyper-V
  config.vm.provider "hyperv" do |h|
    h.vmname = "testubuntu"
    h.cpus = 2
    h.memory = 2048
    h.enable_virtualization_extensions = true
    h.linked_clone = true
    h.vm_integration_services = {
      guest_service_interface: true,
      heartbeat: true,
      shutdown: true,
      time_synchronization: true,
      key_value_pair_exchange: true,
    }
  end

  # Enable provisioning with a shell script. Additional provisioners such as
  # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end

build VM using Vagrant

Run vagrant up to start the VM build. Vagrant asks to choose the virtual switch to use. I select "External VM Switch" I created when setting up Hyper-V here.

vagrant up

# confirm status
vagrant status

# stop the VM
vagrant halt

# start the VM
vagrant up

box

Discover Vagrant Boxes

There seem to be many up-to-date boxes uploaded by "roboxes" with "generic" namespace such as "generic/ubuntu2210". See Roboxes Website here.

multiple VM

https://developer.hashicorp.com/vagrant/docs/multi-machine

https://developer.hashicorp.com/vagrant/docs/vagrantfile/tips#loop-over-vm-definitions

Multiple VMs can be build with a single Vagrantfile, vagrant up command.

I will create a set of 5 Debian VMs to create a Kubernetes cluster.

set-location f:\vagrant
mkdir k8s
cd k8s
vagrant init

Here is the Vagrantfile to build VMs named vcp and vworker1-4 using "generic/debian12" box.

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  # common config
  config.vm.provider "hyperv"
  config.vm.network "public_network"
  config.vm.synced_folder ".", "/vagrant", disabled: true
  config.vm.box = "generic/debian12"

  # control plane
  config.vm.define "vcp", primary: true do |vcp|
    vcp.vm.hostname = "vcp"
  end

  # worker node
  (1..4).each do |i|
    config.vm.define "vworker#{i}" do |node|
      node.vm.hostname = "vworker#{i}"
    end
  end

  # provider
  config.vm.provider "hyperv" do |h|
    h.cpus = 2
    h.memory = 2048
    h.enable_virtualization_extensions = true
    h.linked_clone = true
    h.vm_integration_services = {
      guest_service_interface: true,
      heartbeat: true,
      shutdown: true,
      time_synchronization: true,
      key_value_pair_exchange: true,
    }
  end

end

Here is the result.

╰─❯ get-vm

Name                             State   CPUUsage(%) MemoryAssigned(M) Uptime           Status             Version
----                             -----   ----------- ----------------- ------           ------             -------
k8s_vcp_1707806419070_95468      Running 0           2048              00:13:35.7240000 Operating normally 9.0
k8s_vworker1_1707806649152_50802 Running 0           2048              00:09:45.7320000 Operating normally 9.0
k8s_vworker2_1707806752033_33478 Running 0           2048              00:08:02.9010000 Operating normally 9.0
k8s_vworker3_1707806821736_75591 Running 0           2048              00:06:52.9660000 Operating normally 9.0
k8s_vworker4_1707806890423_84706 Running 0           2048              00:05:43.8540000 Operating normally 9.0


╰─❯ vagrant status
Current machine states:

vcp                       running (hyperv)
vworker1                  running (hyperv)
vworker2                  running (hyperv)
vworker3                  running (hyperv)
vworker4                  running (hyperv)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

snapshot

# save
vagrant snapshot save {vm name} {snapshot name}

# list
vagrant snapshot list {vm name}

# restore
vagrant snapshot restore {vm name} {snapshot name}

Example.

# save
╰─❯ vagrant snapshot save vworker4 init
==> vworker4: Snapshotting the machine as 'init'...
==> vworker4: Snapshot saved! You can restore the snapshot at any time by
==> vworker4: using `vagrant snapshot restore`. You can delete it using
==> vworker4: `vagrant snapshot delete`.

# list
╰─❯ vagrant snapshot list
==> vcp:
init
==> vworker1:
init
==> vworker2:
init
==> vworker3:
init
==> vworker4:
init

# restore
╰─❯ vagrant snapshot restore vworker4 init
==> vworker4: Attempting graceful shutdown of VM...
==> vworker4: Restoring the snapshot 'init'...
==> vworker4: Starting the machine...
==> vworker4: Waiting for the machine to report its IP address...
    vworker4: Timeout: 120 seconds
    vworker4: IP: 192.168.1.40
==> vworker4: Waiting for machine to boot. This may take a few minutes...
    vworker4: SSH address: 192.168.1.40:22
    vworker4: SSH username: vagrant
    vworker4: SSH auth method: private key
==> vworker4: Machine booted and ready!
==> vworker4: Setting hostname...