Open bbrala opened 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:
cap/configure_networks
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.
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.
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.
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)
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.
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.
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.
@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.
@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.
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
@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 :)
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.
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.
@mwhooker; you did the implementation of HyperV for packer. Do you have any insight on this chicken/egg issue regarding static IP's?
@bbrala regretfully, I only merged the PR. @taliesins is the brains behind the endeavor. Maybe he knows?
Ah sorry, it has been a while, i remembered incorrectly, sorry about that :)
@bbrala two potential approaches are:
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.
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?
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.
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.
Good catch, that will make things a lot easier. Patience i guess
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)
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.
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. :)
Has any work been done on this guys?
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?
Yeah. This would work for me.
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.
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.
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.
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!
That is awesome to hear.
This is amazing news, thank you!
@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).
@taliesins Yep, I'm planning on it.
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:
$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
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..
Sorry to join discussion without additional tech info, but would like to kindly ask if there is any progress, or plans?
I can still submit a PR with my working solution using the external DHCP server if there is any interest in it.
@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.
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.
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
@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?
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.
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
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.
@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.
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
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.
@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.
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."
}
}
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.
I'd appreciate any feedback, especially if anyone thinks this is worth pursuing further.
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
...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
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 :)