11

I'm using Laravel Homestead with Vagrant, and I thought I'd would make sense to use Hyper-V instead of Virtual Box, as it's integrated with Windows.

Here is my problem. Every now and then my Hyper-v virtual machine will get a new IP, usually something 172.20.83.X. This messes up my hosts file as I assing ips to test domains. Thus every time my hyper-v server gets a new ip, I have to re-enter the new ip, which quickly becomes very tedious.

I'm wondering if there is a way I can set the ip to always be the same on my machine.

I tried tinkering with the virtual switch manager for the server, but ended up losing complete internet.

2
  • What type of virtual switch is yours? external? internal? or private?
    – S.Leon
    Commented Sep 3, 2018 at 6:50
  • @S.Leon, it's external :)
    – Jazerix
    Commented Sep 3, 2018 at 13:20

3 Answers 3

14

Background

The Asker, and anyone with a similar use-case, is likely operating on a workstation (not Windows Server edition), for local development purposes, and prefers not to run a DHCP server merely to prevent Hyper-V from changing IP address assignments on every host machine reboot.

The need to hard-code a static IP address within the VM's network configuration "manually", after provisioning, is cumbersome and stymies automation. Wouldn't it be far more preferable to automate the entire process, in keeping with the spirit of "disposable" development VMs and a hands-free provisioning workflow? Why, yes it would!

All of this hoop-jumping is necessary because Vagrant cannot (yet) set a static IP address for Hyper-V machines. See:

Overview

In short, the sequence of events to make this work is as follows:

  1. Create NAT switch for Hyper-V. (This is an Internal-only switch behind which any number of VMs with static IP addresses can sit and talk to each other, as well as the Hyper-V host. Guests behind this switch have outbound access to any network resources to which the host has access, such as the Internet or a LAN.)
  2. Configure a Vagrant Trigger that leverages the vagrant-reload plugin within the before-reload event to change a given VM's network switch on-demand.
  3. vagrant up, but choose Default Switch (not the new NATSwitch) on initial provision.
  4. At the beginning of the provisioning process, configure a static IP within the VM's operating system that is within the NATSwitch's range (this step is OS-specific).
  5. Call config.vm.provision :reload, which will a) fire the trigger defined in step 2, thereby changing the VM's network switch to the new NATSwitch; and b) issue vagrant reload and continue the provisioning process after the VM reboots.
  6. When the VM reboots, it will acquire the static IP address from the NATSwitch and use it indefinitely.

1. Create NAT Switch

While this step can certainly be done once, manually, it's even more powerful to bake the commands into a script that can be called from the Vagrantfile during provisioning. Such a script might look like this:

./scripts/create-nat-hyperv-switch.ps1:

# See: https://www.petri.com/using-nat-virtual-switch-hyper-v

If ("NATSwitch" -in (Get-VMSwitch | Select-Object -ExpandProperty Name) -eq $FALSE) {
    'Creating Internal-only switch named "NATSwitch" on Windows Hyper-V host...'

    New-VMSwitch -SwitchName "NATSwitch" -SwitchType Internal

    New-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceAlias "vEthernet (NATSwitch)"

    New-NetNAT -Name "NATNetwork" -InternalIPInterfaceAddressPrefix 192.168.0.0/24
}
else {
    '"NATSwitch" for static IP configuration already exists; skipping'
}

If ("192.168.0.1" -in (Get-NetIPAddress | Select-Object -ExpandProperty IPAddress) -eq $FALSE) {
    'Registering new IP address 192.168.0.1 on Windows Hyper-V host...'

    New-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceAlias "vEthernet (NATSwitch)"
}
else {
    '"192.168.0.1" for static IP configuration already registered; skipping'
}

If ("192.168.0.0/24" -in (Get-NetNAT | Select-Object -ExpandProperty InternalIPInterfaceAddressPrefix) -eq $FALSE) {
    'Registering new NAT adapter for 192.168.0.0/24 on Windows Hyper-V host...'

    New-NetNAT -Name "NATNetwork" -InternalIPInterfaceAddressPrefix 192.168.0.0/24
}
else {
    '"192.168.0.0/24" for static IP configuration already registered; skipping'
}

Then, add an appropriate trigger to the top of the Vagrantfile config section, which will ensure that the configuration is always correct upon vagrant up:

config.trigger.before :up do |trigger|
    trigger.info = "Creating 'NATSwitch' Hyper-V switch if it does not exist..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/create-nat-hyperv-switch.ps1"}
end

2. Configure Vagrant Reload Trigger

Rebooting (reloading) the VM in the middle of provisioning, in order to change over to a static IP address, requires the https://github.com/aidanns/vagrant-reload plugin, so install that first:

vagrant plugin install vagrant-reload

The script that the trigger will call is very simply:

./scripts/set-hyperv-switch.ps1:

# See: https://www.thomasmaurer.ch/2016/01/change-hyper-v-vm-switch-of-virtual-machines-using-powershell/

Get-VM "homestead" | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "NATSwitch"

Next, add an appropriate trigger to the Vagrantfile config section (i.e., just below the trigger added in the previous step):

config.trigger.before :reload do |trigger|
    trigger.info = "Setting Hyper-V switch to 'NATSwitch' to allow for static IP..."

    trigger.run = {privileged: "true", powershell_elevated_interactive: "true", path: "./scripts/set-hyperv-switch.ps1"}
end

3. Configure Static IP Within Guest VM

Configuring a static IP is an OS-specific task, so this procedure should be adjusted to suit the specific guest OS. Two examples follow.

NOTE: If on a corporate network, be sure to use the company's DNS servers, as Google's (shown in these examples) may not be reachable.

Ubuntu 18.04 LTS Example

On Ubuntu 18.04 LTS, this works well:

./scripts/configure-static-ip.sh:

#!/bin/sh

echo 'Setting static IP address for Hyper-V...'

cat << EOF > /etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: no
      addresses: [192.168.0.2/24]
      gateway4: 192.168.0.1
      nameservers:
        addresses: [8.8.8.8,8.8.4.4]
EOF

# Be sure NOT to execute "netplan apply" here, so the changes take effect on
# reboot instead of immediately, which would disconnect the provisioner.

RedHat, CentOS, and Oracle Linux Example

./scripts/configure-static-ip.sh:

#!/bin/sh

echo 'Setting static IP address for Hyper-V...'

cat << EOF > /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
IPADDR=192.168.0.2
GATEWAY=192.168.0.1
DNS1=8.8.8.8
EOF

# Be sure NOT to execute "systemctl restart network" here, so the changes take
# effect on reboot instead of immediately, which would disconnect the provisioner.

With the above script in place, add something like this just below the trigger definitions that we added earlier:

config.vm.provision "shell", path: "./scripts/configure-static-ip.sh"

config.vm.provision :reload

4. Provision the VM

vagrant up and choose Default Switch when prompted; this causes the VM to obtain a dynamic IPv4 address that is sufficient for Vagrant to connect to the VM via SSH, mount Shared Folders, and begin provisioning.

Now, the static IP address will be set and the VM reloaded before provisioning continues.

Sample output:

    homestead: Setting static IP address for Hyper-V...
==> homestead: Running provisioner: reload...
==> homestead: Running action triggers before reload ...
==> homestead: Running trigger...
==> homestead: Setting Hyper-V switch to 'NATSwitch' to allow for static IP...
    homestead: Running local script: ./scripts/set-hyperv-switch.ps1
==> homestead: Attempting graceful shutdown of VM...
==> homestead: Stopping the machine...
    homestead: Configuring the VM...
==> homestead: Starting the machine...
==> homestead: Waiting for the machine to report its IP address...
    homestead: Timeout: 120 seconds
    homestead: IP: 192.168.0.2
==> homestead: Waiting for machine to boot. This may take a few minutes...
==> homestead: Machine booted and ready!

5. Other Thoughts

  1. Manual input is required to choose the Default Switch after the initial vagrant up; it would be ideal to find a way around this.
  2. The vagrant-reload provisioner cannot shutdown the machine gracefully and must halt it forcibly; not a significant concern, given that it happens only once (during initial provisioning). This happens due to the fact that changing the VM's Hyper-V switch from Default Switch to NATSwitch must be done during the before-reload event, which is, in effect, akin to pulling the Ethernet cord out of a physical jack and connecting it to a different switch.
6
  • I did this, and it does indeed create a VM with a static Ip; but it doesn't have internet access. Any thoughts?
    – Hildy
    Commented Oct 21, 2019 at 17:04
  • @Hildy The most likely explanation is a network configuration issue in the guest OS. Does Get-VM "your-vm-name" | Get-VMNetworkAdapter show NATSwitch for the SwitchName and {OK} for the Status? Commented Oct 21, 2019 at 18:06
  • Indeed it does.
    – Hildy
    Commented Oct 21, 2019 at 19:04
  • Using the network script to change the guest OS, then triggering a vagrant reload with :reload works if you use the default switch and set IPADDR with in its range, then set the GATEWAY and DNS1 to both be the Ip of the default switch found with ipconfig on the host. But since the default switche's ip is not static, that's not entirely helpful.
    – Hildy
    Commented Oct 21, 2019 at 19:38
  • 1
    Ok, for you edification, the issue was that my machine is in a corporate network, and it doesn't seem to allow that Google DNS. I did an ipconfig /all in command, used one of the DNS addresses listed under the adapter my machine is using to plug into the network. Your instructions work otherwise.
    – Hildy
    Commented Oct 21, 2019 at 21:20
1

It seems that the external DHCP server with the scope of 172.20.23.X will provide IP addresses for your virtual machines. You can use filters in the DHCP console to prevent the DHCP server from issuing addresses to these machines by entering the mac address of the virtual machine. As shown below: enter image description here

enter image description here

Then you can use other DHCP to set reserved addresses for your virtual machine (my understanding is that you don't want to get the address in the 172.20.83.X scope, so use other DHCP) or manually configure static addresses so that their information is the same as the contents of your host file.

2
  • Also, make sure that you're not using a dynamic MAC address on your Hyper-V VM. This will cause you to get new IPs even with the DHCP reservation. You can check this under the Network adapters advanced settings...I believe, by default, they're set to dynamic.
    – essjae
    Commented Sep 7, 2018 at 18:19
  • This is in no way a criticism of your answer, as at least it provides "workaround" to be applied post-provisioning, but not to be able to specify a static IP address, programatically, while provisioning the VM, is a huge pain. Many workflows require creating and destroying these types of VMs routinely, so any degree of "manual" work required is problematic. Hopefully, Vagrant will address this via github.com/hashicorp/vagrant/issues/8384 Commented Nov 28, 2018 at 16:38
1

There is a workaround if you want to set switch:

config.vm.network "public_network", bridge: "VirtualSwitchName"

enhancement: hyper-v provider vswitch customization parameter #7915

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .