Parallel Provisioning with Vagrant and Ansible
Tags: devops, vagrant, ansible, tutorial
Published: 2021-01-08 13:51:37

Ok, so after installing [Vagrant + Libvirt](https://project42.site/installing-vagrant-libvirt-plugin-in-centos-8/) lets see if we can deploy some VMs in parallel adding also some Ansible magic.

For this test, will be using Oracle Linux Boxes.

The first step is to get the Oracle Linux Boxes and do a simple test to make sure the VM creation is working fine. 

I just followed similar steps from [here](https://blogs.oracle.com/linux/getting-started-with-the-vagrant-libvirt-provider-for-oracle-linux) (good guide as well to get Vagrant + Libvirt running) but needed to change the URL for the OL8 Boxes, which currently is "https://oracle.github.io/vagrant-projects/boxes/oraclelinux". 
More info in [Oracle Yum Website](https://yum.oracle.com/boxes/)
```bash
|=| server in ~ ○ → mkdir Vagrant_Oracle

|=| server in ~ ○ → cd Vagrant_Oracle/

|=| server in ~/Vagrant_Oracle ○ → vagrant init oraclelinux/8 https://oracle.github.io/vagrant-projects/boxes/oraclelinux/8.json
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

```

The Vagrantfile generated is the most simple one, but should be enough to test simple provision
```bash
|=| server in ~/Vagrant_Oracle ○ → grep '^[[:blank:]]*[^[:blank:]#;]' Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "oraclelinux/8"
  config.vm.box_url = "https://oracle.github.io/vagrant-projects/boxes/oraclelinux/8.json"
end

|=| server in ~/Vagrant_Oracle ○ →
```

Let's make sure a simple OL8 Box is created:
```bash
|=| server in ~/Vagrant_Oracle ○ →  vagrant up --provider=libvirt
Bringing machine 'default' up with 'libvirt' provider...
==> default: Box 'oraclelinux/8' could not be found. Attempting to find and install...
    default: Box Provider: libvirt
    default: Box Version: >= 0
==> default: Loading metadata for box 'https://oracle.github.io/vagrant-projects/boxes/oraclelinux/8.json'
    default: URL: https://oracle.github.io/vagrant-projects/boxes/oraclelinux/8.json
==> default: Adding box 'oraclelinux/8' (v8.3.183) for provider: libvirt
    default: Downloading: https://yum.oracle.com/boxes/oraclelinux/ol8/OL8U3_x86_64-vagrant-libvirt-b183.box
Progress: 4% (Rate: 8253k/s, Estimated time remaining: 0:01:06)

[.....]

==> default: Uploading base box image as volume into Libvirt storage...
==> default: Creating image (snapshot of base box volume).
==> default: Creating domain with the following settings...
==> default:  -- Name:              Vagrant_Oracle_default
==> default:  -- Domain type:       kvm
==> default:  -- Cpus:              2
==> default:  -- Feature:           apic
==> default:  -- Feature:           acpi
==> default:  -- Memory:            2048M
==> default:  -- Management MAC:
==> default:  -- Loader:
==> default:  -- Nvram:
==> default:  -- Base box:          oraclelinux/8
==> default:  -- Storage pool:      default
==> default:  -- Image:             /var/lib/libvirt/images/Vagrant_Oracle_default.img (37G)
==> default:  -- Volume Cache:      default
==> default:  -- Kernel:
==> default:  -- Initrd:
==> default:  -- Graphics Type:     vnc
==> default:  -- Graphics Port:     -1
==> default:  -- Graphics IP:       127.0.0.1
==> default:  -- Graphics Password: Not defined
==> default:  -- Video Type:        cirrus
==> default:  -- Video VRAM:        16384
==> default:  -- Sound Type:
==> default:  -- Keymap:            en-us
==> default:  -- TPM Path:
==> default:  -- INPUT:             type=mouse, bus=ps2
==> default: Creating shared folders metadata...
==> default: Starting domain.
==> default: Waiting for domain to get an IP address...
==> default: Waiting for SSH to become available...
    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: Exporting NFS shared folders...
==> default: Preparing to edit /etc/exports. Administrator privileges will be required...
==> default: Mounting NFS shared folders...

|=| server in ~/Vagrant_Oracle ○ →
```

Since I have the intention to create multiple Boxes, decided to create a new libvirt storage pool for Vagrant and avoid any space issues
```bash
|=| server in /home/kvm/HDD/Vagrant ○ → virsh pool-define-as --name Vagrant --type dir --target /home/kvm/HDD/Vagrant
Pool Vagrant defined

|=| server in /home/kvm/HDD/Vagrant ○ → virsh pool-start Vagrant
Pool Vagrant started

|=| server in /home/kvm/HDD/Vagrant ○ → virsh pool-autostart Vagrant
Pool Vagrant marked as autostarted

|=| server in /home/kvm/HDD/Vagrant ○ → virsh pool-list --all
 Name       State    Autostart
--------------------------------
 default    active   yes
 disks      active   yes
 kvm        active   yes
 solifugo   active   yes
 Vagrant    active   yes


|=| server in /home/kvm/HDD/Vagrant ○ → df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdc1       1.8T  960G  780G  56% /home/kvm/HDD

|=| server in /home/kvm/HDD/Vagrant ○ →
```

Let's start now changing the Vagrantfile to create multiple VMs.
If you see something familar, is because I just used a generic one from Vagrant Website: [Ansible Provisioner](https://www.vagrantup.com/docs/provisioning/ansible)
This is our Vagrantfile (added some comments for each option)
```bash
|=| server in ~/Vagrant_Oracle ○ → cat Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.synced_folder ".", "/vagrant", :disabled => true # Disable NFS mount
  config.vm.provider :libvirt do |libvirt| # Tells vagrant to use libvirt
    libvirt.storage_pool_name = "Vagrant" # Use this Storage pool
    libvirt.default_prefix = "Vagrant_" # Shows Vagrant_ prefix in Virsh status
  end
  config.vm.box = "oraclelinux/8" # Box Dsitribution/version
  config.vm.box_url = "https://oracle.github.io/vagrant-projects/boxes/oraclelinux/8.json" # Place to locate the Box Image
  N = 3 # Number of VMs we want to deploy
  (1..N).each do |machine_id|
    config.vm.define "dbnode#{machine_id}" do |machine| #VM name
      machine.vm.hostname = "dbnode#{machine_id}" #VM hostname
      # Only execute once the Ansible provisioner,
      # when all the machines are up and ready.
      if machine_id == N
        machine.vm.provision :ansible do |ansible| #Vagrant to call Ansible
          # Disable default limit to connect to all the machines
          ansible.limit = "all" # Make sure Ansible check all VMs
          ansible.playbook = "Ansible_basic_playbook.yml" # Ansible playbook to execute
        end
      end
    end
  end

|=| server in ~/Vagrant_Oracle ○ →
```

Also, we will execute a simple Ansible Task to just create the user oracle and some user groups as well, just to see if that works in all 3 VMs we are creating, nothing really difficult (in theory)
```bash
|=| server in ~/Vagrant_Oracle ○ → cat Ansible_basic_playbook.yml
---
- name: Playbook
  hosts: all
  become: yes
  vars:
    users:
    -  "oracle"

  tasks:

  - name: Create group
    group:
      name: "{{ item }}"
      state: present
    with_items:
      - "admin"
      - "oinstall"
      - "oracle"

  - name: "Create user accounts"
    user:
      name: "{{ item }}"
      password: "{{ 'Welcome1' | password_hash('sha512') }}"
      groups: "oracle,oinstall,admin"
    with_items: "{{ users }}"

  - name: "Allow admin users to sudo without a password"
    lineinfile:
      dest: "/etc/sudoers"
      state: "present"
      regexp: "^%admin"
      line: "%admin ALL=(ALL) NOPASSWD: ALL"

|=| server in ~/Vagrant_Oracle ○ →
```

Let's try it out:
```bash
|=| server in ~/Vagrant_Oracle ○ → vagrant up
Bringing machine 'dbnode1' up with 'libvirt' provider...
Bringing machine 'dbnode2' up with 'libvirt' provider...
Bringing machine 'dbnode3' up with 'libvirt' provider...
==> dbnode2: Checking if box 'oraclelinux/8' version '8.3.183' is up to date...
==> dbnode1: Checking if box 'oraclelinux/8' version '8.3.183' is up to date...
==> dbnode3: Checking if box 'oraclelinux/8' version '8.3.183' is up to date...
==> dbnode2: Creating image (snapshot of base box volume).
==> dbnode3: Creating image (snapshot of base box volume).
==> dbnode1: Creating image (snapshot of base box volume).
==> dbnode3: Creating domain with the following settings...
==> dbnode2: Creating domain with the following settings...
==> dbnode1: Creating domain with the following settings...
==> dbnode1:  -- Name:              Vagrant_dbnode1
==> dbnode2:  -- Name:              Vagrant_dbnode2
==> dbnode3:  -- Name:              Vagrant_dbnode3
==> dbnode2:  -- Domain type:       kvm
==> dbnode3:  -- Domain type:       kvm
==> dbnode1:  -- Domain type:       kvm
==> dbnode1:  -- Cpus:              2
==> dbnode1:  -- Feature:           apic
==> dbnode1:  -- Feature:           acpi
==> dbnode1:  -- Memory:            2048M
==> dbnode2:  -- Cpus:              2
==> dbnode3:  -- Cpus:              2
==> dbnode3:  -- Feature:           apic
==> dbnode2:  -- Feature:           apic
==> dbnode1:  -- Management MAC:
==> dbnode1:  -- Loader:
==> dbnode3:  -- Feature:           acpi
==> dbnode1:  -- Nvram:
==> dbnode2:  -- Feature:           acpi
==> dbnode1:  -- Base box:          oraclelinux/8
==> dbnode1:  -- Storage pool:      Vagrant
==> dbnode1:  -- Image:             /home/kvm/HDD/Vagrant/Vagrant_dbnode1.img (37G)
==> dbnode2:  -- Memory:            2048M
==> dbnode1:  -- Volume Cache:      default
==> dbnode3:  -- Memory:            2048M
==> dbnode1:  -- Kernel:
==> dbnode3:  -- Management MAC:
==> dbnode2:  -- Management MAC:
==> dbnode1:  -- Initrd:
==> dbnode3:  -- Loader:
==> dbnode1:  -- Graphics Type:     vnc
==> dbnode3:  -- Nvram:
==> dbnode1:  -- Graphics Port:     -1
==> dbnode3:  -- Base box:          oraclelinux/8
==> dbnode2:  -- Loader:
==> dbnode3:  -- Storage pool:      Vagrant
==> dbnode1:  -- Graphics IP:       127.0.0.1
==> dbnode3:  -- Image:             /home/kvm/HDD/Vagrant/Vagrant_dbnode3.img (37G)
==> dbnode2:  -- Nvram:
==> dbnode3:  -- Volume Cache:      default
==> dbnode1:  -- Graphics Password: Not defined
==> dbnode2:  -- Base box:          oraclelinux/8
==> dbnode3:  -- Kernel:
==> dbnode1:  -- Video Type:        cirrus
==> dbnode2:  -- Storage pool:      Vagrant
==> dbnode3:  -- Initrd:
==> dbnode2:  -- Image:             /home/kvm/HDD/Vagrant/Vagrant_dbnode2.img (37G)
==> dbnode3:  -- Graphics Type:     vnc
==> dbnode1:  -- Video VRAM:        16384
==> dbnode1:  -- Sound Type:
==> dbnode2:  -- Volume Cache:      default
==> dbnode1:  -- Keymap:            en-us
==> dbnode3:  -- Graphics Port:     -1
==> dbnode1:  -- TPM Path:
==> dbnode1:  -- INPUT:             type=mouse, bus=ps2
==> dbnode3:  -- Graphics IP:       127.0.0.1
==> dbnode2:  -- Kernel:
==> dbnode3:  -- Graphics Password: Not defined
==> dbnode3:  -- Video Type:        cirrus
==> dbnode3:  -- Video VRAM:        16384
==> dbnode2:  -- Initrd:
==> dbnode3:  -- Sound Type:
==> dbnode3:  -- Keymap:            en-us
==> dbnode2:  -- Graphics Type:     vnc
==> dbnode2:  -- Graphics Port:     -1
==> dbnode3:  -- TPM Path:
==> dbnode2:  -- Graphics IP:       127.0.0.1
==> dbnode3:  -- INPUT:             type=mouse, bus=ps2
==> dbnode2:  -- Graphics Password: Not defined
==> dbnode2:  -- Video Type:        cirrus
==> dbnode2:  -- Video VRAM:        16384
==> dbnode2:  -- Sound Type:
==> dbnode1: Creating shared folders metadata...
==> dbnode2:  -- Keymap:            en-us
==> dbnode1: Starting domain.
==> dbnode2:  -- TPM Path:
==> dbnode1: Waiting for domain to get an IP address...
==> dbnode2:  -- INPUT:             type=mouse, bus=ps2
==> dbnode3: Creating shared folders metadata...
==> dbnode3: Starting domain.
==> dbnode3: Waiting for domain to get an IP address...
==> dbnode2: Creating shared folders metadata...
==> dbnode2: Starting domain.
==> dbnode2: Waiting for domain to get an IP address...
==> dbnode1: Waiting for SSH to become available...
==> dbnode2: Waiting for SSH to become available...
==> dbnode3: Waiting for SSH to become available...
    dbnode1:
    dbnode1: Vagrant insecure key detected. Vagrant will automatically replace
    dbnode1: this with a newly generated keypair for better security.
    dbnode2:
    dbnode2: Vagrant insecure key detected. Vagrant will automatically replace
    dbnode2: this with a newly generated keypair for better security.
    dbnode3:
    dbnode3: Vagrant insecure key detected. Vagrant will automatically replace
    dbnode3: this with a newly generated keypair for better security.
    dbnode2:
    dbnode2: Inserting generated public key within guest...
    dbnode1:
    dbnode1: Inserting generated public key within guest...
    dbnode3:
    dbnode3: Inserting generated public key within guest...
    dbnode2: Removing insecure key from the guest if it's present...
    dbnode1: Removing insecure key from the guest if it's present...
    dbnode3: Removing insecure key from the guest if it's present...
    dbnode2: Key inserted! Disconnecting and reconnecting using new SSH key...
    dbnode1: Key inserted! Disconnecting and reconnecting using new SSH key...
    dbnode3: Key inserted! Disconnecting and reconnecting using new SSH key...
==> dbnode2: Setting hostname...
==> dbnode1: Setting hostname...
==> dbnode3: Setting hostname...
==> dbnode3: Running provisioner: ansible...
    dbnode3: Running ansible-playbook...

PLAY [Playbook] ****************************************************************

TASK [Gathering Facts] *********************************************************
[WARNING]: Platform linux on host dbnode2 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/
reference_appendices/interpreter_discovery.html for more information.
ok: [dbnode2]
[WARNING]: Platform linux on host dbnode3 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/
reference_appendices/interpreter_discovery.html for more information.
ok: [dbnode3]
[WARNING]: Platform linux on host dbnode1 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/
reference_appendices/interpreter_discovery.html for more information.
ok: [dbnode1]

TASK [Create group] ************************************************************
changed: [dbnode3] => (item=admin)
changed: [dbnode1] => (item=admin)
changed: [dbnode2] => (item=admin)
changed: [dbnode3] => (item=oinstall)
changed: [dbnode1] => (item=oinstall)
changed: [dbnode2] => (item=oinstall)
changed: [dbnode3] => (item=oracle)
changed: [dbnode1] => (item=oracle)
changed: [dbnode2] => (item=oracle)

TASK [Create user accounts] ****************************************************
changed: [dbnode2] => (item=oracle)
changed: [dbnode1] => (item=oracle)
changed: [dbnode3] => (item=oracle)

TASK [Allow admin users to sudo without a password] ****************************
changed: [dbnode3]
changed: [dbnode1]
changed: [dbnode2]

PLAY RECAP *********************************************************************
dbnode1                    : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
dbnode2                    : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
dbnode3                    : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


|=| server in ~/Vagrant_Oracle ○ →
```

and ***voilà!*** we have our 3 "dbnodes" running
```bash
|=| server in ~/Vagrant_Oracle ○ → vagrant ssh dbnode3

Welcome to Oracle Linux Server release 8.3 (GNU/Linux 5.4.17-2036.100.6.1.el8uek.x86_64)

The Oracle Linux End-User License Agreement can be viewed here:

  * /usr/share/eula/eula.en_US

For additional packages, updates, documentation and community help, see:

  * https://yum.oracle.com/

Last login: Wed Jan  6 14:07:34 2021 from 192.168.121.1
[vagrant@dbnode3 ~]$

|=| server in ~/Vagrant_Oracle ○ → virsh list
 Id   Name              State
---------------------------------
 16   Vagrant_dbnode1   running
 17   Vagrant_dbnode3   running
 18   Vagrant_dbnode2   running


|=| server in ~/Vagrant_Oracle ○ →
```


If you dont like the fact that you need to be in the Vagrant work folder and execute "vagrant ssh" to connect to your system, you can just add the "vagrant ssh-config" to your user "ssh config" like you can see below:
```bash
|=| server in ~/Vagrant_Oracle ○ → vagrant ssh-config >> ~/.ssh/config

|=| server in ~/Vagrant_Oracle ○ → cd

|=| server in ~ ○ → ssh dbnode1

Welcome to Oracle Linux Server release 8.3 (GNU/Linux 5.4.17-2036.100.6.1.el8uek.x86_64)

The Oracle Linux End-User License Agreement can be viewed here:

  * /usr/share/eula/eula.en_US

For additional packages, updates, documentation and community help, see:

  * https://yum.oracle.com/

Last login: Tue Jan  5 15:11:58 2021 from 192.168.121.1
[vagrant@dbnode1 ~]$
```

By the way, if your VMs are already up but you need to run Ansible playbook again, you can just "vagrant provision"
```bash
|=| server in ~/Vagrant_Oracle ○ → vagrant provision
==> dbnode3: Running provisioner: ansible...
    dbnode3: Running ansible-playbook...

PLAY [Playbook] ****************************************************************

TASK [Gathering Facts] *********************************************************
[WARNING]: Platform linux on host dbnode1 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change the meaning of that path. See https://docs.ansi
ble.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more
information.
ok: [dbnode1]
[WARNING]: Platform linux on host dbnode3 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change the meaning of that path. See https://docs.ansi
ble.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more
information.
ok: [dbnode3]
[WARNING]: Platform linux on host dbnode2 is using the discovered Python
interpreter at /usr/libexec/platform-python, but future installation of another
Python interpreter could change the meaning of that path. See https://docs.ansi
ble.com/ansible/2.10/reference_appendices/interpreter_discovery.html for more
information.
ok: [dbnode2]

TASK [Create group] ************************************************************
ok: [dbnode3] => (item=admin)
ok: [dbnode1] => (item=admin)
ok: [dbnode2] => (item=admin)
ok: [dbnode3] => (item=oinstall)
ok: [dbnode1] => (item=oinstall)
ok: [dbnode2] => (item=oinstall)
ok: [dbnode3] => (item=oracle)
ok: [dbnode1] => (item=oracle)
ok: [dbnode2] => (item=oracle)

TASK [Create user accounts] ****************************************************
changed: [dbnode1] => (item=oracle)
changed: [dbnode3] => (item=oracle)
changed: [dbnode2] => (item=oracle)

TASK [Allow admin users to sudo without a password] ****************************
ok: [dbnode3]
ok: [dbnode1]
ok: [dbnode2]

PLAY RECAP *********************************************************************
dbnode1                    : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
dbnode2                    : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
dbnode3                    : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0


|=| server in ~/Vagrant_Oracle ○ →
```