About Ansible
Ansible is an open-source IT configuration management, deployment, and orchestration tool. It aims to provide large productivity gains for a wide variety of automation challenges.
It's mostly addressed to IT operations, administrators, & decision-makers, helping them achieve operational excellence across their entire infrastructure ecosystem.
Backed by Red Hat and a loyal open-source community, it is considered an excellent option for configuration management, infrastructure provisioning, and application deployment use cases.
Its automation opportunities are endless across hybrid clouds, on-premises infrastructure, and IoT, and it's an engine that can greatly improve the efficiency and consistency of your IT environments.
Ansible Architecture
Ansible Architecture is too simple Just one management node and others will be hosted where we want our configuration management through an SSH connection.
Terms used in Ansible
Ansible Server: The machine where Ansible is installed and from which all tasks and playbooks will be run.
Module: A module is a command or set of similar commands meant to be executed on the client side.
Task: A task is a section that consists of a single procedure to be completed.
Role: A way of organizing tasks and related files to be later called a playbook.
Fact: Information fetched from the client system from the global variables with the gather facts operation.
Inventory: A file containing data about the Ansible client server.
Play: execution of a playbook.
Handler: A task that is called only if a notifier is present.
Notifier: A section attributed to a task that calls a handler if the output is changed.
playbook: It consists of YAML format code describing tasks to be executed.
Host: Nodes, which are automated by Ansible.
How does Ansible work?
Ansible uses the concepts of control and managed nodes. It connects from the control node—any machine with Ansible installed—to the managed nodes, sending commands and instructions to them.
The units of code that Ansible executes on the managed nodes are called modules.
Each module is invoked by a task, and an ordered list of tasks together forms a playbook. Users write playbooks with tasks and modules to define the desired state of the system.
The managed machines are presented in a simplistic inventory file that groups all the nodes into different categories.
Ansible leverages a very simple language, YAML, to define playbooks in a human-readable data format that is easy to understand from day one.
Even more, Ansible doesn't require the installation of any extra agents on the managed nodes, so it's simple to start using it.
Typically, the only thing a user needs is a terminal to execute Ansible commands and a text editor to define the configuration files.
Why to install Ansible
To start using Ansible, you will need to install it on the control node; this could be your laptop, for example. From this control node, Ansible will connect and manage other machines and orchestrate different tasks
Create a master node:
Launch an instance with any type of server.
Create a new secret key because we will use this key later.
Connect with ssh client
Install Ansible
Ansible is written in Python. So we need to install pip.
sudo apt-add-repository ppa:ansible/ansible
sudo apt update
sudo apt install ansible
Note: We added a pip repository to our system.
To check if Ansible is installed or not, go to the inventory file and check it. If it exists, then the Ansible setup is done.
cat /etc/ansible/hosts
The output looks like:
ubuntu@ip-172-31-26-247:~$ cat /etc/ansible/hosts
# This is the default ansible 'hosts' file.
#
# It should live in /etc/ansible/hosts
#
# - Comments begin with the '#' character
# - Blank lines are ignored
# - Groups of hosts are delimited by [header] elements
# - You can enter hostnames or ip addresses
# - A hostname/ip can be a member of multiple groups
# Ex 1: Ungrouped hosts, specify before any group headers:
## green.example.com
## blue.example.com
## 192.168.100.1
## 192.168.100.10
# Ex 2: A collection of hosts belonging to the 'webservers' group:
## [webservers]
## alpha.example.org
## beta.example.org
## 192.168.1.100
## 192.168.1.110
# If you have multiple hosts following a pattern, you can specify
# them like this:
## www[001:006].example.com
# You can also use ranges for multiple hosts:
## db-[99:101]-node.example.com
# Ex 3: A collection of database servers in the 'dbservers' group:
## [dbservers]
##
## db01.intranet.mydomain.net
## db02.intranet.mydomain.net
## 10.25.1.56
## 10.25.1.57
# Ex4: Multiple hosts arranged into groups such as 'Debian' and 'openSUSE':
## [Debian]
## alpha.example.org
## beta.example.org
## [openSUSE]
## green.example.com
## blue.example.com
Now, create another three Ansible servers. It could be any OS.
If all servers are running, then copy their public IPs. In my case, like:
Ansible_servers_1 = 3.82.37.204
Ansible_servers_2 = 107.21.78.126
Ansible_servers_3 = 50.17.74.239
Now go to your master node and edit your inventory file according to your server's public IPs.
To edit an inventory file, run the following commands:
sudo nano /etc/ansible/hosts
Now we will add our three public IPs as ansible host servers.
[servers]
server_1 ansible_host=3.82.37.204
server_2 ansible_host=107.21.78.126
server_3 ansible_host=50.17.74.239
Here server_1 is given a name; it could be any name. ansible_host is an ansible variable. It is fixed.
Run the command to ping servers.
ansible servers -m ping
Note: -m is the denoted module.
Once we run this command, we will see an error because we need to ssh the connection first before pinging it. The public key is missing.
The error message is like this:
To solve this issue, just follow the next steps:
Open your server through the ssh client, go to the Download folder and run the following command to copy access key
Run the following command:
scp -i "ansible-all-access-key.pem" ansible-all-access-key.pem ubuntu@ec2-54-89-168-215.compute-1.amazonaws.com:/home/ubuntu/.ssh
In the last part of our command (/home/ubuntu/ .ssh) this part was taken from the server.
Like:
Now we have our ssh key. Now we will give this key to our Ansible inventory file.
For that, edit the hosts file by following the following command:
sudo nano /etc/ansible/hosts
[servers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=/home/ubuntu/.ssh/ansible-all-access-key.pem
Note: We set up ansible variables. The first is for server names. The second one is for the Python interpreter, and the third one is for SSH key authentications.
Now we need to give file permission to our public key file because this user has no permission to execute this file.
Run the command to ping servers.
ansible servers -m ping
Note: -m is the denoted module.
The result would be looks like:
To see the disk spaces of all servers:
ansible servers -a "df -h"
Note: -a means action, like a command
Looks like:
Update all servers by the following command
ansible servers -a "sudo apt update"
Check uptime:
ansible servers -a "uptime"
Note: -m for module and -a for action. -a will be used for normal commands and -m for complex commands.
Now As a devOps engineer, we will work in several environments. So for each environment, we need an inventory file.
Multi-environment
Make a directory name ansible go to the directory.
Make a directory inventories go to the directory
and create a file name: prod_inv
#prod_inv
[servers]
prod_1 ansible_host=3.82.37.204
[servers:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_private_key_file=/home/ubuntu/.ssh/ansible-all-access-key.pem
Now run this command for prod_inve. It means we designed it for production.
Run the following command:
ansible -i prod_inv servers -m ping
The output looks like the following one.
Note: This inventory file is for specific purposes.
Now create the same file for the development server.
Run the following command:
ansible -i dev_inv servers -m ping
Ansible Galaxy: https://galaxy.ansible.com/ui/
Ansible Playbook
Interview Question: How will you write a playbook in Ansible?
Introduction to Ansible Playbooks:
Playbooks are the simplest way in Ansible to automate repeating tasks in the form of reusable and consistent configuration files. Playbooks are defined in YAML files and contain any ordered set of steps to be executed on our managed nodes.
As mentioned, tasks in a playbook are executed from top to bottom. At a minimum, a playbook should define the managed nodes to target and some tasks to run against them.
In playbooks, data elements at the same level must share the same indentation, while items that are children of other elements must be indented more than their parents.
Let's take a look at a simple playbook to get an idea of how that looks in practice.
For the purposes of this demo, we will use a simple playbook that runs against all hosts, copies a file, creates a user, and upgrades all apt packages on the remote machines.
Write a playbook that will show the date:
Create a YAML file:
-
name: Date playbook
hosts: servers
tasks:
- name: this will show the date
command: date
Note: Playbook YAML files start with the "-" sign, and this hyphen means it is a list.
Playbook Name: "Date playbook"
- This is just a user-defined name for the playbook to help identify its purpose.
Hosts: "servers"
- This specifies the target hosts or servers on which the tasks defined in this playbook will be executed. You would need to define these servers in your Ansible inventory file.
Tasks: A list of tasks that Ansible should execute on the specified hosts.
- There's only one task defined in this playbook.
Task Name: "this will show the date"
- This is a user-defined name for the task, which helps in identifying what the task is meant to do.
Command Task: The actual task is using the "command" module.
- The command module is used to execute shell commands on the remote hosts.
Command: "date"
- This is the shell command that will be executed on the target hosts. It simply runs the
date
command to display the current date and time.
- This is the shell command that will be executed on the target hosts. It simply runs the
Run this playbook:
ansible-playbook date.yaml
The output looks like:
Install NGINX through Ansible
- Create a YAML file, write the playbooks, and run them in your master.
-
name: This playbook will install nginx
hosts: servers
become: yes
tasks:
- name: install nginx
apt:
name : nginx
state: latest
- name: start nginx
service:
name: nginx
state: started
enabled: yes
Note: "become" means, as a root user, install nginx. Here are two tasks: The first task is to install nginx, and the second task is to start nginx. If you use Ubuntu, then use "apt" or "yum" for centos.
Second: state means started
Playbook Name: "This playbook will install nginx"
- This is just a user-defined name for the playbook to describe its purpose.
Hosts: "servers"
- This specifies the target hosts or servers where the tasks defined in this playbook will be executed. You should define the "servers" group or list the specific server hostnames/IPs in your Ansible inventory file.
Become: "yes"
- The
become: yes
setting indicates that Ansible should elevate its privileges to perform tasks as a superuser (e.g., usingsudo
) when necessary. This is typically required to install packages and manage services.
- The
Tasks: A list of tasks to be executed on the target servers.
- There are two tasks defined in this playbook.
Task 1: "install nginx"
Uses the
apt
module to install Nginx.name
: "nginx" specifies the package name.state: latest
ensures that the package is updated to the latest available version.
Task 2: "start nginx"
Uses the
service
module to start and enable the Nginx service.name
: "nginx" specifies the service name.state: started
ensures that the service is started.enabled: yes
ensures that the service is set to start on boot.
Run the following command:
ansible-playbook install_nginx.yml
The output looks like this:
To check if the nginx server is installed or not, Go to the servers and check manually, or just run the command below the specific servers.
service nginx status
The output looks like this:
Now, if you want to stop nginx servers for all servers, then just change your yml file a little bit:
Change: "state: stopped"
Ansible Condition: When
Solution: Just create a condition.
-
name: This playbook will install Apache
hosts: servers
become: yes
tasks:
- name: install apache
apt:
name : apache2
state: latest
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
- name: start apache
service:
name: apache2
state: started
enabled: yes
Note: Condition is : when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
For CentOS or Red Hat
-
name: This playbook will install Apache
hosts: servers
become: yes
tasks:
- name: install httpd
yum:
name : httpd
state: latest
when: ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat Enterprise Linux'
- name: start httpd
service:
name: httpd
state: started
enabled: yes
Deploy a simple HTML webpage through Ansible.
Write a playbook.
-
name: This is a simple html project.
hosts: servers
become: yes
tasks:
- name: Install nginx
apt:
name: nginx
state: latest
- name: Start nginx
service:
name: nginx
state: started
- name: Deploy Webpage
copy:
src: index.html
dest: /var/www/html
index.html
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
color: #333;
text-align: center;
}
h1 {
font-size: 36px;
margin-top: 50px;
color: #6130e8;
}
p {
font-size: 18px;
margin: 20px 0;
}
</style>
</head>
<body>
<h1>Thank you for your learning with Brothers and sisters</h1>
<p>Keep Practicing</p>
</body>
</html>
Now we will apply this web page application to specific servers. In my case, I will deploy the production server (prod_inv)
ansible-playbook -i /home/ubuntu/ansible/inventories/prod_inv deploy_webpage.yml
Once it is done, it will appear like this:
Now go to your prod_inv server, take a public IP, and browse it. You will see your webpage is running successfully. In my case, it will look like this:
Ansible _conditios: when, if and else
-
name: This playbook will install Apache/Httpd
hosts: shuvo_devops
become: yes
tasks:
- name: Install Apache Web-server
apt:
name: apache2
state: latest
when: "ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'"
- name: Install httpd
yum:
name: httpd
state: latest
when: "ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat'"
- name: start Apache/httpd
service:
name: "{{ 'httpd' if (ansible_distribution == 'CentOS' or ansible_distribution == 'Red Hat') else 'apache2' }}"
state: started
enabled: yes
Ansible Tower
Ansible Tower, now known as Red Hat Ansible Automation Platform, is an enterprise-level automation platform built on top of Ansible. It provides a web-based interface, role-based access control, job scheduling, graphical inventory management, and many other features to make it easier to use and manage Ansible in larger organizations. Here are some key features and components of Ansible Tower:
Web-Based Dashboard: Ansible Tower provides a user-friendly web interface that allows users to access and manage Ansible automation tasks through a graphical dashboard.
Role-Based Access Control (RBAC): Ansible Tower offers fine-grained access control with RBAC, allowing you to define who can access and execute automation tasks and who cannot. This is particularly useful for managing user access in large organizations.
Job Templates: Job Templates in Ansible Tower are reusable configurations for running Ansible jobs. They define playbooks, inventory, and other job-related settings. This allows for easier job execution and scheduling.
Inventory Management: Ansible Tower provides an easy-to-use interface for managing your inventory. You can define hosts, groups, and variables through the web interface.
Job Scheduling: Ansible Tower allows you to schedule automation jobs to run at specific times or on a recurring basis. This is particularly useful for regular maintenance and other repetitive tasks.
Notifications: Tower supports notifications, so you can get informed about job status through various channels such as email, Slack, or other messaging platforms.
API: Ansible Tower includes a RESTful API, which allows integration with other systems and tools, making it a versatile tool for automation within a larger ecosystem.
Workflow Automation: Tower can create and manage workflows that string together multiple Ansible playbooks and automate more complex tasks with dependencies and conditional logic.
Multi-Tenancy: Tower supports multi-tenancy, allowing different teams or departments to have their isolated Ansible environments.
Audit Trail: It keeps a detailed log of all activities, making it easier to track and audit changes and job results.
Scaling: Tower can be set up in a high-availability (HA) configuration, allowing it to scale and meet the needs of larger environments.
Integration: Ansible Tower can integrate with various source control systems, cloud providers, and other tools to facilitate seamless automation across different platforms and technologies.
Ansible Tower is a UI-based Ansible service.
Ansible conditions with the team
---
- name: Install Nginx on multiple servers with loop
hosts: servers
become: yes
vars:
nginx_servers:
- server1
- server2
- server3
tasks:
- name: Install Nginx on servers
apt:
name: nginx
state: latest
with_items: "{{ nginx_servers }}"
In this playbook:
We define a list of server names in the
nginx_servers
variable. You can add more server names to this list as needed.We use the
with_items
directive to iterate through thenginx_servers
list. This was the older way to loop through items in Ansible.Inside the task, we use the
apt
module to install Nginx on each server specified in thenginx_servers
list. Thename
parameter specifies "nginx" as the package to be installed.
While with_items
works, note that Ansible has moved to a more modern syntax for looping using loop
or loop_control
. If you're using an older version of Ansible, the with_items
approach will work, but if you have a more recent Ansible version, consider using the loop
approach for better compatibility with newer releases.
Ansible Loops
---
- name: Install Nginx on multiple servers with a condition
hosts: all
become: yes
vars:
nginx_servers:
- server1
- server2
- server3
install_nginx: true
tasks:
- name: Install Nginx on servers
apt:
name: nginx
state: latest
with_items: "{{ nginx_servers }}"
when: install_nginx
In this playbook:
We define a list of server names in the
nginx_servers
variable.We use the
install_nginx
variable as a condition. Ifinstall_nginx
is set totrue
, Nginx will be installed on the specified servers. You can change the condition value to control when Nginx should be installed.Inside the task, we use the
apt
module to install Nginx on each server in thenginx_servers
list. Thewith_items
directive is used to loop through the list of server names. Thewhen
condition checks ifinstall_nginx
istrue
before proceeding with the installation.
With this playbook, you have the flexibility to control the installation of Nginx on multiple servers based on the install_nginx
condition. If install_nginx
is true
, Nginx will be installed on the specified servers; if it's set to false
, no installation will occur.
Example of another loop condition (MySQL)
- name: Install Mysql package
yum: name={{item}}
state=present
with_items:
- mysql-server
- MySQL-python
- libselinux-python
- libsemanage-python
- name: Configure SELinux to start mysql on any port
seboolean: name=mysql_connect_any state=true persistent=yes
when: ansible_selinux.status == "enable"
- name: Create Mysql configuration file
template: src=my.cnf.j2 dest=/etc/my.cnf
notify:
- restart mysql
- name: Start Mysql Service
service: name=mysqld
state=started enabled=yes
Extra Learning
Ansible Playbook Syntax
-name: intro to Ansible Playbooks
hosts: all
tasks:
-name: Copy file hosts with permissions
ansible.builtin.copy:
src: ./hosts
dest: /tmpThosts_backup
mode: '0644'
-name: Add the user 'mohammad'
ansible.builtin.user:
name: mohammad
become: yes
become_method: sudo
-name: Update all apt packages
apt:
force_apt_get: yes
upgrade: dist
become: yes
In the top section, we define the group of hosts on which to run the playbook and its name. After that, we define a list of tasks. Each of the tasks contains some information about the task and the module to be executed, along with the necessary arguments.
To avoid specifying the location of our inventory file every time, we can define this via a configuration file (ansible.cfg).
Using Variables in Playbooks
Variables can be defined in Ansible at more than one level, and Ansible chooses the variable to use based on variable precedence.
Let's see how we can use variables at the playbook level.
The most common method is to use a vars block at the beginning of each playbook. After declaring them, we can use them in a task.
Use {{variable_name}} to reference a variable in a task.
-name: Variables playbook
hosts: all
vars:
state: latest
user: mohammad
tasks:
-name: Add the user {{user}}
ansible.builtin.user:
name: "{{user}}"
-name: Upgrade all apt packages
apt:
force_apt_get: yes
upgrade: dist
-name: Install the {{state}} of package "nginx"
apt:
name: "nginx"
state: "{{state}}"
In the above example, we have used the variables user and state. When referencing a variable as another variable value, we must add quotes around the value, as shown in our example.
Conditions in Ansible
Whenever we have different scenarios, we put conditions according to the scenario.
When statement
Sometimes you want to skip a particular command on a particular node.
-- #Condition Playbook
- hosts: demo <- group name
user: ansible
become: yes <-- sudo privilege
connection: ssh
tasks:
- name: Install Apache Server for Debian Family
command: apt-get -y install apache2
when: ansible_os_family == "Debian"
- name: Install Apache Server for RedHat Family
command: apt-get -y install apache2
when: ansible_os_family == "RedHat"
Vault in Ansible
Ansible allows you to keep sensitive data, such as passwords or keys, in encrypted files rather than plain text in your playbooks. Ansible uses the AES-256 encryption algorithm to encrypt the playbooks.
- Creating a new encrypted playbook
[ansible@ip]$ ansible-vault create <playbook_name>
- Edit the encrypted playbook
[ansible@ip]$ ansible-vault edit <playbook_name>
- To change the password.
[ansible@ip]$ ansible-vault rekey <playbook_name>
- To encrypt an existing playbook
[ansible@ip]$ ansible-vault encrypt <playbook_name>
- To decrypt an encrypted playbook
[ansible@ip]$ ansible-vault decrypt <playbook_name>
- To run vault playbooks
[ansible@ip]$ ansible-playbook --ask-vault-pass <playbook_name>
Roles in Ansible
- Default: It stores data about the role or application. Default variable
Ansible ad hoc commands
Using ad hoc commands is a quick way to run a single task on one or more managed nodes.
Some examples of valid use cases are rebooting servers, copying files, checking connection status, managing packages, gathering facts, etc.
The pattern for ad hoc commands looks like this:
ansible [host-pattern] -m [module] -a "[module options]"
host-pattern: the managed hosts to run against
m: the module to run.
a: The list of arguments required by the module
This is a good opportunity to use our first Ansible ad hoc command and, at the same time, validate that our inventory is configured as expected. Let's go ahead and execute a ping command against all our hosts:
$ansible -i hosts all -m ping
host1 | SUCCESS=>{
"ansible_facts":{
"discovered_interpreter_python": "/usr/bin/python"
}
"changed":false,
"ping":"pong"
}
host2 | SUCCESS =>{
"ansible_facts":{
"discovered_interpreter_python": "/usr/bin/python"
}
"changed":false,
"ping":"pong"
}
Nice! It seems like we can successfully ping the 2 hosts that we have defined in our host's file.
Next, run a live command only to the host2 node by using the -limit flag
ansible all -i hosts --limit host2 -a "/bin/echo hello"
Output:
host2|CHANGED| rc=0>>
hello