ANSIBLE Basics

ANSIBLE Basics

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

  1. Make a directory name ansible go to the directory.

  2. Make a directory inventories go to the directory

  3. 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.

  1. Playbook Name: "Date playbook"

    • This is just a user-defined name for the playbook to help identify its purpose.
  2. 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.
  3. Tasks: A list of tasks that Ansible should execute on the specified hosts.

    • There's only one task defined in this playbook.
  4. 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.
  5. Command Task: The actual task is using the "command" module.

    • The command module is used to execute shell commands on the remote hosts.
  6. 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.

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

  1. Playbook Name: "This playbook will install nginx"

    • This is just a user-defined name for the playbook to describe its purpose.
  2. 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.
  3. Become: "yes"

    • The become: yes setting indicates that Ansible should elevate its privileges to perform tasks as a superuser (e.g., using sudo) when necessary. This is typically required to install packages and manage services.
  4. Tasks: A list of tasks to be executed on the target servers.

    • There are two tasks defined in this playbook.
  5. 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.

  6. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. Notifications: Tower supports notifications, so you can get informed about job status through various channels such as email, Slack, or other messaging platforms.

  7. 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.

  8. Workflow Automation: Tower can create and manage workflows that string together multiple Ansible playbooks and automate more complex tasks with dependencies and conditional logic.

  9. Multi-Tenancy: Tower supports multi-tenancy, allowing different teams or departments to have their isolated Ansible environments.

  10. Audit Trail: It keeps a detailed log of all activities, making it easier to track and audit changes and job results.

  11. Scaling: Tower can be set up in a high-availability (HA) configuration, allowing it to scale and meet the needs of larger environments.

  12. 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:

  1. We define a list of server names in the nginx_servers variable. You can add more server names to this list as needed.

  2. We use the with_items directive to iterate through the nginx_servers list. This was the older way to loop through items in Ansible.

  3. Inside the task, we use the apt module to install Nginx on each server specified in the nginx_servers list. The name 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:

  1. We define a list of server names in the nginx_servers variable.

  2. We use the install_nginx variable as a condition. If install_nginx is set to true, Nginx will be installed on the specified servers. You can change the condition value to control when Nginx should be installed.

  3. Inside the task, we use the apt module to install Nginx on each server in the nginx_servers list. The with_items directive is used to loop through the list of server names. The when condition checks if install_nginx is true 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