Using Ansible with Vagrant

Using Ansible with Vagrant

If you are not familiar with Vagrant, I highly recommend reading my previous Vagrant Introduction post here.

Vagrant for NetDevOps
There were many situations in the past where I had to quickly set up a Syslog or a Radius server to test and troubleshoot something on my Network.

Vagrant supports many provisioners such as Ansible, Salt, Puppet and Chef. This blog post focuses on using Ansible as a provisioner. The Vagrant Ansible provisioner allows you to provision the guest machine using Ansible playbooks by executing ansible-playbook from the Vagrant host.

When you run vagrant up the first time, Vagrant passes off the VM to whichever provisioner is configured on the Vagrantfile (Ansible in our case) and tells it to run the defined Playbook.

Our end goal here is to use Vagrant to create a CentOS 7 VM and then use Ansible to install the required packages (Git, Ansible, Nmap and net-tools) A simple vagrant up will create the VM and the Ansible Playbook will handle the configuration and package installation. You are no longer required to download the ISO from the vendor, boot the ISO from VirtualBox/VMware and then fiddle with various configurations and package installation.

Vagrant

You only need two files for this to work, Vagrantfile and the Ansible Playbook, both files should be located in your project folder. Let's edit the Vagrantfile to create a CentOS 7 VM and use Ansible as the provisioner.

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
    # General Vagrant VM configuration
    config.vm.box = "centos/7"

    # my test server
    config.vm.define "my-server" do |server|
        server.vm.hostname = "my-server"
        server.vm.network :public_network, ip: "10.10.40.5", :netmask => "255.255.0.0"
    end

    config.vm.provision "ansible" do |ansible|
        ansible.playbook = "playbook.yml"
    end
end
Vagrantfile

Let's break it down

  • config.vm.box = "centos/7" - Vagrant to create CentOS VM
  • config.vm.define "my-server" do |server| - Configuration parameters for the VM
  • server.vm.hostname = "my-server" - Set the hostname to 'my-server'
  • server.vm.network :public_network, ip: "10.10.40.5", :netmask => "255.255.0.0" - Set the network to 'bridged/public-network' mode and assign static IP
  • config.vm.provision "ansible" do |ansible| - Configure Ansible as the provisioner

Ansible Playbook file

The main component of a successful Ansible provisioner setup is the Ansible playbook which contains the steps that should run on the guest. Let's create the Ansible playbook.yml file in the same directory as your Vagrantfile.

A playbook that installs Ansible, Git, Nmap and net-tools looks like this:

💡
Since Ansible version 2.0, you can install multiple packages using the name attribute. 
---
- hosts: all
  become: yes

  tasks:
  - name: Install EPEL repo
    yum:
      name: epel-release
      state: present

  - name: Install Ansible
    yum:
      name: ansible
      state: present
  
  - name: Install git, nmap and net-tools 
    yum:
      state: present
      name:
        - git
        - nmap
        - net-tools
  
Playbook.yml

Let's go through the Playbook step-by-step

  • - hosts: all - This tells Ansible to which hosts this Playbook applies. 'All' should work fine because Vagrant uses its own inventory file (more on this later)
  • become: yes - We need privileged/sudo access to install the packages.
  • tasks: - All the individual tasks after this line will be run on the hosts
  • yum: - This is equivalent to running yum install git Ansible will check whether 'git' is installed, and, if not, will install it

For now, let's run vagrant up and see what happens.

suresh@ubuntu:~/Documents/projects/vagrant_ansible$ vagrant up
Bringing machine 'my-server' up with 'virtualbox' provider...
==> my-server: Importing base box 'centos/7'...
==> my-server: Matching MAC address for NAT networking...
==> my-server: Checking if box 'centos/7' version '2004.01' is up to date...
==> my-server: Setting the name of the VM: vagrant_ansible_my-server_1640612834801_97505
==> my-server: Clearing any previously set network interfaces...
==> my-server: Preparing network interfaces based on configuration...
    my-server: Adapter 1: nat
    my-server: Adapter 2: bridged
==> my-server: Forwarding ports...
    my-server: 22 (guest) => 2222 (host) (adapter 1)
==> my-server: Booting VM...
==> my-server: Waiting for machine to boot. This may take a few minutes...
    my-server: SSH address: 127.0.0.1:2222
    my-server: SSH username: vagrant
    my-server: SSH auth method: private key
    my-server: 
    my-server: Vagrant insecure key detected. Vagrant will automatically replace
    my-server: this with a newly generated keypair for better security.
    my-server: 
    my-server: Inserting generated public key within guest...
    my-server: Removing insecure key from the guest if it's present...
    my-server: Key inserted! Disconnecting and reconnecting using new SSH key...
==> my-server: Machine booted and ready!
==> my-server: Checking for guest additions in VM...
    my-server: No guest additions were detected on the base box for this VM! Guest
    my-server: additions are required for forwarded ports, shared folders, host only
    my-server: networking, and more. If SSH fails on this machine, please install
    my-server: the guest additions and repackage the box to continue.
    my-server: 
    my-server: This is not an error message; everything may continue to work properly,
    my-server: in which case you may ignore this message.
==> my-server: Setting hostname...
==> my-server: Configuring and enabling network interfaces...
==> my-server: Rsyncing folder: /home/suresh/Documents/projects/vagrant_ansible/ => /vagrant
==> my-server: Running provisioner: ansible...
    my-server: Running ansible-playbook...

PLAY [all] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [my-server]

TASK [Install EPEL repo] *******************************************************
changed: [my-server]

TASK [Install Ansible] *********************************************************
changed: [my-server]

TASK [Install git, nmap and net-tools] *****************************************
changed: [my-server]

PLAY RECAP *********************************************************************
my-server                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

As you can see below:

  • Vagrant assigned 10.10.40.5/16 to the Interface (eth1)
  • The hostname is set to 'my-server'
  • Ansible, Git, Nmap and net-tools are installed
suresh@ubuntu:~/Documents/projects/vagrant_ansible$ vagrant ssh
Last login: Mon Dec 27 13:48:28 2021 from 10.0.2.2

[vagrant@my-server ~]$ ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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 52:54:00:4d:77:d3 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
       valid_lft 86277sec preferred_lft 86277sec
    inet6 fe80::5054:ff:fe4d:77d3/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:63:8e:fd brd ff:ff:ff:ff:ff:ff
    inet 10.10.40.5/16 brd 10.10.255.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe63:8efd/64 scope link 
       valid_lft forever preferred_lft forever
       
[vagrant@my-server ~]$ git --version
git version 1.8.3.1

[vagrant@my-server ~]$ ansible --version
ansible 2.9.25
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Apr  2 2020, 13:16:51) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
  
[vagrant@my-server ~]$ nmap --version
Nmap version 6.40 ( http://nmap.org )
Platform: x86_64-redhat-linux-gnu
Compiled with: nmap-liblua-5.2.2 openssl-1.0.2k libpcre-8.32 libpcap-1.5.3 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select

A few things to consider
If you reload the VM using vagrant reload command, Vagrant ignores the provisioner configuration. So, if you were to make changes to the Ansible Playbook, please use the command vagrant provision to run the Playbook again.

suresh@ubuntu:~/Documents/projects/vagrant_ansible$ vagrant reload
==> my-server: Attempting graceful shutdown of VM...
==> my-server: Checking if box 'centos/7' version '2004.01' is up to date...

[...]

==> my-server: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> my-server: flag to force provisioning. Provisioners marked to run always will still run.

Vagrant requires the first Network eth0 attached to the virtual machine to be a NAT device. eth0 as NAT is a fundamental requirement for Vagrant functionality. Therefore, any private or bridged networks are added as additional network devices and exposed to the virtual machine as “eth1,” “eth2,” and so on.

Auto-Generated Inventory
As you may already know that Ansible needs to know on which machines a given playbook should run, Ansible does this by way of an inventory file. The straightforward option is to not provide one to Vagrant at all. Vagrant will automatically generate an inventory file containing all of the VMs it manages, and use it for provisioning machines.

# Generated by Vagrant

my-server ansible_host=127.0.0.1 ansible_port=2222 ansible_user='vagrant' ansible_ssh_private_key_file='/home/suresh/Documents/projects/vagrant_ansible/.vagrant/machines/my-server/virtualbox/private_key'
💡
Note that the generated inventory file is stored as part of your local Vagrant environment in .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory.

With this small example, you can see the power of using Ansible with Vagrant.  If you want another VM, all you have to do is edit the Vagrantfile file and add another server with a different IP/hostname etc.


Thank you for reading, as always your comments and feedback are always welcome.