Automation with Ansible

Shefali Sharma
CodeX
Published in
10 min readAug 28, 2021

IT automation is a way of creating software and systems that replace repetitive tasks and reduce manual intervention. This methodology accelerates the delivery of IT infrastructure, provides significant cost savings and allows IT staff to focus more on strategic work instead of administrative work.

Ansible is an open-source tool that makes IT automation very smooth. It uses a markup language written in Python. It creates a very strong impact on our workflow as it enables us to automate configuration management, cloud provisioning, application deployment, intra-service orchestration, and many other IT needs. Ansible was created by Michael DeHaan and acquired by Red Hat in 2015.

Image Courtesy

How Ansible works ⚙️

Ansible is installed in the Controller node. In a data center Controller node is the node that controls or manages other nodes. The nodes that are being managed are known as Managed Nodes or Target Nodes. Controller nodes manage Target nodes (with the help of Ansible scripts) via network protocols like SSH.

Image Courtesy

Features ✨

⚡️ Declarative

In declarative languages like Ansible, SQL, we only have to specify What is to be done, not How to do it rest everything is automatically managed for us. Therefore, we can run the same Ansible code in different environments. For instance, the command to install a package in Ubuntu is different from the one in Red Hat. In Ubuntu, we use the apt package manager and in Red Hat, we use yum. But Ansible enables us to install the required packages in both environments with the same command due to its declarative nature.

Languages like python and ruby do not offer this type of functionality because they are imperative in nature. In imperative languages, we have to specify both “What is to be done” and “How to do it”.

⚡️ Simple

Ansible provides a wealth of modules to its developers (we can also create our own). These modules are used for performing a specific task on the Target Nodes. They can be referenced with the help of a command or a script. Some of these tasks include installing packages, rebooting the system, integrating with some other technology like AWS, Gmail, etc.

The declarative nature of Ansible comes from these modules. For the same purpose, modules for many different types of environments (Red Hat, Debian, Windows…) are available. Whenever we execute an Ansible script on a Target Node, Ansible first gathers all the necessary information about it (its OS, version…). This information is stored in a variable, known as Ansible Facts. Ansible then executes the appropriate module (for that OS/version) on the Target Node. We only have to specify the module name and the required parameters here, the rest of everything is automatically managed by Ansible.

⚡️ Agentless

Ansible enables us to manage the target nodes without having to install any additional software over them.

⚡️ Idempotent

In Mathematics Idempotent operations, except for the initial first time, do not change the result whenever they are applied on a particular set of operands. For instance taking the absolute value of a number, multiplication with 1. Similarly, in Ansible execution of the same code, multiple times (without any intervening actions) on Target Nodes have the same effect as that of executing it once.

We write Ansible code keeping in mind the desired state of the and Target nodes and not their current state. Ansible figures out the current state of the system from the Facts. Then only that part of the script is executed over the Target node that does not match with its desired state. This approach saves computational resources from unnecessary usage.

Image Courtesy

The above image shows the output obtained after executing an Ansible script. Here the part that did not match with the desired state has been modified by Ansible and is indicated by amber color. The part that matched the desired state is not touched by Ansible and is represented by green color.

⚡️ Parallelism

Ansible runs the same task in five hosts by default in parallel. This setting can also be modified.

⚡️ Integrations

Ansible can be easily integrated with different technologies.

Image Courtesy

Installation 📥

pip3 install ansible

NOTE: Ansible cannot be installed in Windows directly. There one has to use Cygwin, or WSL, or a Linux VM to install it.

Configuring Ansible

To configure Ansible we need to create its Inventory and Configuration file. An inventory file is a text file that stores details about the target nodes or hosts. It stores their IP Address and other login information. Whenever Ansible has to connect with the Target Nodes, it refers to this file.

mkdir /etc/ansible
touch /etc/ansible/hosts

This file can be initialized either in INI or in YAML format. For INI, the details of the respective hosts are given in the following manner.

<IP1> ansible_user=<user_name> ansible_ssh_pass=<password> ansible_connection=ssh
<IP2> ansible_user=<user_name> ansible_ssh_pass=<password> ansible_connection=ssh
<IP3> ansible_user=<user_name> ansible_ssh_private_key_file=<file_path> ansible_connection=ssh

Here <IP1>, <IP2> and <IP3> are the IP Addresses of the Hosts and ansible_user, ansible_ssh_pass, ansible_connection, ansible_ssh_private_key_file are the Host Variables. They specify the details regarding the connection.

It is also possible to group a collection of hosts under one name. These collections are known as groups.

[web_server]
<IP1> ansible_user=<user_name> ansible_ssh_pass=<password> ansible_connection=ssh
<IP2> ansible_user=<user_name> ansible_ssh_pass=<password> ansible_connection=ssh
[load_balancer]
<IP3> ansible_user=<user_name> ansible_ssh_private_key_file=<file_path> ansible_connection=ssh

Here <IP1> and <IP2> are the part of web_server group and <IP3> is the part of load_balancer group.

Two default groups are also provided:

  • all: It comprises all the hosts.
  • ungrouped: It comprises all the hosts that are not part of any other group except all.

Now creating Ansible’s Configuration file in the same directory and initializing it with the following value.

# This is ansible.cfg 
[defaults]
inventory = /etc/ansible/hosts

Finally, install sshpass to complete the configuration.

Ansible Commands 👨🏻‍💻

Ansible instructions are given either through ad-hoc commands or through the playbook. An ad-hoc command is a one-line command that is operated on the hosts. An ad-hoc command looks like this:

ansible <group or hostname> -m <module> -a "<module parameters>"

Checking connectivity with all the hosts

ansible all -m ping

Gathering facts about the hosts. Facts are the built-in variables of Ansible, we can use these variables in our scripts. The output of the following command is returned in JSON format.

ansible all -m setup
Output in JSON format

Creating a file, using shell module for executing host-based shell commands over the Target Nodes.

ansible all -m shell -a 'touch /myfile.txt'
New file created in the first Target Node
New file created in the second Target Node

Checking current disk usage in all the Target Nodes. Here we are logging in as a non-root user.

ansible all -m shell -a 'fdisk -l' -u user1 --become -K
  • -u is used to specify the username
  • --become is used for privilege escalation
  • -K is used to prompt for the privilege escalation password of the user

NOTE: By default, the shell module is not idempotent we have to use conditional statements in our scripts to make it idempotent. It is good to use a module even when we know the right commands for that task because modules cover all the edge cases.

The main drawback with Ansible Commands is that we can give only one command at a time. We can overcome this drawback with the help of Ansible scripts called Playbooks. They are written in YAML format and are executed sequentially. They are like a to-do list and enable us to perform complex tasks over the target nodes.

Building blocks of an Ansible Playbook 📋

Image Source

Plays

A play is a set of tasks mapped to a set of hosts.

Hosts

It is a keyword that defines the group of IP Addresses on which we want to execute our play.

hosts: 192.168.0.33, 192.168.0.144

Instead of manually typing the IP Addresses we can use the group variable to specify the hosts.

hosts: <group1>, <group2>

Tasks

A task is a unit of work in a play. It specifies a module and its parameters. We may also use an optional name keyword for naming our tasks, it enables us to define our tasks and also helps in debugging.

Installing packages on the Target Nodes: I have configured two Target Nodes for the practical, one runs on Ubuntu and the other on RedHat. Git is not installed in either of them.

- hosts: all
tasks:
- name: "Installing git"
package:
name: git
state: present
  • package module is used to detect the default package manager of Target Nodes and install the required software over them.
  • Use become module if you are logging in as a non-root user.

Command to check a playbook for syntax errors.

ansible-playbook <pb_name> --syntax-check

Executing the playbook.

ansible-playbook <pb_name>
Git installed on RHEL8 OS
Git installed on Ubuntu OS

This was the demonstration of the Declarative nature of Ansible. Note that how easily we executed the same script over two different environments.

Comments

Comments in YAML begin with a hash mark (#) and continue to the end of the line.

# This is a comment

Variables

Variables help us to increase the flexibility of our playbooks by replacing hardcoded values with dynamic values.

Variables are declared using the vars module. We reference those variables in the other parts of our playbook using Jinja Templating Engine.

- hosts: all
tasks:
- name: "Adding user account"
vars:
username: "Susan"
groupname : "adm"
pw: "pass123"
# Using user module to create a new user
user:
name: "{{ username }}"
state: present
group: "{{ groupname }}"
password: "{{ pw }}"

List: A list is used for storing a group of values under one name.

- hosts: all
tasks:
- name: "Ensure file is in place"
vars:
mylist:
- "/ws/test.txt"
- "/files/test.txt"
# Copy module copies a file from Controller Node to Target Node
copy:
# Referencing list variables
src: "{{ mylist[0] }}"
dest: "{{ mylist[1] }}"
owner: root
group: root
mode: 0644

Variable File: For easy maintenance, variables can be also be stored in a separate file. We can reference the file variables in the following manner.

Variable File:

# This is /ws/vars.yml
nam: "nginx"
state: "started"
enabled: "yes"

Playbook:

- hosts: all
vars_files:
- "/ws/vars.yml"
tasks:
- name: "Enable and Start the Service persistently"
service:
name: "{{ nam }}"
state: "{{ state }}"
enabled: "{{ enabled }}"

Registered variables: We can store the output of a particular Ansible task in a variable. These variables are known as Registered variables and can be used in the later parts of the play.

User Input

User input can be taken with help of the vars_prompt module. Here the user input is hidden by default but it can be made visible by setting private: no.

- hosts: all
vars_prompt:
- name: username
prompt: Enter the name of the user that wou want to add
private: no
- name: groupname
prompt: Enter the group in which you want to add the user
private: no
- name: password
prompt: Enter the password
tasks:
- name: "Adding user account."
user:
name: "{{ username }}"
state: present
group: "{{ groupname }}"
password: "{{ password }}"

Program Output

debug module is used to print output to the console. The following Playbook prints the content of a file.

- hosts: all
tasks:
- name: Print content of a file on the screen
shell: cat /etc/passwd
register: file_content
- debug:
var: file_content

Conditionals

In a playbook, you may want to execute different tasks or have, depending upon some condition, the value of a fact, or a variable. For this, you have to use the when clause.

The following playbook checks for disk usage.

- hosts: all
tasks:
- name: Check /tmp freespace
shell: df /tmp --output\=avail | tail -1
register: tmp_freespace

- fail:
msg: /tmp does not have the minimum space required to continue (3Gb requested).
when: tmp_freespace.stdout|float is lt 3000000

Source:

https://gist.github.com/alexanderadam/5661307ef6ad1f4f42fa954524104219

Loop

Loop is used for repeating a task multiple times. The following loop iterates over a list

- hosts: all
tasks:
- name: "Install the packages in the list"
vars:
package_list:
- perl
- git
- mariadb-server
package: name="{{ item }}" state=present
with_items: "{{ package_list }}"

For more information, refer to the official Ansible documentation.

Here is a Cheat Sheet by Skalavala for Jinja Templating:

Cheat Sheet by Skalavala

NOTE: While writing playbooks, we should focus mainly on small tasks instead of big complex ones. This way work is more manageable and less intimidating. It is good to break down our requirements into simpler steps and write code for those steps.

Concluding Remarks

Ansible is a state-of-the-art automation tool. Many tech giants including NASA, also use it to speed up their service delivery. The scope of Ansible is big. It plays a huge role in the DevOps cycle and can also be used to deploy IoT devices.

Image source

--

--