hashicorp / vagrant

Vagrant is a tool for building and distributing development environments.
https://www.vagrantup.com
Other
26.28k stars 4.43k forks source link

Discussion: Static IP address for Hyper-V #8384

Open bbrala opened 7 years ago

bbrala commented 7 years ago

I was looking into setting a static ip address for the machine's but im not entirely sure how we would go about that.

There are a few different ways to setup your network switch and would we implement different ways to set the ip address based on the network configuration?

Currently Vagrant doesnt manage the switches in any way, should we even try and start doing that.

When it is a extenal switch you basically part of the same network as the host, which usually means there is DHCP and a static IP is limited to your subnet. Also there is the possibility of collisions.

An internal switch has possiblities, but would require the user to share his internet on his adapter manually to get internet on the machine.

Another possibility is doing an internal switch and create a nat network, which would give some flexiblity, but i know that docker does this also and would probably conflict with that since windows can only handle one NAT network.

Anyways, i'm not entirely sure what the best way to go about it is.

Perhaps you have some insight @PatrickLang :)

PatrickLang commented 7 years ago

Yeah, I've been thinking about this. My goal is to get @StefanScherer 's windows10-docker-swarm working on Hyper-V.

Things that I know need to change:

  1. Actually implement networks. Here's some notes on what needs to change https://github.com/PatrickLang/vagrant/commit/d26e3fe0b5857acaf7e7dd5f27484fb11de8e1d2.
    • I will probably submit a doc update to explain Hyper-V networking support as it is today
  2. Support creating a private_network
  3. Set private IPs using one of:

Questions:

Do you have any opinions on 3? It seems like the guest plugins support setting IPs for more operating systems than the Hyper-V Integration Components, but I have been struggling to find docs and examples of how to use them.

If a VM was exported with multiple NICs - should those settings (including MAC & IP) be preserved when importing it, or should the network configuration in the Vagrantfile take precedence? As I implement 1, I haven't decided if I should keep or delete existing NICs. When I'm trying to build up a lab with precise network settings I think I would rather create new NICs with the exact settings instead of trying to reuse the old ones.

bbrala commented 7 years ago

I've looked into how Vagrant does things right now and from what i understand, it indeed has implementations for setting the network for the guests. I would probably look at how the implementation for VirtualBox is and use that for Hyper-V. This will keep the basic code for setting the network in the guest the same which i feel is a pro.

https://github.com/mitchellh/vagrant/blob/9c299a2a357fcf87f356bb9d56e18a037a53d138/plugins/providers/virtualbox/action/network.rb#L132

I think there is a lot of logic there that can be reused for the Hyper-V logic. We only would need to be very carefull in what different options there are on the machines of the users. I really think we can screw up the networking of the users if we do too much.

I've even had moments when i was manually setting up my network for my VM that i screwed up my network settings after enabling or disabling Hyper-V networks. We don't want that to happen to an unsuspecting user.

Regarding the deletion of NIC's, i feel you should be carefull deleting nics. Perhaps make it an option because unless you make every little think configurable in the config file you might not be able to conserve a specific setup a box might need. I really think rebuilding the NIC's from scratch always will become a problem in the future, and for most people they just need the VM as close to how it was exported as possible, since that was how the software was set up for them.

When i implemented support for VMCX i started of with rebuilding the VM from the settings of the old one, but that really ends up making things harder and more code.

s0lucien commented 7 years ago

I have achieved this using https://sourceforge.net/projects/dhcpserver/ and some powershell scripts that manage it. I am not sure whether it's the right path to go , or whether it's fine to add it as a dependency but it works pretty well without any bugs. If you are interested, i could spend some time adding it into a fork.

The advantage is that it can be used for any Win/Linux box, since they all ask for an IP address via DHCP

What it does is : 1) create internal switch (using a naming scheme which involves the machine name, so it is possible to keep track of it) 2) write the info in the OpenDHCP INI file (hyper-V will give the VM some MAC address, it is possible to retrieve it at run-time using powershell. Using the MAC address it is possible to let the DHCP server know it's supposed to assign a specific IP address to it) 3) start the dhcp server on the host endpoint of this internal switch (it runs as a service)

bbrala commented 7 years ago

I'm not sure adding another dependency is really the way to go.

From what i understand in the new Windows 10 update there has been an update to NAT networking, this allows for multiple NAT networks to exist, which makes the whole static IP problem quite a lot easier since we can control the NAT network as we see fit.

https://blogs.technet.microsoft.com/virtualization/2017/04/13/whats-new-in-hyper-v-for-the-windows-10-creators-update/

Multiple NAT networks and IP pinning

NAT networking is vital to both Docker and Visual Studio’s UWP device emulators. When we released Windows Containers, developers discovered number of networking differences between containers on Linux and containers on Windows. Additionally, introducing another common developer tool that uses NAT networking presented new challenges for our networking stack.

In the Creators Update, there are two significant improvements to NAT:

Developers can now use for multiple NAT networks (internal prefixes) on a single host. That means VMs, containers, emulators, et. al. can all take advantage of NAT functionality from a single host. Developers are also able to build and test their applications with industry-standard tooling directly from the container host using an overlay network driver (provided by the Virtual Filtering Platform (VFP) Hyper-V switch extension) as well as having direct access to the container using the Host IP and exposed port.

I think if we can use this to create an easy quick internal network for Vagrant to use for the machine. It should check for overlap in NAT networks though, since that might cause problems. If a NAT already exists which would serve that IP then just use that. Otherwise create a network.

Cleaning up should be a breeze if we use well defined names for the network.

The only catch is you need a recent version of Windows 10. But i feel that this should not really be an issue that stops Vagrant from having this.

chrisroberts commented 7 years ago

The only catch is you need a recent version of Windows 10. But i feel that this should not really be an issue that stops Vagrant from having this.

I think the important part to this is ensuring graceful fallbacks (even if it's just explanatory error messages) for when the feature is not available. I'm very much in agreement with the approach though and prefer it over adding more dependencies.

bbrala commented 7 years ago

@chrisroberts ok, fair enough, i will try and find some time to get something up and running. I agree on the fallbacks ofcourse.

@PatrickLang I've run through your linked commit, is there any reason why you want support for multiple networks, i haven't really seen machines that need multiple networks really.

PatrickLang commented 7 years ago

@bbrala multiple networks are very helpful when working with Windows Active Directory where I need to have a tightly integrated Kerberos, DNS, and DHCP setup spanning multiple VMs.

I typically have 1 network that's "public" relying on DHCP & routing to the public internet such as public wifi. The other network is private just for the machines using the AD infrastructure.

Here's an example of a doc I'm doing at work targeted to Azure, but I'd like to fully automate it with Packer+Vagrant so others can run it on a Win10 laptop.

GuyPaddock commented 7 years ago

Also useful, as @bbrala pointed out, is that HyperV has support for a NAT (with port forwarding) under the hood. Here's how to use it in Windows 10: http://www.thomasmaurer.ch/2016/05/set-up-a-hyper-v-virtual-switch-using-a-nat-network/ https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/setup-nat-network

bbrala commented 7 years ago

@PatrickLang Hmm, i'll think about how to go about doing it for multiple network interfaces, but perhaps this should be built in 2 steps. First one interface, then add the possibility for a second one. Although i could make sure we use a hash of the settings or something to identify the network interface. Not sure yet.

Time is a little short at the moment, but I will have a go at this eventually :)

bbrala commented 7 years ago

Walking through the docs and code for networking i noticed multiple networks is supported in VirtualBox, think it would be weird to change the interface much for HyperV so we need multiple networks right away.

Other than that i'm still a little torn in how smart we would make the code. I'm not entirely sure if for example Vagrant with virtualbox does anything to fix/find collisions in private networks. If you make 2 overlapping networks that would be created in VirtualBox for example, but i guess that isn't that hard to just test.

PatrickLang commented 7 years ago

I forked the NAT topic to a new issue. I think there may be a quick option to implement that first while we think about multiple nic and static ip here.

bbrala commented 7 years ago

@mwhooker; you did the implementation of HyperV for packer. Do you have any insight on this chicken/egg issue regarding static IP's?

mwhooker commented 7 years ago

@bbrala regretfully, I only merged the PR. @taliesins is the brains behind the endeavor. Maybe he knows?

bbrala commented 7 years ago

Ah sorry, it has been a while, i remembered incorrectly, sorry about that :)

taliesins commented 7 years ago

@bbrala two potential approaches are:

  1. Scripts set ip address of nics. No changes to Packer.
  2. Assign mac address to nics, and then use dhcp with ip reservation (this is the one I prefer, as it means base box will not need to be modified). Changes required to Packer builders.

We then need to change Packer to add an option to specify the management ip address to use instead of it always polling the hypervisor api for the ip address.

anlx-sw commented 7 years ago

to contribute to this discussion - i played around with vagrant and hyper-v lately.

since windows server 2012 hyper-v allows to set the ip of guests from the host: http://www.ravichaganti.com/blog/set-or-inject-guest-network-configuration-from-hyper-v-host-windows-server-2012/

i tested this script method with a linux guest on my hyper-v and it worked as proposed.

there also seems to be a workaround to set the vswitch used mentioned in this discussion https://github.com/mitchellh/vagrant/issues/7915 config.vm.network "public_network", bridge: "VirtualSwitchName"

it is possible to create a NAT-Vswitch via Powershell: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/setup-nat-network

so i think all pieces to automatically create a nat-vswitch (or use a pre-created one) and apply the config to the vagrant guest would be there. so the same things which are done with virtualbox could be done with hyper-v.

or am i missing any limitations on the hyper-v side?

bbrala commented 7 years ago

The script you posted doesn't work for me in Windows 10 unfortunately. We did have a look at setting ip's through hyperv but OS support is a bit limited regarding that. Perhaps we need to accept that fact for now.

carlpett commented 7 years ago

This Hyper-V post contains an interesting piece about new functionality ahead: link. In the comments they mention that this new "special" virtual switch will do DHCP.

bbrala commented 7 years ago

Good catch, that will make things a lot easier. Patience i guess

rogermartensson commented 7 years ago

Is there any progress in this? I would like to get setting at static IP working since I need to do this manually today. DHCP is not a blocking issue for me.

I already have a NAT-enabled vSwitch (via Docker) and I think that vagrant should error-out if the vSwitch IP and VM-IP is not in the same subnet (if possible).

Creation of NAT-enabled vSwitches are also documented on blogs around the world. So if no NAT-enabled vSwitch can be found and used then maybe it should be created. Windows 10 does not support more than one NAT-enabled vSwitch but I think I saw something about it getting support for it in the future? (then this possibility should be checked before creation/use)

bbrala commented 7 years ago

The new Fall Creators update might help us getting this to work. There now is a Default switch available which is always present. This switch would enable us to connect to the VM regardless of current configuration on the host. This means we could actually connect and set the IP address on the selected switch without a problem.

This would mean some refactoring though, since this would only work on machines with Windows 10 Fall creators update. Also i'm not entirely sure yet how we would implement using that interface as a managing interface for the VM. I would need to look into how the current VirtualBox implementation does this. From what i remember it always requires a NAT switch for exactly that reason.

The update is pretty fresh, i haven't had time to look into this yet but i feel we could get this implemented using the new default switch.

rogermartensson commented 7 years ago

VirtualBox implementation requires two NICs. First one is host-only and the second is used for communicating with "the world". VirtualBox can also use an Internal Network that is ever more strict than host-only. Is hyperv set up the same way?

Host-only sounds really like a hyper-v internal network with the infamous exception of missing DHCP. The reason "vagrant up" works at the moment seems to be that we get an ipv6-address that is used during setup. (ubuntu vms in my case)

So if first nic is management only maybe create a vSwitch with Internal Network if one doesn't exists? or maybe create a vagrant internal network that is always used with or without the Default Switch?

The public network is then later used to connect to whatever network that is needed. May it be internal, private och external. If Default Switch exists and no config says otherwise, then nic 2 is connected to it. If no default switch is found, nic2 is in "unconfigured" state and Vagrantfile needs be be updated with right vSwitch to use.

Since we have no DHCP you Have to insert both private and public ip# in Vagrantfile.

My ruby is almost non-existent but my powershell is most acceptable. Since I would like this to get better I will try to test whatever you come up with. :)

gurry commented 6 years ago

Has any work been done on this guys?

PatrickLang commented 6 years ago

I haven't done anything yet. I was hoping the NAT changes in the Windows 10 & Windows Server 1709 release would make this easier but not quite yet.

Here's my current proposal:

I think I may be able to make this work for a second NIC. The first NIC would still use an external switch or the Windows built-in nat on "Default Switch". This would give the VM outbound connectivity. The second NIC could use a static IP on a private network. This would be good for labs where you want the VMs networked to each other with static IPs.

Would that be helpful for your case?

gurry commented 6 years ago

Yeah. This would work for me.

StefanScherer commented 6 years ago

The second NIC is used in the docker-swarm scenario for VMware as well. The Vagrant VMware plugin cannot handle setting a fixed IP address in Windows VM's, but my workaround to do this in a provision script https://github.com/StefanScherer/windows10-docker-swarm/blob/master/Vagrantfile#L46 scripts/fix-second-network.ps1 helps at least for Workstation and Fusion.

jurosh commented 6 years ago

Sorry to join discussion without additional tech info, but would like to kindly ask if there is any progress, or plans? As more of you noted, there is way how to make it work (and be fast) with two NICs like is in VirtualBox so curious if this is might be priority in near future. For me this is (currently) the most annoying thing while doing development on Windows, cause I need to always manually update hosts file and few other configs with automatic IP from hyperv.

bbrala commented 6 years ago

I'm not sure there is people working on this right now. At least, i'm not. Perhaps i can help by sharing how we handle this at the moment. Since we also need to update the hostfiles we use a plugin to do that for us.

The plugin is vagrant-hostmanager.

In our vagrant file:

if Vagrant.has_plugin?("vagrant-hostmanager")

    require_relative "lib/project_hosts"
    config.hostmanager.enabled = false
    config.hostmanager.manage_host = true
    config.hostmanager.manage_guest = true
    config.hostmanager.ignore_private_ip = false
    config.hostmanager.include_offline = true
    config.hostmanager.aliases = PROJECT_HOSTS

    config.vm.provision :hostmanager, run: 'always'
  end

The lib/project_hosts file, which reads all directories above the vagrant repository to add those to our hostfile.

projects_dir = File.expand_path('../..', File.dirname(__FILE__))

# Get list of all directories in folder above vagrant project repository.
paths = Dir.entries(projects_dir).select {|entry| File.directory? File.join(projects_dir,entry) and !(entry.start_with?(".")) }

PROJECT_HOSTS = Array.new
paths.each do |path|
  PROJECT_HOSTS.push("#{path}.dev.tld")
end

require 'json'
extra_hosts_json = File.read('provision/configuration/hosts.json')
extra_hosts = JSON.parse(extra_hosts_json)

PROJECT_HOSTS.push(*extra_hosts)

And provision/configuration/hosts.json for some hardcoded uri's

[
  "testing.weirddomain.dev.tld",
  "dev.tld"
]

Perhaps this helps.

chrisroberts commented 6 years ago

Hi everyone,

Just wanted to drop a note that I'm going to be picking up a handful of Hyper-V related tasks over the next few weeks and this will be one of them.

Cheers!

bbrala commented 6 years ago

That is awesome to hear.

chrisls121 commented 6 years ago

This is amazing news, thank you!

taliesins commented 6 years ago

@chrisroberts can you drop a proposal here before starting the work. It would be interesting to see how you are going to tackle this. This might be usable in Packer HyperV and Terraform HyperV (wip).

chrisroberts commented 6 years ago

@taliesins Yep, I'm planning on it.

HarishAP88 commented 6 years ago

You can assign IP to HyperV VM using the below command for both Windows and Linux. I have automated and assigned for almost 300 VMs successfully.

For Windows use the below command: Note:

  1. .Net frame work 3.5.1 need to be installed on the 2008 r2 Server.
  2. Hyper-v Integration services has to be installed.
  3. For 2012 its already having latest version of .netframework.
  4. Please provide instance ID of the network adapter to assign particular nic, Command is below $NetworkID=Get-VMNetworkAdapter -VMName testvm | Select -ExpandProperty ID
  5. Execute the command on Hyperv Host of the VM to assign IP.

$vmName = "TestVM"; $vmHost = "HypervHost"; $ipAddresses="IP";; $NetworkID="InstanceID of Nic" #"Microsoft:8721894F-7E0D-4863-9A49-74B2A126618F\DA267016-02D6-40E3-B50C-4F0E646DE1CC"; $subnets ="255.255.255.0"; $Defaultgateways= "gataeway"; $DNSservers ="DNS"; $DNS1="10.10.10.10"; $Msvm_VirtualSystemManagementService = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_VirtualSystemManagementService -ComputerName $vmHost ; $Msvm_ComputerSystem = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "ElementName='$vmName'" -ComputerName $vmHost ; $Msvm_VirtualSystemSettingData = ($Msvm_ComputerSystem.GetRelated("Msvm_VirtualSystemSettingData", "MsvmSettingsDefineState", $null, $null, "SettingData", "ManagedElement", $false, $null) | % {$}); $Msvm_SyntheticEthernetPortSettingData = $Msvm_VirtualSystemSettingData.GetRelated("Msvm_SyntheticEthernetPortSettingData"); $Msvm_GuestNetworkAdapterConfiguration = ($Msvm_SyntheticEthernetPortSettingData.GetRelated( "Msvm_GuestNetworkAdapterConfiguration", "MsvmSettingDataComponent", $null, $null, "PartComponent", "GroupComponent", $false, $null) | % {$}); foreach($Instanc in $Msvm_GuestNetworkAdapterConfiguration){ if($NetworkID.Replace("Microsoft:","Microsoft:GuestNetwork\") -eq $Instanc.InstanceID) { $Instanc.DHCPEnabled = $false; $Instanc.IPAddresses = @("$ipAddresses"); $Instanc.Subnets = @("$subnets"); $Instanc.DefaultGateways = @("$Defaultgateways"); if($DNS1){ $Instanc.DNSServers = @($DNSservers,$DNS1); } else{ $Instanc.DNSServers = @("$DNSservers"); } $out=$Msvm_VirtualSystemManagementService.SetGuestNetworkAdapterConfiguration($Msvm_ComputerSystem.Path,$Instanc.GetText(1)); $out1=$out.ReturnValue | out-string; echo $out1; break; } }

For Linux use the below command: below are the pre requisites

  1. Install RedHat Hyperv Deamons.
  2. Install SCVMM agent on the VM.
  3. Make sure Microsoft LIS has to be uninstalled before installing redhat deamons

Command as below:

$VM="TestVM"; $IP="10.10.10.10"; $Subnet="255.255.255.0"; $Gateway="10.10.10.1"; $DNS="8.8.8.8"; Function Set-VMNetworkAdapterConfiguration { [CmdletBinding()] Param ( [Parameter(Mandatory=$true, Position=1, ParameterSetName='DHCP', ValueFromPipeline=$true)] [Parameter(Mandatory=$true, Position=0, ParameterSetName='Static', ValueFromPipeline=$true)] [Microsoft.HyperV.PowerShell.VMNetworkAdapter]$NetworkAdapter,

    [Parameter(Mandatory=$true,
               Position=1,
               ParameterSetName='Static')]
    [String[]]$IPAddress=@(),

    [Parameter(Mandatory=$false,
               Position=2,
               ParameterSetName='Static')]
    [String[]]$Subnet=@(),

    [Parameter(Mandatory=$false,
               Position=3,
               ParameterSetName='Static')]
    [String[]]$DefaultGateway = @(),

    [Parameter(Mandatory=$false,
               Position=4,
               ParameterSetName='Static')]
    [String[]]$DNSServer = @(),

    [Parameter(Mandatory=$false,
               Position=0,
               ParameterSetName='DHCP')]
    [Switch]$Dhcp
)

$VM = Get-WmiObject -Namespace 'root\virtualization\v2' -Class 'Msvm_ComputerSystem' | Where-Object { $_.ElementName -eq $NetworkAdapter.VMName } 
$VMSettings = $vm.GetRelated('Msvm_VirtualSystemSettingData') | Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }    
$VMNetAdapters = $VMSettings.GetRelated('Msvm_SyntheticEthernetPortSettingData') 

$NetworkSettings = @()
foreach ($NetAdapter in $VMNetAdapters) {
    if ($NetAdapter.Address -eq $NetworkAdapter.MacAddress) {
        $NetworkSettings = $NetworkSettings + $NetAdapter.GetRelated("Msvm_GuestNetworkAdapterConfiguration")
    }
}

$NetworkSettings[0].IPAddresses = $IPAddress
$NetworkSettings[0].Subnets = $Subnet
$NetworkSettings[0].DefaultGateways = $DefaultGateway
$NetworkSettings[0].DNSServers = $DNSServer
$NetworkSettings[0].ProtocolIFType = 4096

if ($dhcp) {
    $NetworkSettings[0].DHCPEnabled = $true
} else {
    $NetworkSettings[0].DHCPEnabled = $false
}

$Service = Get-WmiObject -Class "Msvm_VirtualSystemManagementService" -Namespace "root\virtualization\v2"
$setIP = $Service.SetGuestNetworkAdapterConfiguration($VM, $NetworkSettings[0].GetText(1))

if ($setip.ReturnValue -eq 4096) {
    $job=[WMI]$setip.job 

    while ($job.JobState -eq 3 -or $job.JobState -eq 4) {
        start-sleep 1
        $job=[WMI]$setip.job
    }

    if ($job.JobState -eq 7) {
        Write-Output "Success"
    }
    else {
        $job.GetError()
    }
} elseif($setip.ReturnValue -eq 0) {
    Write-Output "Success"
}

} Get-VMNetworkAdapter -VMName "$VM" | Set-VMNetworkAdapterConfiguration -IPAddress $IP -Subnet $Subnet -DefaultGateway $Gateway -DNSServer $DNS

Cheers guys..

schnyders commented 5 years ago

Sorry to join discussion without additional tech info, but would like to kindly ask if there is any progress, or plans?

s0lucien commented 5 years ago

I can still submit a PR with my working solution using the external DHCP server if there is any interest in it.

cbj4074 commented 5 years ago

@s0lucien If it works, I'm interested, and I suspect I'm not alone.

Whether or not your PR will be merged is anybody's guess, but something that works is a lot better than nothing at all, especially given that this proverbial can has been kicked down the road into 2.2, which has no Due Date as yet.

If it's not too much trouble, I, for one, would love to see your fork, and would use it until this is addressed to whatever degree of elegance Hyper-V affords.

And I'm not holding my breath for Microsoft to give the Vagrant developers something to work with here, given the myriad problems that Hyper-V's switching implementation has incurred as recently as build 1809.

schnyders commented 5 years ago

I think with the new "Typed Triggers" there should be also a way to set a static IP address. As we should be able to start a script on the host that will set the guest IP. But up to now, I was not able to get them working (Typed Triggers), and yes I've added the VAGRANT_EXPERIMENTAL="typed_triggers" env variable.

schnyders commented 5 years ago

Got it working with something like that

config.trigger.before :"VagrantPlugins::HyperV::Action::WaitForIPAddress", type: :action do |t| t.info = "-----------------------------------Configure IP----------------------------" t.run = { inline: "scripts/SetStaticIP.ps1 -VirtualMachine \"SRV0001\" -Username \"vagrant\" -Password \"vagrant\" -IPAddress \"172.16.10.1\" -PrefixLength \"24\" -DefaultGateway \"172.16.10.254\" -DNSServer \"172.16.10.1\""
} end

my next issue is, that it looks like that these type of triggers do not work together with config.vm.define method

cbj4074 commented 5 years ago

@schnyders Are you able to elaborate upon your approach at all?

Specifically, what is that SetStaticIP.ps1 script doing under-the-hood? Are you able to share the source for that script, by chance?

cbj4074 commented 5 years ago

I took the time to get this "working" and posted a comprehensive how-to:

https://superuser.com/a/1379582/176764

All the required networking-related Powershell is there (and it's far less verbose than what's described in https://github.com/hashicorp/vagrant/issues/8384#issuecomment-410985566 ).

They key is not to use Hyper-V's Default Switch, specifically because its DHCP IP address assignment range changes with every host system reboot, as described in https://techcommunity.microsoft.com/t5/Windows-Insider-Program/Hyper-V-Default-switch-IP-address-range-change-Ver-1809-Build/m-p/899983 .

This odd behavior seems to rule-out the possibility to do as @taliesins suggests, with regard to using DHCP and predictable MAC addresses inside base boxes. Unfortunately, the alternative is to configure a static IP address inside the guest, which, of course, is OS (and therefore base box) specific.

In other words, the only viable approach as yet seems to be for Vagrant to create a switch for its own exclusive use (see the Powershell commands in my linked post).

Hopefully, a core Vagrant developer or a contributor with sufficient knowledge of the software is able to take what I describe and synthesize it into a more elegant and fully-configurable solution.

Yivan commented 5 years ago

Hello,

Thanks @cbj4074 for you help on this, your posts was very valuable to make it run. I managed to find a solution for the 2 remainings problems, default switch and not gracefull shutdown.

For the first one, default switch, you can include in the vagrantfile, you must adapt #{vm_name} variable:

  vm_exists = system("powershell.exe", "-Command", "Get-VM -VMNAME #{vm_name} -ErrorAction SilentlyContinue | out-null")
  if !vm_exists
    config.vm.network "public_network", bridge: "Default Switch"
  end

So it will only assign the default switch on the vm creation. Just what we need ! : )

Second part for the not gracefull shutdown, i did'nt use any reload plugin, but just issue a vagrant reload on the script trigger on the after up event.

Here is the PS script, $vmName is a variable to set to the name of the vm. This example is for Debian (tested on buster, aka Debian 10), it can be adapted (the part where it sets fixed ip inside the VM depending the guest OS).

    # Set fixed IP in the VM
    $mustRestart = 0
    if (Get-VM -VMName $vmName -ErrorAction SilentlyContinue) {
        if (Get-VM -VMNAME $vmName | Where-Object {$_.State -eq 'Running'}) {
            $VAGRANT_IP=(Get-VM -VMName $vmName | Get-VMNetworkAdapter).IpAddresses | Select -first 1

            # Set fixed IP inside the VM
            if ($VAGRANT_IP -And $VAGRANT_IP -ne "10.213.237.2") {
                Write-Host "Actual IP is $VAGRANT_IP, setting the new IP address 10.213.237.2 inside the VM..."
                $mustRestart = 1

                ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i .vagrant/machines/$vmHostname/hyperv/private_key -q vagrant@$VAGRANT_IP @"
                if ! cat /etc/network/interfaces | grep -q 10.213.237.2; then
                    sudo sed -i 's/^allow-hotplug eth0/#allow-hotplug eth0/g' /etc/network/interfaces
                    sudo sed -i 's/^iface eth0 inet dhcp/#iface eth0 inet dhcp/g' /etc/network/interfaces
                    sudo chmod o+w /etc/network/interfaces
                    cat <<EOT >> /etc/network/interfaces

# Fixed IP
auto eth0
iface eth0 inet static
  address 10.213.237.2
  netmask 255.255.255.0
  gateway 10.213.237.1
  dns-nameservers 8.8.8.8 8.8.4.4
EOT
                    sudo chmod o-w /etc/network/interfaces
                fi
"@
            }

            # Set Hyper-V
            If ("VagrantSwitch" -in (Get-VMSwitch | Select-Object -ExpandProperty Name) -eq $FALSE) {
                Write-Host 'Creating Internal-only switch named "VagrantSwitch" on Windows...'
                New-VMSwitch -SwitchName "VagrantSwitch" -SwitchType Internal | out-null
                sleep 2
                $mustRestart = 1
            }
            if ("VagrantSwitch" -in (Get-VM -Name $vmName | Get-VMNetworkAdapter | Select-Object -ExpandProperty SwitchName) -eq $FALSE) {
                Write-Host 'Associate VagrantSwitch to the VM...'
                Get-VM -VMNAME $vmName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "VagrantSwitch"
                sleep 2
                $mustRestart = 1
            }
            If ("10.213.237.1" -in (Get-NetIPAddress | Select-Object -ExpandProperty IPAddress) -eq $FALSE) {
                Write-Host 'Registering new IP gateway address 10.213.237.1 for VagrantSwitch on Windows...'
                New-NetIPAddress -IPAddress 10.213.237.1 -PrefixLength 24 -InterfaceAlias "vEthernet (VagrantSwitch)" | out-null
                sleep 2
                $mustRestart = 1
            }
            If ("10.213.237.0/24" -in (Get-NetNAT | Select-Object -ExpandProperty InternalIPInterfaceAddressPrefix) -eq $FALSE) {
                Write-Host 'Registering new NAT adapter for 10.213.237.0/24 on Windows...'
                New-NetNAT -Name "NATVagrantSwitch" -InternalIPInterfaceAddressPrefix 10.213.237.0/24 | out-null
                sleep 2
                $mustRestart = 1
            }

            # Restart if needed
            if ($mustRestart) {
                Write-Host 'VM will reload now to get its new fixed IP 10.213.237.2...'
                vagrant reload
            } else {
                Write-Host 'VM has already a fixed IP: 10.213.237.2'
            }
        } else {
            Write-Host 'VM needs to be running to set a fixed IP.'
        }
    }

With all this, you can fix an IP for your vagrant VM.

I hope it helps other people struggling on this, and maybe on day we can get this feature native in Vagrant. The hard part you be to set the fixed ip inside the guest OS as iti is very depending of it.

Best regards

Yivan commented 5 years ago

I just discovery a new tool hvc for hyperv, and i think it should be mentionned here as it allow connecting inside the VM even if no network between host and guest is available :

In powershell, in the directory where the Vagrantfile resides, run:

hvc ssh -o StrictHostKeyChecking=no vagrant@$vmName -o UserKnownHostsFile=$NULL -i .vagrant/machines/$vmHostname/hyperv/private_key

Replace $vmName and $vmHostname by yours.

This way, we can set network inside the VM even if connection is broken, and we can properly halt it or even run sudo systemctl restart networking.service or sudo ifdown eth0 && sudo ifup eth0 to refresh network without VM needed to reboot at all !

I hope those last posts can provide some way to vagrant team so they will achive to directly manage the IP from the vagrantfile (actually is is what all thos scripts can manage, but il will be proper to have it implemented cleanly in vagrant).

I think the topic should maybe be updated to "New feature" instead "Discussion". As all the tools are here to make it possible.

EDIT: after trying, it works only for CentOS/RedHat guest OS it seems...

Thanks.

cbj4074 commented 4 years ago

@Yivan Thanks for the suggestions, and I'm glad I could help you!

With regard to specifying the Default Switch on first boot automatically, your suggestion does work! Great idea!

Regarding the second issue, again, your suggestion works!

The key to your approach, which avoids the SSH timeout, is issuing the vagrant reload command in the Powershell script (not in the Vagrantfile)! This makes the SSH disconnection that occurs when the Hyper-V switch is changed while the VM is powered-on a non-issue.

Actually, upon further consideration, the same problem exists, regardless of how the VM is reloaded, which now that I think about it, makes sense. Even if we issue vagrant reload from within the Powershell context, the Vagrant daemon still cannot reach the VM via SSH after we change the switch.

Again, this isn't a big deal; it just causes a bit of a delay (60 seconds or so) before Vagrant restarts the machine forcibly. This is evident from the following output (the second line is present only if the graceful attempt fails):

homestead: ==> homestead: Attempting graceful shutdown of VM...
homestead: ==> homestead: Stopping the machine...

But, this change is still worthwhile because it allows me to eliminate the vagrant-reload plugin; it's simply unnecessary now.

Fantastic! I'm pretty happy with this workaround, although, it goes without saying that proper fixed-IP support in Vagrant would be vastly superior.

robsoncloud commented 3 years ago

Hi @cbj4074,

do you happen to know how to decrease the timeout of the halt? In my setup, it is taking about 10 mins to forcibly stop the VM. I went ahead and already changed the value of graceful_halt_timeout to 5 but it did not take any effect.

Thanks in advance

jesperkragh commented 3 years ago

I dont quite understand how you can do a vagrant reload command inside the script. Vagrant is locked during execution and should result in an error

An action 'read_state' was attempted on the machine 'name', but another process is already executing an action on the machine. Vagrant locks each machine for access by only one process at a time. Please wait until the other Vagrant process finishes modifying this machine, then try again.

Yivan commented 3 years ago

@jesperkragh Hello, in recent vagrant versions, this check is now done, and so running "vagrant reload" inside the script work no more like you said.

I needed to change my script to handle the restart directly using the VM api. It makes the process faster by the way : ) (using Stop-VM -Name $vmName , Start-VM -Name $vmName, Restart-VM -Name $vmName). As those VM commands are asynchrone, you need to check manually to go on after restart. Using this loop does the works:

For ($i=0; $i -le 120; $i++) { if (-Not (Get-VM -VMName $vmName | Get-VMNetworkAdapter).IpAddresses) { sleep 1 } else { break } } # Wait for VM being started

Hope it helps! Best regards.

jesperkragh commented 3 years ago

Hi @Yivan and @cbj4074

So after quite some time i got it to work. I ran into several issues. First the ssh command would not work because the file permissions on the insecure private key was wrong. After i fixed that i ran into wrong line endings in the shell command to set the static ip. I fixed that by changing the entire script from windows line endings to unix using notepad++

I also found out that setting the new switch for the vm before stopping the vm made the stop command very slow, so i changed the script so that it does this after vm shut down.

The only problem i have left, is that my samba shares are lost after the reboot. I am unable to ping the host ip from inside the guest vm, and i am at a loss as to why this happens. Anyone know what the problem could be? EDIT: After a host restart i can now ping the host from inside the vm

Here is my script if anyone is interrested. For information i create the Nat switch in the before up trigger and this script is run on the after up trigger.

param (
    [parameter (Mandatory=$true)]
    [string]$vmName,
    [parameter (Mandatory=$true)]
    [string]$switchName,
    [parameter (Mandatory=$true)]
    [string]$staticIP,
    [parameter (Mandatory=$true)]
    [string]$switchIP,
    [parameter (Mandatory=$true)]
    [string]$dnsIP,
    [parameter (Mandatory=$true)]
    [string]$prefixLength,
    [parameter (Mandatory=$true)]
    [string]$sshPrivateKey
)

$tmpSSHPrivateKey="C:/tmp/vagrant_insecure_private_key"
$restartRequired = 0
$startTimeout = 120
$stopTimeout = 30

if (Get-VM -VMName $vmName -ErrorAction SilentlyContinue) {
  if (Get-VM -VMNAME $vmName | Where-Object {$_.State -eq "Running"}) {
    $vagrantIP = (Get-VM -VMName $vmName | Get-VMNetworkAdapter).IpAddresses | Select -first 1

    Write-Host "Validate IP configuration"
    # Set fixed IP inside the VM
    if ($vagrantIP -And $vagrantIP -ne "$staticIP") {

      # If the private key is owned by anyone other then current user, ssh will not accept it
      Write-Host "IP configuration of network adapter eth0 incorrect... Restart required"
      Write-Host "Copy ssh private key to $tmpSSHPrivateKey"
      Remove-Item -Path $tmpSSHPrivateKey -Force > $null
      cp $sshPrivateKey $tmpSSHPrivateKey > $null
      Write-Host "Setting SSH friendly permissions on temp key file"
      icacls $tmpSSHPrivateKey /setowner $env:username > $null
      icacls $tmpSSHPrivateKey /inheritance:r > $null
      icacls $tmpSSHPrivateKey /grant  "$($env:username):F"  > $null
      icacls $tmpSSHPrivateKey /remove Administrator "Authenticated Users" BUILTIN\Administrators BUILTIN Everyone System Users > $null

      Write-Host "Setting new IP in network scripts via SSH..."
      Write-Host "Current  IP: $vagrantIP"
      Write-Host "New Static IP IP: $staticIP"
      Write-Host "Gateway IP: $switchIP"
      Write-Host "DNS IP: $dnsIP"
      Write-Host "DNS Prefix Length: $prefixLength"
      # ITS VERY IMPORTANT THIS FILE HAS UNIX LF, OR SSH SCRIPTS WILL FAIL
      ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i $tmpSSHPrivateKey -q vagrant@$vagrantIP @"
if ! cat /etc/sysconfig/network-scripts/ifcfg-eth0 | grep -q $staticIP; then
sudo chmod o+w /etc/sysconfig/network-scripts/ifcfg-eth0;
sudo cat <<EOF >> /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
ONBOOT=yes
PREFIX=$prefixLength
IPADDR=$staticIP
GATEWAY=$switchIP
DNS1=$dnsIP
EOF
fi
"@
      $restartRequired = 1
    }

    Write-Host "Validate Switch configuration"
    if ("VagrantSwitch" -in (Get-VM -Name $vmName | Get-VMNetworkAdapter | Select-Object -ExpandProperty SwitchName) -eq $FALSE) {
      Write-Host "Switch configuration is incorrect... Restart required"
      $restartRequired = 1
    }

    # Restart if needed
    if ($restartRequired) {
      Write-Host "Restarting VM $vmName to finalize static ip configuration"

      # Stop
      Stop-VM -Name $vmName
      Write-Host "Stopping VM $vmName"
      For ($i=0; $i -le $stopTimeout; $i++) { if ((get-vm -name $vmName).state -ne "Off") { sleep 1 } else { break } }      

      # Switch switch :) when the vm is stopped, otherwise stop action takes a long time
      Write-Host "Associate VM $vmName with switch $switchName"
      Get-VM -VMNAME $vmName | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName "$switchName"

      # Start
      Start-VM -Name $vmName
      Write-Host "Starting VM $vmName"
      For ($i=0; $i -le $startTimeout; $i++) { if ((get-vm -name $vmName).state -ne "Running") { sleep 1 } else { break } }
    } else {
      Write-Host "VM has already a fixed IP: $staticIP"
    }
    #TODO: Setup samba share
  } else {
    throw "ERROR: VM needs to be running to set a fixed IP."
  }
}
ShaunLawrie commented 2 years ago

I've been doing a bunch of kubernetes dev and need to build and tear down windows+linux kubernetes nodes pretty frequently on Hyper-V and have always wanted to use Vagrant for this type of thing.
I have a proof of concept version working here https://github.com/ShaunLawrie/vagrant-hyperv-v2/pull/1 that's successfully attaching NAT and static IPs to my machines but I have a lot of cleanup to do to make it good enough to pull into the current provider.

image

I'd appreciate any feedback, especially if anyone thinks this is worth pursuing further.

leftygardner commented 2 weeks ago

I took the time to get this "working" and posted a comprehensive how-to:

https://superuser.com/a/1379582/176764

All the required networking-related Powershell is there (and it's far less verbose than what's described in #8384 (comment) ).

They key is not to use Hyper-V's Default Switch, specifically because its DHCP IP address assignment range changes with every host system reboot, as described in https://techcommunity.microsoft.com/t5/Windows-Insider-Program/Hyper-V-Default-switch-IP-address-range-change-Ver-1809-Build/m-p/899983 .

This odd behavior seems to rule-out the possibility to do as @taliesins suggests, with regard to using DHCP and predictable MAC addresses inside base boxes. Unfortunately, the alternative is to configure a static IP address inside the guest, which, of course, is OS (and therefore base box) specific.

In other words, the only viable approach as yet seems to be for Vagrant to create a switch for its own exclusive use (see the Powershell commands in my linked post).

Hopefully, a core Vagrant developer or a contributor with sufficient knowledge of the software is able to take what I describe and synthesize it into a more elegant and fully-configurable solution.

Great solution, thanks for taking the time to share.

Some observations of my own deploying Windows VM -

1) Unless I am missing something, Vagrant requires the VM to use DHCP to get an IP to work with, i.e. no way to inject static IP config into the .vhdx before the VM is started

2) Setting the IP address within the Windows Guest OS is achievable via the registry to allow the change to happen after a reboot - this allows leveraging of the config.vm.provision :reload command

3) The problem starts here when Vagrant appears to expect the DHCP IP address instead of the static IP address. I found this relates to the way Vagrant detects the VM's IP address as it is booting, e.g. a PowerShell script runs on the Hyper-V host and queries the VM in question over and over until the guest IP is exposed and output in simple JSON format.

4) This Vagrant PowerShell script is called \embedded\gems\gems\vagrant-2.4.1\plugins\providers\hyperv\scripts\get_network_config.ps1

...and uses two methods to detect the IP -

First method (Get-VMNetworkAdapter) Hyper-V command which gets the IP address of the guest OS. If this isn't detected, it tries the fallback method.

Fallback method (Get-NetNeighbor) General Windows OS command which queries the recent ARP table. It takes the VM's MAC address from the previous command, then matches it against an IP - it is this IP it will use for WinRM

It is the fallback method which is causing Vagrant to hang onto the DHCP IP address because the guest OS is not quick enough to announce its new IP address (and update the Hyper-V host ARP table), and so Vagrant hangs onto the DHCP IP address which of course is no longer active.

5) I found I could comment out the fallback method in get_network_config.ps1 so it continues to use the first method and basically waits for the guest OS to announce its IP to the Hyper-V host. This does the trick without the need to create the NAT Switch in your solution. However, this means you'd have to remember to comment this out every time you update Vagrant. - I personally don't want to have to remember to do this.

6) I was thinking something could be written to clear the VM MAC address on the host using Remove-NetNeighbor whilst the VM is booting (I tried this manually and it worked) but this feels horribly inelegant.

In the end I decided your solution was much easier and was able to write everything into the 'vagrantfile' using variables without the need for external scripts etc. I did note when going with this solution, the detection method only ever needs to use Get-VMNetworkAdapter and never Get-NetNeighbor, hence why it works.

One simple proposal could be a vagrantfile Hyper-v option to disable the use of the fallback method.

Thanks again