PolarSPARC

Introduction to Ansible - Part 1


Bhaskar S 06/17/2018


Overview

Ansible is an agentless open-source automation tool that enables Enterprise IT teams to perform one or more tasks, such as:

So, how does Ansible actually work ???

At a high level, Ansible makes ssh connections to one or more specified hosts and executes each of the listed tasks (one at a time) across all the specified hosts in parallel.

The host from where Ansible is executed, is referred to as the Controller, while the hosts being managed are referred to as the Targets.

The following are some of the commonly used terms in the context of Ansible:

Term Description
Host A managed remote server with an IP address. Could be a bare metal server, a virtual machine, or a container, either on-premise or the cloud
Group A list of hosts targeted and managed as a single entity
Inventory A text file that lists the targeted hosts and groups
Module A reusable script, performing some action, that can be executed on a target (host or group)
Task An action performed on a target (host or group) using a module
Play A list of tasks performed on a target (host or group)
Playbook A list of plays

Installation

The installation is on a Ubuntu 16.04 LTS based Linux desktop.

Download and install the Python 3 version of the Anaconda distribution.

Install Ansible by executing the following command:

$ conda install -c conda-forge ansible

To emulate multiple hosts, we will make use of virtual machine instances. For this, we will install VirtualBox and Vagrant by executing the following commands:

$ sudo apt-get update

$ sudo apt-get install virtualbox

$ sudo apt-get install vde2 virtualbox-guest-additions-iso

$ sudo apt-get install vagrant

Setup

Assume a hypothetical user alice with the home directory located at /home/alice.

Create two directories - one called Ansible and another called Vagrant under the home directory /home/alice by executing the following command:

mkdir -p Ansible Vagrant

Create and save a text file called Vagrantfile, with the following contents, in the directory /home/alice/Vagrant:

Vagrantfile
COUNT = 2

Vagrant.configure(2) do |config|
  config.vm.provider "virtualbox" do |vb|
    vb.cpus = 1
    vb.memory = 2048
  end
  
  (1..COUNT).each do |i|
    config.vm.define "host#{i}" do |host|
      host.vm.box = "ubuntu/xenial64"
      host.vm.hostname = "host#{i}"
      host.vm.synced_folder ".", "/vagrant", disabled: true
      host.vm.network "private_network", ip: "192.168.100.#{i+10}"
    end
  end
end

The above Vagrantfile defines 2 virtual machines, each with 1 cpu and 2 MB memory, based on Ubuntu 16.04 LTS, with host names of host1 (ip address 192.168.100.11) and host2 (ip address 192.168.100.12) respectively.

Change to the directory /home/alice/Vagrant and execute the following command to start the 2 virtual machine instances:

vagrant up

The following would be a typical output:

Output.1

Bringing machine 'host1' up with 'virtualbox' provider...
Bringing machine 'host2' up with 'virtualbox' provider...
==> host1: Importing base box 'ubuntu/xenial64'...
==> host1: Matching MAC address for NAT networking...
==> host1: Checking if box 'ubuntu/xenial64' is up to date...
==> host1: Setting the name of the VM: Vagrant_host1_1529241165789_22840
==> host1: Clearing any previously set network interfaces...
==> host1: Preparing network interfaces based on configuration...
    host1: Adapter 1: nat
    host1: Adapter 2: hostonly
==> host1: Forwarding ports...
    host1: 22 (guest) => 2222 (host) (adapter 1)
==> host1: Running 'pre-boot' VM customizations...
==> host1: Booting VM...
==> host1: Waiting for machine to boot. This may take a few minutes...
    host1: SSH address: 127.0.0.1:2222
    host1: SSH username: vagrant
    host1: SSH auth method: private key
    host1: Warning: Connection reset. Retrying...
    host1: Warning: Remote connection disconnect. Retrying...
    host1: Vagrant insecure key detected. Vagrant will automatically replace
    host1: this with a newly generated keypair for better security.
    host1: Inserting generated public key within guest...
    host1: Removing insecure key from the guest if it's present...
    host1: Key inserted! Disconnecting and reconnecting using new SSH key...
==> host1: Machine booted and ready!
==> host1: Checking for guest additions in VM...
==> host1: Setting hostname...
==> host1: Configuring and enabling network interfaces...
==> host2: Importing base box 'ubuntu/xenial64'...
==> host2: Matching MAC address for NAT networking...
==> host2: Checking if box 'ubuntu/xenial64' is up to date...
==> host2: Setting the name of the VM: Vagrant_host2_1529241250945_29687
==> host2: Fixed port collision for 22 => 2222. Now on port 2200.
==> host2: Clearing any previously set network interfaces...
==> host2: Preparing network interfaces based on configuration...
    host2: Adapter 1: nat
    host2: Adapter 2: hostonly
==> host2: Forwarding ports...
    host2: 22 (guest) => 2200 (host) (adapter 1)
==> host2: Running 'pre-boot' VM customizations...
==> host2: Booting VM...
==> host2: Waiting for machine to boot. This may take a few minutes...
    host2: SSH address: 127.0.0.1:2200
    host2: SSH username: vagrant
    host2: SSH auth method: private key
    host2: Warning: Connection reset. Retrying...
    host2: Warning: Remote connection disconnect. Retrying...
    host2: Vagrant insecure key detected. Vagrant will automatically replace
    host2: this with a newly generated keypair for better security.
    host2: Inserting generated public key within guest...
    host2: Removing insecure key from the guest if it's present...
    host2: Key inserted! Disconnecting and reconnecting using new SSH key...
==> host2: Machine booted and ready!
==> host2: Checking for guest additions in VM...
==> host2: Setting hostname...
==> host2: Configuring and enabling network interfaces...

To check the status of the 2 virtual machine instances, execute the following command:

vagrant status

The following would be a typical output:

Output.2

Current machine states:

host1                     running (virtualbox)
host2                     running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

From the Output.2 above, we see the 2 virtual machine instances running and we are now ready to get our hands dirty with Ansible.

Hands-on with Ansible

Create and save an inventory file called hosts, with the following contents, in the directory /home/alice/Ansible:

hosts
192.168.100.11
192.168.100.12

[host1]
192.168.100.11

[host2]
192.168.100.12

[myhosts]
192.168.100.11
192.168.100.12

The above inventory file defines the two target hosts with ip addresses 192.168.100.11 and 192.168.100.12 respectively. In addition, there are definitions for the 3 groups - host1, host2, and myhosts.

A group is defined by using the format [group-name] on its own line followed by a list of target hosts belonging to the group-name, each on its own line.

To ping the host with ip address 192.168.100.11 using Ansible, change to the directory /home/alice/Ansible and execute the following command:

$ ansible 192.168.100.11 -i hosts -m ping

The -i command-line option is used to specify the inventory file, while the -m command-line option is used to specify the module to use.

The following would be a typical output:

Output.3

192.168.100.11 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.100.11 port 22: Connection timed out\r\n",
    "unreachable": true
}

Hmm !!! What happened here ???

From Output.1 above, we see the ssh address for the virtual machine instance with ip address 192.168.100.11 is mapped to the host address 127.0.0.1:2222. Similarly, the ssh address for the virtual machine instance with ip address 192.168.100.12 is mapped to the host address 127.0.0.1:2200.

Modify the inventory file called hosts (located in the directory /home/alice/Ansible) with the following contents:

hosts
192.168.100.11 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user='vagrant' ansible_ssh_private_key_file
='/home/alice/Vagrant/.vagrant/machines/host1/virtualbox/private_key'
192.168.100.12 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2200 ansible_ssh_user='vagrant' ansible_ssh_private_key_file
='/home/alice/Vagrant/.vagrant/machines/host2/virtualbox/private_key'

[host1]
192.168.100.11

[host2]
192.168.100.12

[myhosts]
192.168.100.11
192.168.100.12

From the above inventory file called hosts, we see some parameters (with values) at the end of each of the hosts. These are referred to as the inventory Behavior Parameters and allow one to control how Ansible interacts with the target hosts.

The parameter ansible_ssh_host specifies the ssh host, ansible_ssh_port the ssh port, ansible_ssh_user the ssh user, and ansible_ssh_private_key_file the shh private key.

Let us retry the Ansible ping to the host with ip address 192.168.100.11 by executing the following command:

$ ansible 192.168.100.11 -i hosts -m ping

Type 'yes' when prompted to continue.

The following would be a typical output:

Output.4

The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established.
ECDSA key fingerprint is SHA256:CFwZX0o1O5BJ8wAgc68oItcu1ATQaIoXViJbbFeRiqM.
Are you sure you want to continue connecting (yes/no)? yes
192.168.100.11 | FAILED! => {
    "changed": false,
    "module_stderr": "Shared connection to 127.0.0.1 closed.\r\n",
    "module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n",
    "msg": "MODULE FAILURE",
    "rc": 127
}

We will encounter a similar output if we Ansible ping the host with ip address 192.168.100.12.

From the Output.4 above, we see that the target hosts are missing Python, which is critical dependency for working with Ansible.

To install Python on the target host host1 (ip address 192.168.100.11), change to the directory /home/alice/Vagrant, and execute the following command:

$ vagrant ssh host1

The following would be a typical output:

Output.5

Welcome to Ubuntu 16.04.4 LTS (GNU/Linux 4.4.0-128-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.


Last login: Sun Jun 17 13:19:06 2018 from 10.0.2.2
vagrant@host1:~$

At the command prompt, execute the following command to install Python:

$ sudo apt-get install -y python-minimal

Once the installation completes, the target host1 (192.168.100.11) will have Python.

Repeat the same installation steps for the target host2 (192.168.100.12) as well.

Once again, let us try the Ansible to ping the host with ip address 192.168.100.11 by executing the following command:

$ ansible host1 -i hosts -m ping

The following would be a typical output:

Output.6

192.168.100.11 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Walla !!! Success at last.

To ping all the targets (the 2 virtual machine instances - host1 and host2) using Ansible, execute the following command:

$ ansible all -i hosts -m ping

The following would be a typical output:

Output.7

192.168.100.12 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.100.11 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

The command-line option all is a built-in alias representing all the target hosts.

To execute any command using Ansible, we use the command module. To execute the uname command on all the targets, execute the following command:

$ ansible myhosts -i hosts -m command -a "/bin/uname -a"

The following would be a typical output:

Output.8

192.168.100.12 | SUCCESS | rc=0 >>
Linux host2 4.4.0-128-generic #154-Ubuntu SMP Fri May 25 14:15:18 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

192.168.100.11 | SUCCESS | rc=0 >>
Linux host1 4.4.0-128-generic #154-Ubuntu SMP Fri May 25 14:15:18 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

References

Introduction to Vagrant - Part 1

Introduction to Vagrant - Part 2

Official Ansible Documentation



© PolarSPARC