3

I'm trying to create multi-machine environment in Vagrant using Ansible as provisioner.

My Vagrantfile looks like next:

   Vagrant.configure("2") do |config|

    config.vm.provision "ansible" do |ansible|
       ansible.limit = "all"
       ansible.playbook = "main.yml"
       ansible.inventory_path = "staging"
       ansible.verbose = "-vvvv"
     end

    config.vm.define "machine1" do |machine1| 
        machine1.vm.box = "ubuntu/trusty64"
        machine1.vm.network "private_network", ip:"192.168.77.10"
        machine1.vm.hostname = "machine1"
        machine1.vm.provider :virtualbox do |vb|
           vb.name = "machine1"
        end
    end    

    config.vm.define "machine2" do |machine2| 
        machine2.vm.box = "ubuntu/trusty64"
        machine2.vm.network "private_network", ip:"192.168.77.11"
        machine2.vm.hostname = "machine2"
        machine2.vm.provider :virtualbox do |vb|
            vb.name = "machine2"
        end
    end    

    config.vm.define "machine3" do |machine3| 
        machine3.vm.box = "ubuntu/trusty64"
        machine3.vm.network "private_network", ip:"192.168.77.12"
        machine3.vm.hostname = "machine3"
        machine3.vm.provider :virtualbox do |vb|
           vb.name = "machine3"
        end
    end      
end

Inventory:

[AppServers]
192.168.77.10
192.168.77.11
192.168.77.12

[WebServers]
192.168.77.11
192.168.77.12

[WebLoadBalancers]
192.168.77.10

[SlaveDbServers]
192.168.77.10
192.168.77.12

[MasterDbServers]
192.168.77.11

[DbLoadBalancers]
192.168.77.11

main.yml:

- hosts: all
  roles:
  - Common
  - ConsulServer
  - ConsulAgent  

- hosts: WebServers
  roles:
  - WebServer

- hosts: WebLoadBalancers
  roles:
  - LoadBalancer

- hosts: MasterDbServers
  roles:
  - DbServer

I want to get 3 machines. All of them have to contain common soft(Consul servers and agents, vim etc). And some additional - own for each machine. But once i'm running "vagrant up" only first machine created, provisioner runs, fails because other 2 not created. Is it possible to run provisioner after all machines created? Or my approach is incorrect and i should perform this in other way? Thank you for your time.

Pavlo I.
  • 117
  • 3
  • 11
  • first, the obvious, do you have `ansible` installed on your local host? second, you can defer provisioning but `vagrant up --no-provision` and optionally `--no-destroy-on-error`. lastly, your `main.yml` needs `---` to be valid yaml (although it might not cause it to fail). – Mike D Dec 23 '15 at 22:23
  • Ansible installed on localhost and runs as provisioner, but it runs only for host 192.168.77.10 - other are unreachable. Seems, vagrant runs provisioner after each machine created, not after ALL machines from Vagrantfile created – Pavlo I. Dec 24 '15 at 08:31

1 Answers1

8

The first problem I had was ERROR: cannot find role in.... I'm assuming you have these roles and excluded them for brevity. My advice here is to have a simple Ansible playbook when you are testing this:

---
- hosts: all
  gather_facts: false
  tasks:
  - command: hostname -f

Secondly, the problem at hand surround the use of static inventory file and caveats therein. You are seeing an error because the Ansible provisioner is failing to find all hosts when it runs after the first machine is up but the others are not.

Lastly, each machine will have a different key, which you must pass. So, following Vagrant's documented approach for multi-machine parallelism with Ansible and with help from this work-around, here is what I recommend your Vagrantfile look like:

Vagrant.configure("2") do |config|
  N = 3

  VAGRANT_VM_PROVIDER = "virtualbox"
  ANSIBLE_RAW_SSH_ARGS = []

  (1..N-1).each do |machine_id|
    ANSIBLE_RAW_SSH_ARGS << "-o IdentityFile=.vagrant/machines/machine#{machine_id}/#{VAGRANT_VM_PROVIDER}/private_key"
  end

  (1..N).each do |machine_id|
    config.vm.define "machine#{machine_id}" do |machine|
      machine.vm.box = "ubuntu/trusty64"
      machine.vm.hostname = "machine#{machine_id}"
      machine.vm.network "private_network", ip: "192.168.77.#{10+machine_id-1}"

      # Only execute once the Ansible provisioner,
      # when all the machines are up and ready.
      if machine_id == N
        machine.vm.provision :ansible do |ansible|
          # Disable default limit to connect to all the machines
          ansible.limit = "all"
          ansible.playbook = "main.yml"
          ansible.inventory_path = "staging"
          ansible.verbose = "-v"
          ansible.raw_ssh_args = ANSIBLE_RAW_SSH_ARGS
        end
      end
    end
  end
end
Mike D
  • 5,984
  • 4
  • 31
  • 31
  • You are right - i have created all roles you see in main.yml file. Currently they do nothing just "echo 'Hello from some role'" as i'm just started to learn Vagrant and Ansible. I saw in Vagrant docs workaround you wrote about. So, we have two possible options to run provisioner **after** **all** machines created not per machine - use some kind of loop(through the list of machines or range of numbers in your example) or run **vagrant up** with **--no-provision** and then separately **vagrant provision**, right? – Pavlo I. Dec 24 '15 at 08:43
  • There are actually quite a few ways to tackle this. I do not recommend you use **--no-provision** in the two-step pattern. Personally, I like to have a hosts file to specify my groups since this is how I work with my production `ansible-playbook` usage. However, if you do not need or want a static inventory file, you can use **vagrant** built-in support for host groups. See [How to generate Inventory Groups](https://docs.vagrantup.com/v2/provisioning/ansible_intro.html) and I can provide an example. – Mike D Dec 24 '15 at 11:33
  • I've tried your Vagrantfile with hosts file from my post above but got new problem: now Ansible succesfully connects to the last machine(192.168.77.12) but other 2(192.168.77.10-11) not reachable: `fatal: [192.168.77.11] => SSH Error: while connecting to 192.168.77.11:22 It is sometimes useful to re-run the command using -vvvv, which prints SSH debug output to help diagnose the issue.` Looks like some additional ssh option required. Could you please advise where to look here? – Pavlo I. Dec 24 '15 at 19:24
  • @PavloI. that would be because the work-around isn't working. any chance you could put your code less proprietary data and roles into a repo I can look at? I'd be happy to help. – Mike D Dec 25 '15 at 00:00
  • Currently there is no any propietary things. Thank you very much for your time! Here is repo: [GitHub](https://github.com/Agnikay/vagrant-ansible-study) – Pavlo I. Dec 25 '15 at 07:56
  • Now it works as expected. Firstly i've added whitespaces at the beginning and ending of `ANSIBLE_RAW_SSH_ARGS` and it started to work. But then i did `vagrant destroy` and removed _.vagrant_ dir and tried again from scratch. And it started to work. I have no ideas what magic is it. – Pavlo I. Dec 25 '15 at 16:03
  • @PavloI. Awesome, glad to hear. The private keys Vagrant puts in the vms are stored in the .vagrant dir among other metadata. Cleaning it up after a destroy can sometimes help. – Mike D Dec 25 '15 at 17:44
  • @PavloI. here's another way of doing it that is more Vagrant friendly but does not use a your custom inven file: https://github.com/miked0004/vagrant-ansible-multimachine.git – Mike D Dec 25 '15 at 18:13
  • Great, I'll try this! What approach from your point and experience is more clear to support both staging and production? What if keep dynamic inventory for dev systems and create separated static for production? Playbook will will be the same as depends on grouping. – Pavlo I. Dec 25 '15 at 19:15
  • @PavloI. that totally depends upon your workflow and use case. There are quite a few other ways to implement this (i.e. check out `config.ssh.insert_key=false`). For me, I try to keep hacks out of code. The way I posted in my repo is purely vagrant and generates the inven file based on your machines. Then refine as you go. BTW **vagrant 1.8.1** is already out. – Mike D Dec 26 '15 at 02:28