vagrant-hostmanager icon indicating copy to clipboard operation
vagrant-hostmanager copied to clipboard

Make hostmanager work with DHCP addresses

Open stucki opened this issue 11 years ago • 40 comments
trafficstars

It seems like vagrant-hostmanager is reading the IP address of a host from the Vagrantfile. However, when using DHCP, that does not work.

stucki avatar Apr 04 '14 14:04 stucki

I'm currently using the following custom resolver to assign the correct IP address of my eth1 device (I'm using this to assign a private IP in Vagrant):

      node.hostmanager.ip_resolver = proc do |vm, resolving_vm|
        if hostname = (vm.ssh_info && vm.ssh_info[:host])
          `vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet" | tail -n 1 | egrep -o "[0-9\.]*" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
        end
      end

stucki avatar Apr 04 '14 14:04 stucki

Made several changes due to ip6 address etc.

  config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      `vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet addr" | tail -n 1 | egrep -o "[0-9\.]+" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
    end
  end

chulkilee avatar Apr 20 '14 04:04 chulkilee

Sorry for the delay in getting to this. I'd like to implement this capability into hostmanager, pending #99 and #100. If anyone is interested in helping out with those, let me know!

For now, I think using a custom ip_resolver is the only way to go.

pbitty avatar Jun 01 '14 23:06 pbitty

Hi, i want to use this plugin to detect the private network ip of vagrant box and update /etc/hosts on my host so i can access the apache webserver over the hostname like www.mydev.com.

After the standard vagrant init, up and ssh

vagrant init ubuntu/trusty64
vagrant up
vagrant ssh

i install apache2 with the common command

sudo apt-get install apache2

after that i leave the box and shutdown it with vagrant halt, until this point i don't touch the Vagrantfile, it was the standard file created by vagrant. Now i want to update /etc/hosts on host but ONLY the /etc/hosts on hosts without modify the hostname on guest. I use private network dhcp like @stucki but the plugin don't update my /etc/hosts. My Vagrantfile looks like:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.hostname = 'www.mydev.com'
  config.vm.network "private_network", type: "dhcp"
  config.hostmanager.enabled = false
  config.hostmanager.manage_host = true
  config.hostmanager.ignore_private_ip = false
  config.hostmanager.include_offline = true
  config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      `vagrant ssh -c "/sbin/ifconfig eth1" | grep "inet" | tail -n 1 | egrep -o "[0-9\.]*" | head -n 1 2>&1`.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
    end
  end
end

Is this a bug or do i something wrong? And how can i prevent that vagrant change the hostname on guest, i want only set the right private ip with a specific hostname into /etc/hosts on host.

gr33nturtl3 avatar Jun 28 '14 13:06 gr33nturtl3

If you know you're using VirtualBox as a provider, this is significantly faster than SSHing in (for me anyway):

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
  if vm.id
    `VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
  end
end

Updated with @m1keil's suggestion, thanks. Also wrapped in a conditional to handle upping groups of VMs, some of which might not be created yet.

halfninja avatar Jul 30 '14 14:07 halfninja

halfninja thx for that config line. just minor fix - it probably needs to be printf in awk and not print because print will insert newline and break hostfile

m1keil avatar Aug 09 '14 16:08 m1keil

Another possible edit is to use Ruby's split() instead of AWK, so this will work on Windows as well:

`VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]

m1keil avatar Aug 19 '14 14:08 m1keil

Yeah, that makes loads more sense - got caught up in the command line and forgot I was in Ruby :)

halfninja avatar Aug 19 '14 16:08 halfninja

+1

Custom resolver works, but having DHCP option supported natively would be great!

mbrgm avatar Nov 03 '14 11:11 mbrgm

+1 for support of DHCP-assigned private secondary interfaces.

Also, any general solution should not rely on "vagrant ssh" to gain access into the VM. Be aware that the guests running Windows generally lack ssh access and instead use a winrm communicator. I share this not because I'm a Windows fanboy but because I have to deal with the darn guests and nifty plugins that rely on "vagrant ssh" cannot be used for networks of heterogeneous-os clients.

The VBoxManage trick here avoids the vagrant ssh issue but has a similar issue with expecting all the clients to be provisioned to local VirtualBox.

What's needed is a general solution that works regardless of the chosen communicators and the chosen providers.

lonniev avatar Jan 05 '15 22:01 lonniev

In case it's helpful to anyone, this variant doesn't use vagrant ssh, which can break if this is called while another vagrant process has the VM locked (which seems to be the case during vagrant up). Instead it uses Vagrant's API to communicate with the VM. It also caches the results, which if you have more than a trivial number of VMs can be a big win. I use this with EC2 to get at the private IP addresses because my setup requires it an vagrant-aws only exposes the public IP address.

cached_addresses = {}
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
  if cached_addresses[vm.name].nil?
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      vm.communicate.execute("/sbin/ifconfig eth0 | grep 'inet addr' | tail -n 1 | egrep -o '[0-9\.]+' | head -n 1 2>&1") do |type, contents|
        cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
      end
    end
  end
  cached_addresses[vm.name]
end

ewencp avatar Feb 03 '15 00:02 ewencp

+1

@ewencp awesome thanks. If anyone has trouble with, try changing eth0 to another interface

kuremu avatar Feb 11 '15 13:02 kuremu

Is vm.communicate undocumented? It seems so useful. Ideally, the VM should be telling us its IP address once its has booted.

CMCDragonkai avatar May 17 '15 09:05 CMCDragonkai

Provider independent and works even with changed locales (german) which response with: inet Adresse

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      `vagrant ssh -c "hostname -I"`.split()[1]
    end
  end

dominikzogg avatar May 30 '15 15:05 dominikzogg

+1 :+1:

@ewencp also works in multi machine set up :100:

dherges avatar Jul 26 '15 16:07 dherges

@ewencp thanks. I modified your answer to work with CentOS 7 since ifconfig is not included by default. Now, if I can just get this to return the external IPs on the AWS instances only on the host I'll be set.

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
  if cached_addresses[vm.name].nil?
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      vm.communicate.execute("/usr/sbin/ip addr show eth0 | grep 'inet ' | xargs | cut -f 2 -d ' '| cut -f 1 -d '/' 2>&1") do |type, contents|
        cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
      end
    end
  end
  cached_addresses[vm.name]
end

ChinnoDog avatar Sep 17 '15 16:09 ChinnoDog

The centos7 impl for me was a ifesaver, thanks alot for this... so .. Is this hack still necessary? I think so.

jayunit100 avatar Nov 26 '15 18:11 jayunit100

My version, combining @dominikzogg's use of hostname -I with @ewencp's solution of caching the results and not using vagrant ssh. This one should work on centos/rhel 6, centos/rhel 7, and ubuntu 15.10 (and hopefully most linux guests).

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
  if cached_addresses[vm.name].nil?
    if hostname = (vm.ssh_info && vm.ssh_info[:host])
      vm.communicate.execute("hostname -I | cut -d ' ' -f 2") do |type, contents|
        cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]
      end
    end
  end
  cached_addresses[vm.name]
end

sworisbreathing avatar Jan 29 '16 04:01 sworisbreathing

Combining all the above as well as handling AWS private/public IP, Linux only but works on Redhat and Debian based system, and does not use vagrant ssh:

$cached_addresses = {}
$ip_resolver = proc do |vm, resolving_vm|
  # For aws, we should use private IP on the guests, public IP on the host
  if vm.provider_name == :aws
    if resolving_vm.nil?
      used_name = vm.name.to_s + '--host'
    else
      used_name = vm.name.to_s + '--guest'
    end
  else
    used_name= vm.name.to_s
  end

  if $cached_addresses[used_name].nil?
    if hostname = (vm.ssh_info && vm.ssh_info[:host])

      # getting aws guest ip *for the host*, we want the public IP in that case.
      if vm.provider_name == :aws and resolving_vm.nil?
        vm.communicate.execute('curl http://169.254.169.254/latest/meta-data/public-ipv4') do |type, pubip|
          $cached_addresses[used_name] = pubip
        end
      else

        vm.communicate.execute('uname -o') do |type, uname|
          unless uname.downcase.include?('linux')
            warn("Guest for #{vm.name} (#{vm.provider_name}) is not Linux, hostmanager might not find an IP.")
          end
        end

        vm.communicate.execute('hostname --all-ip-addresses') do |type, hostname_i|
          # much easier (but less fun) to work in ruby than sed'ing or perl'ing from shell

          allips = hostname_i.strip().split(' ')
          if vm.provider_name == :virtualbox
            # 10.0.2.15 is the default virtualbox IP in NAT mode.
            allips = allips.select { |x| x != '10.0.2.15'}
          end

          if allips.size() == 0
            warn("Trying to find out ip for #{vm.name} (#{vm.provider_name}), found none useable: #{allips}.")
          else
            if allips.size() > 1
              warn("Trying to find out ip for #{vm.name} (#{vm.provider_name}), found too many: #{allips} and I cannot choose cleverly. Will select the first one.")
            end
            $cached_addresses[used_name] = allips[0]
          end
        end
      end
    end
  end
  $cached_addresses[used_name]
end

lomignet avatar Feb 08 '16 13:02 lomignet

I found that I needed to wrap the communicate.execute in a "communicate.ready?" because otherwise issuing a vagrant destroy would fail because the machine is already down before cleaning up the /etc/hosts file:

vagrant destroy -f 
==> hypernode: Forcing shutdown of VM...
==> hypernode: Destroying VM and associated drives...
==> hypernode: Running cleanup tasks for 'shell' provisioner...
==> hypernode: Updating /etc/hosts file on active guest machines...
==> hypernode: Updating /etc/hosts file on host machine (password may be required)...
The provider for this Vagrant-managed machine is reporting that it
is not yet ready for SSH. Depending on your provider this can carry
different meanings. Make sure your machine is created and running and
try again. Additionally, check the output of `vagrant status` to verify
that the machine is in the state that you expect. If you continue to
get this error message, please view the documentation for the provider
you're using.

Custom ip_resolver based on this issue with "if machine.communicate.ready?":

    # Get the dynamic hostname from the running box so we know what to put in 
    # /etc/hosts even though we don't specify a static private ip address
    config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
      if vm.communicate.ready?
        result = ""
        vm.communicate.execute("ifconfig eth1") do |type, data|
          result << data if type == :stdout
        end
      end
      (ip = /inet addr:(\d+\.\d+\.\d+\.\d+)/.match(result)) && ip[1]
    end
vagrant destroy -f 
==> hypernode: Forcing shutdown of VM...
Connection to 127.0.0.1 closed by remote host.
==> hypernode: Destroying VM and associated drives...
==> hypernode: Running cleanup tasks for 'shell' provisioner...
==> hypernode: Updating /etc/hosts file on active guest machines...
==> hypernode: Updating /etc/hosts file on host machine (password may be required)...

vdloo avatar Feb 12 '16 10:02 vdloo

`Vagrant.configure("2") do |config| #-----------------------------------# # Use this base box # #-----------------------------------# config.vm.box = "ubuntu/trusty64"

#-----------------------------------#
# Port Forwading and networking     #
#-----------------------------------#

config.vm.network :private_network, ip: "10.0.240.80"

config.vm.network :private_network, type: :dhcp
config.vm.network :forwarded_port, guest: 80, host: 80              #webserver
config.vm.network :forwarded_port, guest: 443, host: 8443            #webserver (ssl)
config.vm.network :forwarded_port, guest: 3306, host: 3306          #mysql
config.vm.network :forwarded_port, guest: 3819, host: 3819          #phpmyadmin

#-----------------------------------#
# Update hostsfile                  #
#-----------------------------------#
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.manage_guest = true
config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true

config.vm.hostname = "docker-host.vm"
config.hostmanager.aliases = ["docker-host-alias.vm"]

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|

if hostname = (vm.ssh_info && vm.ssh_info[:host])

vagrant ssh -c "hostname -I".split()[1]

end

end

#-----------------------------------#
# VirtualBox Setup and Optimization #
#-----------------------------------#
config.vm.provider :virtualbox do |v|
    v.name = "docker-host"
    v.customize ["modifyvm", :id, "--name",                "docker-host"]
    v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    v.customize ["modifyvm", :id, "--memory",              2048]
    v.customize ["modifyvm", :id, "--cpus",                2]
    v.customize ["modifyvm", :id, "--chipset",             "ich9"]
    v.customize ["modifyvm", :id, "--ioapic",              "on"]
    v.customize ["modifyvm", :id, "--rtcuseutc",           "on"]
    v.customize ["modifyvm", :id, "--pae",                 "on"]
    v.customize ["modifyvm", :id, "--hwvirtex",            "on"]
    v.customize ["modifyvm", :id, "--nestedpaging",        "on"]

    # Workaround: stability fix
    v.auto_nat_dns_proxy = false
    v.customize ["modifyvm", :id, "--natdnsproxy1", "off" ]
    v.customize ["modifyvm", :id, "--natdnshostresolver1", "off" ]

    # network
    v.customize ["modifyvm", :id, "--nictype1", "virtio"]
    v.customize ["modifyvm", :id, "--nictype2", "virtio"]


end

config.hostmanager.ip_resolver = proc do |vm, resolving_vm|

    puts "==============================================================================================================="
    puts "#{`\"#{ENV['VBOX_MSI_INSTALL_PATH']}\\VBoxManage\" guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`}"
    puts "==============================================================================================================="

    puts "=============== VB ID: #{vm.id}"

    if vm.id && Vagrant::Util::Platform.windows?
        `\"#{ENV['VBOX_MSI_INSTALL_PATH']}\\VBoxManage\" guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
    else
        `VBoxManage guestproperty get #{vm.id} "/VirtualBox/GuestInfo/Net/1/V4/IP"`.split()[1]
    end
end`

This fails as at the time of querying the IP address it seems that there is no value set: Output:

`==> default: Updating /etc/hosts file on active guest machines...

No value set!

=============== VB ID: 03ec23de-18c5-4583-9e86-3406a8c62a22 `

Queryiing manually in a windows cmd after the machine is created reveals the correct IP address: "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" guestproperty get 03ec23de-18c5-4583-9e86-3406a8c62a22 "/VirtualBox/GuestInfo/Net/1/V4/IP" Value: 172.28.128.16

Any ideas where it goes wrong?

bernhardberger avatar Jul 12 '16 12:07 bernhardberger

Hello!

Since this issue seems to SEO pretty well, as of vagrant 1.8.5 and later on ubuntu 16.10 (host) and Centos7 (guest), this line;

cached_addresses[vm.name] = contents.split("\n").first[/(\d+\.\d+\.\d+\.\d+)/, 1]

throws a nasty ruby exception saying,

/Vagrantfile:63:in `block (3 levels) in <top (required)>': undefined method `[]' for nil:NilClass (NoMethodError)`

I found a fix here that fixes the issue and does not rely on vagrant ssh. Here is my updated version of the read_ip_address function that allows it to get the ip address second interface.

def read_ip_address(machine)
  command =  "ip a | grep 'inet' | grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $2 }' | cut -f1 -d\"/\""
  result  = ""

  $logger.info "Processing #{ machine.name } ... "

  begin
    # sudo is needed for ifconfig
    machine.communicate.sudo(command) do |type, data|
      result << data if type == :stdout
    end
    $logger.info "Processing #{ machine.name } ... success"
  rescue
    result = "# NOT-UP"
    $logger.info "Processing #{ machine.name } ... not running"
  end

  # the second inet is more accurate
  result.chomp.split("\n").last
end

Hopefully this helps people work around the regression in 1.8.5 and later. For full context here this is a gist of my complete Vagrantfile

Max-AR avatar Oct 23 '16 09:10 Max-AR

@Max-AR I have this error with your complete Vagrantfile in my context:

/Users/stephane/projets/openshift/ta-ansible/vagrant/Vagrantfile:24:in `read_ip_address': undefined method `info' for nil:NilClass (NoMethodError)

full log and Vagrantfile: https://gist.github.com/harobed/f3198e47417b026969fcae99b6cef39d

harobed avatar Feb 16 '17 10:02 harobed

My mistake: I forget this line

$logger = Log4r::Logger.new('vagrantfile')

harobed avatar Feb 16 '17 11:02 harobed

Hi, Is this issue still relevant? I just tried to start two Vagrant VM with some basic configuration (DHCP) and this hostmanager config

  config.hostmanager.enabled = true
  config.hostmanager.manage_host = false
  config.hostmanager.manage_guest = true

When starting VM, I see a message about updating /etc/hosts

vm2: Updating /etc/hosts file on active guest machines...
vm1: Updating /etc/hosts file on active guest machines...

And in fact the two VM have a correct /etc/hosts and can communicate with each other.

daks avatar Apr 26 '17 13:04 daks

Without hack, in /etc/hosts on guest machine I have:

## vagrant-hostmanager-start id: 11058adc-e452-4267-8273-a16ce56cc723
127.0.0.1	server-elk
## vagrant-hostmanager-end

with the hack, I don't have 127.0.0.1 but the real ip 172.28.128.27.

Then I think that the bug is always here.

Context:

  • vagrant 1.9.2
  • debian jessie
$ vagrant ssh -c "sudo ip addr"
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:7a:b8:ad brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe7a:b8ad/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:99:35:b5 brd ff:ff:ff:ff:ff:ff
    inet 172.28.128.27/24 brd 172.28.128.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe99:35b5/64 scope link
       valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:37:99:e4:c5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 scope global docker0
       valid_lft forever preferred_lft forever
Connection to 127.0.0.1 closed.

harobed avatar Apr 26 '17 13:04 harobed

It's weird because I don't have the hack.

$ vagrant -v
Vagrant 1.9.1
$ vagrant plugin list
vagrant-cachier (1.2.1)
vagrant-env (0.0.3)
vagrant-hostmanager (1.8.5)
vagrant-libvirt (0.0.37)
vagrant-mutate (1.2.0)
vagrant-scp (0.5.7)
vagrant-share (1.1.6, system)

daks avatar Apr 26 '17 13:04 daks

What say vagrant ssh -c "sudo ip addr" ?

I think that your first interface isn't lo:.

harobed avatar Apr 26 '17 13:04 harobed

I got lo and eth0 in this order, for both machines.

daks avatar Apr 26 '17 13:04 daks

I have same, I have tried many ip_resolver, but no onne works

Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.4"
  
  # Manage hosts file https://github.com/devopsgroup-io/vagrant-hostmanager
  config.hostmanager.enabled = true
  config.hostmanager.manage_host = true
  
  config.vm.hostname = 'uwbox'
  config.hostmanager.aliases = %w(uwbox.loc)

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "1024"
  end
  
end
➜  vgtest vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'uw172'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: vgtest_default_1509098784527_71154
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: uniworldbox
    default: SSH auth method: private key
    default: Warning: Remote connection disconnect. Retrying...
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Setting hostname...
==> default: Mounting shared folders...
    default: /vagrant => /Users/i_skiridomov/Projects/vgtest
==> default: [vagrant-hostmanager:guests] Updating hosts file on active guest virtual machines...
==> default: [vagrant-hostmanager:host] Updating hosts file on your workstation (password may be required)...

## vagrant-hostmanager-start id: 5300db46-00b5-48f0-a201-52edb2e1cd76
127.0.0.1	uwbox
127.0.0.1	uwbox.loc
## vagrant-hostmanager-end

Update: Only after static_private ip it works

  config.vm.network :private_network, ip: "192.168.50.50"

boskiv avatar Oct 27 '17 10:10 boskiv