<p>I'm trying to figure out how to use Ansible with Vagrant the proper way. By default, it seems Vagrant is isolating Ansible execution per box and executes playbooks after each box partially as it applies to that single box in the loop. I find this VERY counterproductive and I have tried tricking Vagrant into executing a playbook across all of the hosts AFTER all of them booted, but it seems Ansible, when started from Vagrant never sees more than a single box at a time.</p>

<p>Edit: these are the version I am working with:</p>

<p>Vagrant: 2.2.6
Ansible: 2.5.1
Virtualbox: 6.1</p>

<p>The playbook (with the hosts.ini) by itsef executes without issues when I run it stand-alone with the ansible-playbook executable after the hosts come up, so the problem is with my Vagrant file. I just cannot figure it out.</p>

<p>This is the Vagrantfile:</p>

<pre class="lang-rb prettyprint-override"><code># -*- mode: ruby -*-
# vi: set ft=ruby :

IMAGE_NAME = "ubuntu/bionic64"

Vagrant.configure("2") do |config|
    config.ssh.insert_key = false
    config.vm.box = IMAGE_NAME

    # Virtualbox configuration
    config.vm.provider "virtualbox" do |v|
        v.memory = 4096
        v.cpus = 2
        #v.linked_clone = true
    end

    # master and node definition
    boxes = [
        { :name =&gt; "k8s-master", :ip =&gt; "192.168.50.10" },
        { :name =&gt; "k8s-node-1", :ip =&gt; "192.168.50.11" }
    ]

    boxes.each do |opts|
        config.vm.define opts[:name] do |config|
            config.vm.hostname = opts[:name]
            config.vm.network :private_network, ip: opts[:ip]

            if opts[:name] == "k8s-node-1"
                config.vm.provision "ansible_local" do |ansible|
                    ansible.compatibility_mode = "2.0"
                    ansible.limit = "all"
                    ansible.config_file = "ansible.cfg"
                    ansible.become = true
                    ansible.playbook = "playbook.yml"
                    ansible.groups = {
                        "masters" =&gt; ["k8s-master"],
                        "nodes" =&gt; ["k8s-node-1"]
                    }
                end
            end
        end
    end
end
</code></pre>

<p>ansible.cfg</p>

<pre><code>[defaults]
connection = smart
timeout = 60
deprecation_warnings = False
host_key_checking = False
inventory = hosts.ini

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes
</code></pre>

<p>hosts.ini</p>

<pre><code>[masters]
k8s-master ansible_host=192.168.50.10 ansible_user=vagrant

[nodes]
k8s-node-1 ansible_host=192.168.50.11 ansible_user=vagrant

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
</code></pre>

<p>playbook.yml</p>

<pre><code>- hosts: all
  become: yes
  tasks:
    - name: Update apt cache.
      apt: update_cache=yes cache_valid_time=3600
      when: ansible_os_family == 'Debian'

    - name: Ensure swap is disabled.
      mount:
        name: swap
        fstype: swap
        state: absent

    - name: Disable swap.
      command: swapoff -a
      when: ansible_swaptotal_mb &gt; 0

    - name: create the 'mobile' user
      user: name=mobile append=yes state=present createhome=yes shell=/bin/bash

    - name: allow 'mobile' to have passwordless sudo
      lineinfile:
        dest: /etc/sudoers
        line: 'mobile ALL=(ALL) NOPASSWD: ALL'
        validate: 'visudo -cf %s'

    - name: set up authorized keys for the mobile user
      authorized_key:
        user: mobile
        key: "{{ lookup('pipe','cat ssh_keys/*.pub') }}"
        state: present
        exclusive: yes

- hosts: all
  become: yes
  tasks:
   - name: install Docker
     apt:
       name: docker.io
       state: present
       update_cache: true

   - name: install APT Transport HTTPS
     apt:
       name: apt-transport-https
       state: present

   - name: add Kubernetes apt-key
     apt_key:
       url: https://packages.cloud.google.com/apt/doc/apt-key.gpg
       state: present

   - name: add Kubernetes' APT repository
     apt_repository:
      repo: deb http://apt.kubernetes.io/ kubernetes-xenial main
      state: present
      filename: 'kubernetes'

   - name: install kubelet
     apt:
       name: kubelet=1.17.0-00
       state: present
       update_cache: true

   - name: install kubeadm
     apt:
       name: kubeadm=1.17.0-00
       state: present

- hosts: masters
  become: yes
  tasks:
   - name: install kubectl
     apt:
       name: kubectl=1.17.0-00
       state: present
       force: yes

- hosts: k8s-master
  become: yes
  tasks:
    - name: check docker status
      systemd:
        state: started
        name: docker

    - name: initialize the cluster
      shell: kubeadm init --apiserver-advertise-address 192.168.50.10 --pod-network-cidr=10.244.0.0/16 &gt;&gt; cluster_initialized.txt
      args:
        chdir: $HOME
        creates: cluster_initialized.txt

    - name: create .kube directory
      become: yes
      become_user: mobile
      file:
        path: $HOME/.kube
        state: directory
        mode: 0755

    - name: copy admin.conf to user's kube config
      copy:
        src: /etc/kubernetes/admin.conf
        dest: /home/mobile/.kube/config
        remote_src: yes
        owner: mobile

    - name: install Pod network
      become: yes
      become_user: mobile
      shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml &gt;&gt; pod_network_setup.txt
      args:
        chdir: $HOME
        creates: pod_network_setup.txt

- hosts: k8s-master
  become: yes
  gather_facts: false
  tasks:
    - name: get join command
      shell: kubeadm token create --print-join-command 2&gt;/dev/null
      register: join_command_raw

    - name: set join command
      set_fact:
        join_command: "{{ join_command_raw.stdout_lines[0] }}"

- hosts: nodes
  become: yes
  tasks:
    - name: check docker status
      systemd:
        state: started
        name: docker

    - name: join cluster
      shell: "{{ hostvars['k8s-master'].join_command }} &gt;&gt; node_joined.txt"
      args:
        chdir: $HOME
        creates: node_joined.txt
</code></pre>

<p>The moment the playbook tries to execute against k8s-master, it fails like this:</p>

<pre><code>fatal: [k8s-master]: UNREACHABLE! =&gt; {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname k8s-master: Temporary failure in name resolution", "unreachable": true}
</code></pre>

<p>The host is up. SSH works.</p>

<p>Who can help me sort this out?</p>

<p>Thanks!</p>

<p>I have managed to use Ansible inside of Vagrant. </p>

<p>Here is what I did to make it work: </p>

<p><strong>Steps to reproduce:</strong></p>

<ul>
<li>Install Vagrant, Virtualbox</li>
<li>Create all the necessary files and directories

<ul>
<li>ansible.cfg</li>
<li>playbook.yml</li>
<li>hosts</li>
<li>insecure_private_key</li>
<li>Vagrant file</li>
</ul></li>
<li>Test</li>
</ul>

<h2>Install Vagrant, Virtualbox</h2>

<p>Follow installation guides at appropriate sites:</p>

<ul>
<li><a href="https://www.vagrantup.com/docs/installation/" rel="nofollow noreferrer">Vagrant</a></li>
<li><a href="https://www.virtualbox.org/wiki/Downloads" rel="nofollow noreferrer">Virtualbox</a></li>
</ul>

<h2>Create all the necessary files and directories</h2>

<p><strong>This example bases on original poster files</strong>.</p>

<p>Create <code>vagrant</code> and <code>ansible</code> folders to store all the configuration files and directories. The structure of it could look like that:</p>

<ul>
<li><code>vagrant</code> - directory 

<ul>
<li>Vagrantfile - file with main configuration </li>
</ul></li>
<li><code>ansible</code> - directory 

<ul>
<li>ansible.cfg - configuration file of Ansible</li>
<li>playbook.yml - file with steps for Ansible to execute </li>
<li>hosts - file with information about hosts </li>
<li>insecure_private_key - private key of created machines  </li>
</ul></li>
</ul>

<p><code>Ansible</code> folder is a seperate directory that will be copied to <code>k8s-node-1</code>. </p>

<p>By default Vagrant shares a <code>vagrant</code> folder with permissions of <code>777</code>. It allows owner, group and others to have full access on everything that is inside of it. </p>

<p>Logging to virtual machine manualy and running <code>ansible-playbook</code> command inside <code>vagrant</code> directory will output errors connected with permissions. It will render <code>ansible.cfg</code> and <code>insecure_private_key</code> useless. </p>

<h3>Ansible.cfg</h3>

<p><code>Ansible.cfg</code> is configuration file of Ansible. Example used below:</p>

<pre><code>[defaults]
connection = smart
timeout = 60
deprecation_warnings = False
host_key_checking = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes
</code></pre>

<p>Create <code>ansible.cfg</code> inside <code>ansible</code> directory.</p>

<h3>Playbook.yml</h3>

<p>Example <code>playbook.yml</code> is a file with steps for Ansible to execute.
It will check connections and test if groups are configured correctly: </p>

<pre><code>- name: Check all connections 
  hosts: all 
  tasks:
    - name: Ping
      ping: 

- name: Check specific connection to masters 
  hosts: masters 
  tasks:
    - name: Ping
      ping: 

- name: Check specific connection to nodes 
  hosts: nodes 
  tasks:
    - name: Ping
      ping: 
</code></pre>

<p>Create <code>playbook.yml</code> inside <code>ansible</code> directory.</p>

<h3>Insecure_private_key</h3>

<p>To successfully connect to virtual machines you will need <code>insecure_private_key</code>. You can create it by invoking command:<code>$ vagrant init</code> inside <code>vagrant</code> directory. 
It will create <code>insecure_private_key</code> inside your physical machine in <strong><code>HOME_DIRECTORY/.vagrant.d</code></strong>. 
Copy it to <code>ansible</code> folder. </p>

<h3>Hosts</h3>

<p>Below <code>hosts</code> file is responsible for passing the information about hosts to Ansible:</p>

<pre><code>[masters]
k8s-master ansible_host=192.168.50.10 ansible_user=vagrant

[nodes]
k8s-node-1 ansible_host=192.168.50.11 ansible_user=vagrant

[all:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_user=vagrant
ansible_ssh_private_key_file=/ansible/insecure_private_key
</code></pre>

<p>Create <code>hosts</code> file inside <code>ansible</code> directory.</p>

<p><strong>Please take a specific look on:</strong> <code>ansible_ssh_private_key_file=/ansible/insecure_private_key</code></p>

<p>This is declaration for Ansible to use  earlier mentioned key. </p>

<h3>Vagrant</h3>

<p><code>Vagrant</code> file is the main configuration file:</p>

<pre class="lang-rb prettyprint-override"><code># -*- mode: ruby -*-
# vi: set ft=ruby :

IMAGE_NAME = "ubuntu/bionic64"

Vagrant.configure("2") do |config|
    config.ssh.insert_key = false
    config.vm.box = IMAGE_NAME

    # Virtualbox configuration
    config.vm.provider "virtualbox" do |v|
        v.memory = 4096
        v.cpus = 2
        #v.linked_clone = true
    end

    # master and node definition
    boxes = [
        { :name =&gt; "k8s-master", :ip =&gt; "192.168.50.10" },
        { :name =&gt; "k8s-node-1", :ip =&gt; "192.168.50.11" }
    ]

    boxes.each do |opts|
        config.vm.define opts[:name] do |config|
            config.vm.hostname = opts[:name]
            config.vm.network :private_network, ip: opts[:ip]

            if opts[:name] == "k8s-node-1"
                config.vm.synced_folder "../ansible", "/ansible", :mount_options =&gt; ["dmode=700", "fmode=700"]

                config.vm.provision "ansible_local" do |ansible|
                    ansible.compatibility_mode = "2.0"
                    ansible.limit = "all"
                    ansible.config_file = "/ansible/ansible.cfg"
                    ansible.become = true
                    ansible.playbook = "/ansible/playbook.yml"
                    ansible.inventory_path = "/ansible/hosts" 
                end
            end
        end
    end
end
</code></pre>

<p><strong>Please take a specific look on:</strong></p>

<pre class="lang-rb prettyprint-override"><code>config.vm.synced_folder "../ansible", "/ansible", :mount_options =&gt; ["dmode=700", "fmode=700"]
</code></pre>

<p><code>config.vm.synced_folder</code> will copy <code>ansible</code> directory to <code>k8s-node-1</code> with all the files inside. </p>

<p>It will set permissions for full access only to owner (vagrant user). </p>

<pre class="lang-rb prettyprint-override"><code>ansible.inventory_path =  "/ansible/hosts"
</code></pre>

<p><code>ansible.inventory_path</code> will tell Vagrant to provide <code>hosts</code> file for Ansible. </p>

<h2>Test</h2>

<p>To check run the following command from the <code>vagrant</code> directory:
<code>$ vagrant up</code></p>

<p>The part of the output responsible for Ansible should look like that: </p>

<pre><code>==&gt; k8s-node-1: Running provisioner: ansible_local...
    k8s-node-1: Installing Ansible...
    k8s-node-1: Running ansible-playbook...

PLAY [Check all connections] ***************************************************

TASK [Gathering Facts] *********************************************************
ok: [k8s-master]
ok: [k8s-node-1]

TASK [Ping] ********************************************************************
ok: [k8s-master]
ok: [k8s-node-1]

PLAY [Check specific connection to masters] ************************************

TASK [Gathering Facts] *********************************************************
ok: [k8s-master]

TASK [Ping] ********************************************************************
ok: [k8s-master]

PLAY [Check specific connection to nodes] **************************************

TASK [Gathering Facts] *********************************************************
ok: [k8s-node-1]

TASK [Ping] ********************************************************************
ok: [k8s-node-1]

PLAY RECAP *********************************************************************
k8s-master                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
k8s-node-1                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
</code></pre>
