Tuesday, 6 September 2016

Step by step Ansible and Vagrant quick start tutorial

I am learning Ansible and Vagrant, and had a lot of issues getting things running on Ubuntu 14.04.

I did manage to get a simple playbook working with Vagrant.  Hopefully, the entries below will save you some hair pulling and cursing.

For a quick intro of what Ansible can do, watch this video https://www.ansible.com/get-started

First things First: Install Vagrant

I want to use Ansible to set up nginx on a Vagrant VM.  So first, we need to install both Ansible and Vagrant.

In order to run Vagrant, you will need to install a virtual machine provider, such as virtualbox.

You can install virtualbox with:
$ sudo apt-get install virtualbox 
On my 14.04 machine, this got me virtualbox 4.3.36.  It is not the latest (5.1), but it seems to work fine.

You can download the latest Vagrant from: https://www.vagrantup.com/downloads.html

Then to install:
$ sudo dpkg -i vagrant_1.8.5_x86_64.deb
The Vagrant quick start says:
$ vagrant init hashicorp/precise64
That didn't work for me, I found I had to add the full precise64 virtualbox image's URL:
$ vagrant box add precise64 https://vagrantcloud.com/hashicorp/boxes/precise64/versions/1.1.0/providers/virtualbox.box

Install Ansible

To install the latest Ansible on Ubuntu 14.04, I had to add some additional repositories.
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible 

This gets me the latest Ansible:
$ ansible --version
ansible 2.1.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

Start up Vagrant

Let's bring up your Precise VM and see if the basics of Vagrant are working.
$ vagrant init precise64
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.
Bring up the VM:
 $ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'precise64'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: test_default_1473215023932_83230
==> default: Fixed port collision for 22 => 2222. Now on port 2200.
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 (guest) => 2200 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2200
    default: SSH username: vagrant
    default: SSH auth method: private key
    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: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.2.0
    default: VirtualBox Version: 4.3
==> default: Mounting shared folders...
    default: /vagrant => /tmp/test
Let's see if we can get into the VM:
$ vagrant ssh
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '14.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Fri Sep 14 06:23:18 2012 from 10.0.2.2
vagrant@precise64:~$
Excellent!

Now let's see if we can SSH into the VM the normal way.  Note that in the output of the "vagrant up", it told us the SSH port for this VM is 2200.  The default password for the "vagrant" user is, you guessed it, "vagrant"! (no quotes of course)
$ ssh vagrant@127.0.0.1 -p 2200
The authenticity of host '[127.0.0.1]:2200 ([127.0.0.1]:2200)' can't be established.
ECDSA key fingerprint is 11:5d:55:29:8a:77:d8:08:b4:00:9b:a3:61:93:fe:e5.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[127.0.0.1]:2200' (ECDSA) to the list of known hosts.
vagrant@127.0.0.1's password:
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
New release '14.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Welcome to your Vagrant-built virtual machine.
Last login: Wed Sep  7 02:29:18 2016 from 10.0.2.2
There we go, now we can SSH into the VM directly, we are ready for Ansible.

Getting Ansible to talk to the VM

In order to get Ansible to access the VM, you will need to create a hosts or inventory file telling it what user, port, IP to access the VM.  So use your favourite editor and make a file "hosts" with the following contents:
[vagranthost]
127.0.0.1 ansible_port=2200 ansible_user=vagrant ansible_password=vagrant 
Now, you can put this into your global /etc/ansible/hosts file, but having it locally gives you more options down the road.  Consider, if you have all your hosts in the global file, and then you use the "hosts: all" command in an Ansible playbook, it will literally run against ALL the hosts defined in that global file.  The downside of having separate hosts (or inventory) files is you do have to always use the "-i <filename>" running any Ansible command.  Otherwise Ansible will default to only looking at hosts defined in the global hosts file.

Note: Please do not put your password into your hosts file.  I've done it here only because this is just a test VM's password.

Now that we have the hosts file, let's do a ping test:
$ ansible vagranthost -i hosts -m ping
127.0.0.1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
} 
Voila!

Simple Playbook

Let's make a simple playbook that does a ping, updates apt cache, and then installs nginx, copies over a hello world html file, and starts nginx.  Again, use your favourite editor and create playbook.yaml with the following contents:

---
- hosts: vagranthost
  tasks:
  - name: test ping
    ping:

  - name: apt update
    become: yes
    apt: update_cache=yes

  - name: install curl
    become: yes
    apt: name=curl state=latest

  - name: install nginx
    become: yes
    apt: name=nginx state=latest

  - name: start nginx
    become: yes
    service: name=nginx state=started

  - name: copy index
    become: yes
    copy: src=hello.html dest=/usr/share/nginx/www/index.html
Let's also make hello.html in this same directory:
<html>
<body>
<h1>Hello world from inside the vagrant vm!</h1>
</body>
</html> 

Run the Playbook!

Moment of truth, let's run this against our VM:
$ ansible-playbook -i hosts playbook.yaml

PLAY [vagranthost] *************************************************************

TASK [setup] *******************************************************************
ok: [127.0.0.1]

TASK [test ping] ***************************************************************
ok: [127.0.0.1]

TASK [apt update] **************************************************************
ok: [127.0.0.1]

TASK [install curl] ************************************************************
changed: [127.0.0.1]

TASK [install nginx] ***********************************************************
changed: [127.0.0.1]

TASK [start nginx] *************************************************************
changed: [127.0.0.1]

TASK [copy index] **************************************************************
changed: [127.0.0.1]

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=6    changed=4    unreachable=0    failed=0
 
Let's check that nginx is actually started and serving the hello world homepage by using curl.
$ ansible vagranthost -i hosts -m shell -a "curl -s http://127.0.0.1"
127.0.0.1 | SUCCESS | rc=0  
That's nice, but how do we access this nginx server hosted inside the VM?  Simple, let's make Vagrant forward that port.

Edit Vagrantfile and add Ansible playbook and port forward
config.vm.network :forwarded_port, guest: 80, host: 12345
You can put this anywhere in between "Vagrant.configure .... end".

Now do a vagrant reload to restart the VM.  After the VM restarts, you can see the hello world page from your localhost by visiting:
$ curl -s http://localhost:12345
<html>
<body>
<h1>Hello world from inside the vagrant vm!</h1>
</body>
</html>
Try visiting http://localhost:12345 from your browser, hurray!

Even faster way, using Vagrant Provisioning

So far, we've set up this VM by using Ansible directly.  But Vagrant actually can do much of this behind the scenes.  Add the following to your Vagrantfile, again you can put this anywhere in between "Vagrant.configure .... end".
config.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbook.yaml"
end
Also change the playbook.yaml's hosts value from vagranthost to all:
$ head playbook.yaml
---
- hosts: all
  tasks:
  - name: test ping
    ping:

  - name: apt update
    become: yes
    apt: update_cache=yes

Destroy the old VM:
$ vagrant destroy -f
==> default: Forcing shutdown of VM...
==> default: Destroying VM and associated drives...
Now, when you ask Vagrant to up the machine, it will also run the Ansible playbook!
$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'precise64'...
==> default: Matching MAC address for NAT networking...
==> default: Setting the name of the VM: test_default_1473218743683_49067
==> default: Fixed port collision for 22 => 2222. Now on port 2200.
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 80 (guest) => 12345 (host) (adapter 1)
    default: 22 (guest) => 2200 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2200
    default: SSH username: vagrant
    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: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 4.2.0
    default: VirtualBox Version: 4.3
==> default: Mounting shared folders...
    default: /vagrant => /tmp/test
==> default: Running provisioner: ansible...
    default: Running ansible-playbook...

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

TASK [setup] *******************************************************************
ok: [default]

TASK [test ping] ***************************************************************
ok: [default]

TASK [apt update] **************************************************************
ok: [default]

TASK [install curl] ************************************************************
changed: [default]

TASK [install nginx] ***********************************************************


TASK [start nginx] *************************************************************
changed: [default]

TASK [copy index] **************************************************************
changed: [default]

PLAY RECAP *********************************************************************
default                    : ok=7    changed=4    unreachable=0    failed=0

Tada!  Great success!

You don't have actually to destroy the VM actually, you can run vagrant provision at any time, and as many times as you want.

$ vagrant provision

==> default: Running provisioner: ansible...
    default: Running ansible-playbook...

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

TASK [setup] *******************************************************************
ok: [default]

TASK [test ping] ***************************************************************
ok: [default]

TASK [apt update] **************************************************************
ok: [default]

TASK [install curl] ************************************************************
ok: [default]

TASK [install nginx] ***********************************************************
ok: [default]

TASK [start nginx] *************************************************************
ok: [default]

TASK [copy index] **************************************************************
ok: [default]

PLAY RECAP *********************************************************************
default                    : ok=7    changed=0    unreachable=0    failed=0

Final Tips and Lessons Learned


  1. You can always use "-vvvv" in an Ansible command to get really really verbose debug output to see behind the scenes what is happening.
  2. The Ansible commands "become_user" and "become_method" don't do anything unless you have "become: yes" in the playbook (either that task, or globally)!
  3. Some Ansible commands are not backward compatible, for the many tutorials on the web, always note the version of Ansible they are running.  The above is for Ansible v2.1.1.0, Vagrant 1.8.5, Virtualbox 4.3.36_Ubuntur105129 on Ubuntu 14.04.
  4. Ansible and Vagrant are awesome and very powerful, but there are a lot of growing pains and the devil is always in the details.  I hope this entry has helped you in some small way, cheers!