ლ(ಠ益ಠლ)

Vagrant on Windows: VirtualBox Network Configuration & Provisioning

This article explores Vagrant automation and VirtualBox networking (NAT/Bridging) configuration fundamentals on a Microsoft Windows operating system host.

Prerequisites

There is a Microsoft Windows installer available for Vagrant–you can download Vagrant here. In addition, Oracle VirtualBox should already be installed.

Note: The Vagrant Microsoft Windows installer will add ‘vagrant’ to your environment’s system path.

Initialization

Using PowerShell, create a new directory and, using Vagrant, initialize a new project:

1
2
3
4
5
PS C:\Users\hexdump\vagrant\centos> vagrant init
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

As the return output indicates, a Vagrantfile has been generated inside the directory. The primary function of the Vagrantfile is to describe the type of virtual machine required, and more importantly, how to configure and provision the virtual machine.

If you open the Vagrantfile in a text editor, most of the file is simply comments and noise–therefore, let’s generate a minimal Vagrantfile and expand on it–first, delete the existing Vagrantfile:

1
2
# PS Get-Help Remove-Item
PS C:\Users\hexdump\vagrant\centos> Remove-Item .\Vagrantfile

Generate a minimal file instead:

1
PS C:\Users\hexdump\vagrant\centos> vagrant init -m

Contents of minimal Vagrantfile:

1
2
3
Vagrant.configure(2) do |config|
  config.vm.box = "base"
end

Building upon this simple foundation, let’s expand on the contents, with the primary goal of building a CentOS virtual machine–edit the Vagrantfile:

1
2
3
4
5
6
7
8
9
10
11
# Source: https://atlas.hashicorp.com/chef/boxes/centos-6.6
Vagrant.configure(2) do |config|

  config.vm.box = "chef/centos-6.6"
  config.vm.hostname = "vagrant-centos01"

  config.vm.provider "virtualbox" do |v|
    v.name = "vagrant-centos01"
  end

end

This instructs Vagrant to reference HashiCorp’s Atlas box catalog and grab the chef/centos-6.6 box image.

In addition, this instructs Vagrant to apply a hostname as well as apply a “label” which VirtualBox can use to identify the virtual machine–in this case, both have a value of “vagrant-centos01”.

Build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS C:\Users\hexdump\vagrant\centos> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'chef/centos-6.6'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'chef/centos-6.6' is up to date...
==> default: Setting the name of the VM: vagrant-centos01
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/hexdump/vagrant/centos

How do we access this new virtual machine?

VirtualBox & Network Address Translation (NAT)

By default, Network Address Translation (NAT) is the default networking mode in VirtualBox.

In NAT mode, the guest network interface is assigned to the IPv4 range 10.0.x.0/24 by default, where x corresponds to the instance of the NAT interface +2. Therefore, x is 2 when there is only one NAT instance active.

With this configuration, the virtual machine receives its network address and configuration on the private network from a DHCP server integrated into VirtualBox. Typically, the first virtual network interface is connected to the private network 10.0.2.0/24, the second virtual network interface to the private network 10.0.3.0/24, and so on.

Which private network has the virtual machine been connected to?

The VirtualBox utility ‘VBoxManage’ is useful for this:

1
2
3
4
5
6
7
8
9
10
11
# .\VBoxManage.exe guestproperty enumerate <virtual-machine> --patterns "/VirtualBox/GuestInfo/Net/0/V4*"
PS C:\Program Files\Oracle\VirtualBox> pwd

Path
----
C:\Program Files\Oracle\VirtualBox

PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe guestproperty enumerate vagrant-centos01 --patterns "/VirtualBox/GuestInfo/Net/0/V4*"
Name: /VirtualBox/GuestInfo/Net/0/V4/IP, value: 10.0.2.15, timestamp: 1438584561055110200, flags:
Name: /VirtualBox/GuestInfo/Net/0/V4/Netmask, value: 255.255.255.0, timestamp: 1438584561055110202, flags:
Name: /VirtualBox/GuestInfo/Net/0/V4/Broadcast, value: 10.0.2.255, timestamp: 1438584561055110201, flags:

The above example virtual machine is connected to the private network “10.0.2.0/24” (CIDR, netmask 255.255.255.0), as expected. This private network is internal to VirtualBox and therefore invisible to the underyling Windows host.

Therefore, any and all network services on the guest are not accessible to the host (or to other computers on the same network).

Fortunately, much like a physical router, VirtualBox can make selected services available to the world outside the guest virtual machine through port forwarding.

Vagrant’s automation has performed the forwarding on our CentOS virtual machine, for us: “Host” port 2222 has been forwarded to “Guest” port 22.

Naturally, rather than using the VBoxManage utility to glean the virtual machine’s network configuration, we could have used a SSH client (Putty), and connected to 127.0.0.1 on port 2222:

1
2
3
4
5
6
7
# username: vagrant / password: vagrant
[[email protected] ~]$ ip a | grep inet | grep eth0
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0

[[email protected] ~]$ ip route show dev eth0
10.0.2.0/24  proto kernel  scope link  src 10.0.2.15
default via 10.0.2.2

Obviously, we can’t SSH to 10.0.2.15. Enter bridged networking.

Bridged Networking

Rather than provision virtual machines within private virtual networks, we can use a bridged networking configuration–our guests will be assigned an IP address from the same subnet as the Windows host.

With bridged networking, VirtualBox uses a device driver on your host system that filters data from your physical network adapter.

Configure Bridged Networking

Let’s re-work our Vagrantfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Source: https://atlas.hashicorp.com/chef/boxes/centos-6.6
Vagrant.configure(2) do |config|

  config.vm.box = "chef/centos-6.6"
  config.vm.hostname = "vagrant-centos01"

  config.vm.network "public_network", bridge: "<physical host interface>"

  config.vm.provider "virtualbox" do |v|
    v.name = "vagrant-centos01"
  end

end

Most likely, your Windows host has more than one physical network interface available to bridge against.

Per the documentation, the string identifying the desired interface must exactly match the name of an available interface.

Back to VBoxManage:

1
2
3
4
5
# .\VBoxManage.exe list bridgedifs | Select-String -Pattern "^Name:*"
PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe list bridgedifs | Select-String -Pattern "^Name:*"

Name:            Realtek PCIe GBE Family Controller
Name:            TAP-Windows Adapter V9

In this case, the physical host interface is called “Realtek PCIe GBE Family Controller”.

Back to the Vagrantfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Source: https://atlas.hashicorp.com/chef/boxes/centos-6.6
Vagrant.configure(2) do |config|

  config.vm.box = "chef/centos-6.6"
  config.vm.hostname = "vagrant-centos01"

  config.vm.network "public_network", bridge: "Realtek PCIe GBE Family Controller"

  config.vm.provider "virtualbox" do |v|
    v.name = "vagrant-centos01"
  end

end

Destroy the previous virtual machine (‘vagrant destroy’) and re-provision:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PS C:\Users\hexdump\vagrant\centos> vagrant destroy
    default: Are you sure you want to destroy the 'default' VM? [y/N] y
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...

PS C:\Users\hexdump\vagrant\centos> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'chef/centos-6.6'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'chef/centos-6.6' is up to date...
==> default: Setting the name of the VM: vagrant-centos01
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: bridged
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/hexdump/vagrant/centos

We now have a new bridged virtual interface (the NAT was also provisioned).

Virtual interface 0 (NAT):

1
2
3
4
5
# .\VBoxManage.exe guestproperty enumerate <virtual-machine> --patterns "/VirtualBox/GuestInfo/Net/0/V4*"
PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe guestproperty enumerate vagrant-centos01 --patterns "/VirtualBox/GuestInfo/Net/0/V4*"
Name: /VirtualBox/GuestInfo/Net/0/V4/IP, value: 10.0.2.15, timestamp: 1438587231706739900, flags:
Name: /VirtualBox/GuestInfo/Net/0/V4/Netmask, value: 255.255.255.0, timestamp: 1438587231707740100, flags:
Name: /VirtualBox/GuestInfo/Net/0/V4/Broadcast, value: 10.0.2.255, timestamp: 1438587231707240000, flags:

Virtual interface 1 (Bridged):

1
2
3
4
5
# .\VBoxManage.exe guestproperty enumerate <virtual-machine> --patterns "/VirtualBox/GuestInfo/Net/1/V4*"
PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe guestproperty enumerate vagrant-centos01 --patterns "/VirtualBox/GuestInfo/Net/1/V4*"
Name: /VirtualBox/GuestInfo/Net/1/V4/Broadcast, value: 192.168.1.255, timestamp: 1438587251741284000, flags:
Name: /VirtualBox/GuestInfo/Net/1/V4/Netmask, value: 255.255.255.0, timestamp: 1438587251741284001, flags:
Name: /VirtualBox/GuestInfo/Net/1/V4/IP, value: 192.168.1.16, timestamp: 1438587251740783900, flags:

The second (bridged) virtual network interface was assigned 192.168.1.16/24 via DHCP.

From the perspective of the virtual machine:

1
2
3
4
5
6
7
8
9
10
[[email protected] ~]$ ip a | grep inet | grep eth
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
    inet 192.168.1.16/24 brd 192.168.1.255 scope global eth1

[[email protected] ~]$ ip route show dev eth0
10.0.2.0/24  proto kernel  scope link  src 10.0.2.15

[[email protected] ~]$ ip route show dev eth1
192.168.1.0/24  proto kernel  scope link  src 192.168.1.16
default via 192.168.1.1

It’s tedious to query the VirtualBox provider for virtual network configuration information (or the edge firewall), so let’s instruct Vagrant to provide the bridged IPv4 address as returned Vagrant output within PowerShell, once the virtual machine has been provisioned.

Update the Vagrantfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Vagrant.configure(2) do |config|

  config.vm.box = "chef/centos-6.6"
  config.vm.hostname = "vagrant-centos01"

  config.vm.network "public_network", bridge: "Realtek PCIe GBE Family Controller"

  config.vm.provider "virtualbox" do |v|
    v.name = "vagrant-centos01"
  end

  config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"

  config.vm.provision "shell" do |s|
    s.inline = "echo $(ip -family inet a | grep 192 | awk '{print $2}')"
  end

end

Destroy any existing instance, and re-provision:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PS C:\Users\hexdump\vagrant\centos> vagrant destroy
    default: Are you sure you want to destroy the 'default' VM? [y/N] y
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...

PS C:\Users\hexdump\vagrant\centos> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'chef/centos-6.6'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'chef/centos-6.6' is up to date...
==> default: Setting the name of the VM: vagrant-centos01
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: bridged
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => C:/Users/hexdump/vagrant/centos
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: 192.168.1.13/24

As you can see, Vagrant ran our custom script as a part of its automation and returned the IPv4 address 192.168.1.13/24 as part of the Vagrant output within PowerShell.

Virtual Disk Size Information

Once you’re comfortable with Vagrant, you’ll want to be aware of your disk utilization.

To determine the initial disk usage of the vagrant-centos01 virtual machine, perform the following in PowerShell:

1
2
3
PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe list hdds | Select-String -Pattern "^Location"

Location:       C:\Users\hexdump\VirtualBox VMs\vagrant-centos01\packer-centos-6.6-x86_64-disk1.vmdk

This provides us the absolute location of the VMDK disk image file, then perform:

1
2
3
4
5
6
7
8
9
10
11
12
PS C:\Program Files\Oracle\VirtualBox> .\VBoxManage.exe showhdinfo "C:\Users\hexdump\VirtualBox VMs\vagrant-centos01\packer-centos-6.6-x86_64-disk1.vmdk"

UUID:           37ac779e-e0dc-4719-8c72-3bc1b3366bb1
Parent UUID:    base
State:          locked write
Type:           normal (base)
Location:       C:\Users\hexdump\VirtualBox VMs\vagrant-centos01\packer-centos-6.6-x86_64-disk1.vmdk
Storage format: VMDK
Format variant: dynamic default
Capacity:       40960 MBytes
Size on disk:   728 MBytes
In use by VMs:  vagrant-centos01 (UUID: 6360ea42-acc6-4c5e-a246-50b50a05aa6b)

Based on the above, the “Size on disk” is 728 megabytes–this value will vary depending on the base box image specified in your Vagrantfile.

In addition, the “Format variant” is dynamic. In dynamic mode, storage will be allocated on-demand, as it is needed. You will likely incur a disk I/O penalty as the VMDK expands, as long as the disk image file is using the dynamic allocation format.

Note: VirtualBox supports two types of image file formats: dynamic and fixed.

Vagrantfile Example Template (Reference)

The following is a rudimentary Vagrantfile, using the public Ubuntu image, allocating 2 GB of RAM, 2 vCPU(s) with a 90% execution cap, and utilizing a bridged network configuration.

Both the NAT and Bridged virtual interfaces have been set to use the adapter type virtio-net for improved performance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Vagrant.configure(2) do |config|

  # https://atlas.hashicorp.com/ubuntu/boxes/trusty64
  config.vm.box = "ubuntu/trusty64"

  config.vm.hostname = "vagrant-ubuntu01"

  config.vm.network "public_network", bridge: "Realtek PCIe GBE Family Controller"

  config.vm.provider "virtualbox" do |v|

    v.name = "vagrant-ubuntu01"
    v.customize ["modifyvm", :id, "--cpuexecutioncap", "90"]
    v.memory = 2048
    v.cpus   = 2

    # set bridged adapter to use (KVM) para-virtualized virtio-net
    v.customize ["modifyvm", :id, "--nictype1", "virtio"]
    v.customize ["modifyvm", :id, "--nictype2", "virtio"]

    # default is "headless"; to debug, set to true
    v.gui = false

  end

  config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'"

  config.vm.provision "shell" do |s|
    s.inline = "echo $(ip -family inet a | grep 192 | awk '{print $2}')"
  end

end

If you come across any interesting tips or tricks, please feel free to comment.

Happy rapid provisioning!

Comments