Ansible with Task Automation Demo
Tags: ansible, automation, linux, devops
Published: 2024-05-20 18:28:11

###**Table of Content**
1. Introduction
   - What is Ansible
   - How Ansible Works
2. Demo
   - Solution
   - Inventory
   - Connecting to Slave Nodes
   - Playbook 
   - Playbook Breakdown
     - Updating Packages
     - Installing Apache
     - Starting Apache and Opening Port 80 in the CentOS System
   - Running the Playbook
3. Conclusion

##**Introduction**

You got that lucky break and bagged an internship in cloud engineering. One of your duties is managing over 10 servers on the cloud, updating and upgrading the packages, and installing new applications on new servers.

You'll be wondering how to SSH into all the servers and run all those commands one after the other. That sounds like a lot. However, a way to automate that and make your life easy is by using Ansible.

**What is Ansible?**
Ansible is an open-source IT automation engine that automates provisioning, configuration management, application deployment, orchestration, and many other IT processes. It is free to use, and the project benefits from the experience and intelligence of its thousands of contributors.

So imagine you have many machines for different purposes and tasks and you have to manage all of them, Ansible is a super robot that helps you connect to those machines and you tell Ansible what you want it to do in all of those machines and it does it for you.

**How Ansible works**

Ansible needs a master node or controller that will be used to connect to all other nodes, machines, or servers to manage them. 
It uses an Inventory file that stores the IP of all the machines it should connect to and perform tasks on.
It also needs a playbook which is a YAML file that tells ansible what it wants it to do and the tasks to perform on all other nodes or machines it is connected to.

##**Demo**

To explain how Ansible works better, You will use a demonstration. In this demo, you are going to provision 3 Linux servers. Two will be Ubuntu and one will be CentOS. The Master node (controller) will be any of the Ubuntu servers and the slave node will be One Ubuntu server and the CentOS server. 
You are going to run a playbook that installs Apache on the slave nodes. In the CentOS server, you will start the Apache and enable port 80 in the firewall.

**Solution**

First, you have to provision 3 virtual machines using Vagrant. An alternative is your AWS EC2.

In the master node, first, install ansible on your master node with the command:
`sudo apt install ansible`

Then create a directory with any name of your choice. This directory will house all the files and playbooks related to our Ansible project. 

![making directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9x5jt5ixel9z3bwxentc.PNG)

**- Inventory**

Now you have to create your Inventory file. An Inventory in Ansible contains the IP address or domain name of all the servers or slave nodes we want to manage with Ansible.
It can also contain some other data like groupings, group variables, and host variables (which we will address later for better understanding). The inventory file should be inside the directory we created.
To create an inventory file, run the command:
`sudo vim inventory`
The picture below is the inventory file with the IP addresses of both our slave nodes. 

![Inventory file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q1gnopxfgc8lddvwrf7m.PNG)

Remember that an inventory file can contain data like groupings? That means that we can group our slave nodes into whatever groups we want to put them in. The groups can be web servers, databases, or by their Linux distribution. 
The picture below shows how to group the IP addresses of our slave nodes.

![Inventory groupings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zc36bcgy4atflcjan3tk.PNG)

**- Connecting to Slave nodes**

For Ansible to be able to run tasks on your slave nodes with the playbook, it needs some form of connection to those slave nodes in your inventory. 
The way to get that connection is to generate an SSH key on your master node or controller, copy the public keys, and paste them into the authorized keys of your slave nodes. 
The command to generate the SSH key is:
`ssh-keygen -t ed25519 -C "default"`
The "ssh-keygen" is the command line utility for generating SSH keys, the "-t ed25519" specifies the type of key to create which is ed25519, the "-C "default" add a comment 'default' to key for information purposes. 
The picture below is the result after creating the ssh key.

![Ansible default key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fb3z05e423fvznffkto2.PNG)
Do the same thing for the second one but this time the comment will be ansible and you will change the path to where the keys will to /home/vagrant/.ssh/ansible.pub
This is how your ansible key creation will look

![ssh key creation for ansible](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nyjd46uzp7bngyrtym01.PNG)
For this article, please don't create a password when you create the SSH keys. To get the public key, navigate into /home/vagrant/.ssh/ the copy the keys of ansible.pub and id_ed25519.pub. Then paste it into the authorized key file in your slave nodes separately.
The default key is for you to be able to connect or SSH into your slave nodes from the controller and the Ansible key is the one Ansible will use to connect to the slave nodes.
To be sure your master node can connect to your slave nodes in your inventory, make sure your slave nodes are up and running then run the command in the directory that has your inventory:
`ansible all --key-file ~/.ssh/ansible -i inventory -m ping`
"ansible" is the command line tool. You are calling ansible.
"all" is specifying that the command should run against all the IP addresses or hosts in the inventory file.
"--key-file ~/.ssh/ansible" is telling ansible the particular SSH key to use for authentication. In the case the key located in (~/.ssh/ansible)
"-i inventory" is you specifying the inventory file that contains all the IP addresses of your slave nodes
"-m ping" specifies the ansible module to use. In this case 'ping' which just testing to check if the IPs in the Inventory are reachable. 

![Ansible ping result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eugp5rczbcczfe4bgdf0.PNG)

**- Playbook**

A playbook in Ansible is like a blueprint of tasks you want to automate. These tasks are executed with little manual effort across all the hosts in your inventory. The playbook tells Ansible what to do and which device to do it in. 
One or more tasks are combined to make a play and these tasks can be mapped to a specific host, a group of hosts, or all the hosts. A playbook can also include one or more plays. The tasks/plays are executed the way they are written.
Tasks are executed by Ansible modules. These modules contain some data that determines when and where a task is executed and the user that executes the tasks.

Now to open a playbook file, still inside the directory you created that your inventory is in, run the command:
`sudo vim playbook.yml` 

The playbook for our demo is the code block below. 

```
---
- hosts: all
  become: true
  pre_tasks:
    - name: install updates (centOS)
      tags: always
      yum:
        update_only: yes
        update_cache: yes
      when: ansible_os_family == 'RedHat'

    - name: install updates (Ubuntu)
      tags: always
      apt:
        upgrade: dist
        update_cache: yes
      when: ansible_os_family == 'Ubuntu'

#Installing apache for ubuntu and centos
- hosts: all
  become: true
  tasks:
    - name: install apache for ubuntu server
      tags: apache,apache2,ubuntu
      apt:
        name: apache2
        state: latest
      when: ansible_distribution == "Ubuntu"

    - name: install Apache for CentOS server
      tags: apache,centos,httpd
      yum:
        name: httpd
        state: latest
      when: ansible_distribution == "CentOS"

    #Start and enable apache for CentOS
    - name: start httpd (CentOS)
      tags: apache,centos,httpd
      service:
        name: httpd
        state: started
        enabled: yes
      when: ansible_distribution == "CentOS"

    #Open port 80/tcp for CentOS
    - name: Open port 80/tcp in firewalld
      firewalld:
        service: http
        permanent: yes
        state: enabled
      when: ansible_distribution == "CentOS
      notify: Reload firewalld

    #Reload Firewall for CentOS
  handlers:
    - name: Reload firewalld
      service:
        name: firewalld
        state: reloaded
      when: ansible_distribution == "CentOS"
```
N.B You should note that when writing your ansible playbook, your indentation is very important. You miss a single line of indentation in a task or a play and that particular task or play won't run. Sometimes the entire playbook might not work.

**- Component Breakdown**

- Updating Package

```
---
- hosts: all
  become: true
  pre_tasks:
    - name: install updates (centOS)
      tags: always
      yum:
        update_only: yes
        update_cache: yes
      when: ansible_os_family == 'RedHat'

    - name: install updates (Ubuntu)
      tags: always
      apt:
        upgrade: dist
        update_cache: yes
      when: ansible_os_family == 'Ubuntu'
```
- `---` represents the beginning of a YAML file
- `- hosts: all` specifies that the playbook should run on all hosts. You can also pick any specific host group.
`become: true` This ensures that all tasks are executed with sudo privileges. Although it is optional. Your plays can run without it.
- `pre_tasks:` You can also use "tasks" here however, using pre_tasks means the tasks here are executed before any other tasks. 
The first part of the task here updates the packages for the CentOS servers and the second task does the same for the Ubuntu server
- `name` This is the description of the tasks
- `tags` when running an ansible playbook, you can specify a tag you want the playbook to run on, setting this tag to always makes sure these tasks run regardless of the tags specified when running the playbook. It is also optional.
- `yum` and `apt` These are modules used to manage packages in the RedHat and Debian-based systems.
   - `update_only` This ensures only updates are installed
   - `update_cache` This refreshes the package database cache 
   -`upgrade` Specifies the type of upgrade to perform. 'dist' in this case performs a full distribution upgrade.
- `when` This is a conditional statement in Ansible that states a task should run on Ubuntu or RedHat systems. 
So the above Play is you going into that system to run `sudo apt update` or `sudo yum upgrade` as it is best practice to update your packages before any installation but this time you are doing it remotely with Ansible!

- Installing Apache

```
 #Installing apache for ubuntu and centos
- hosts: all
  become: true
  tasks:
    - name: install apache for ubuntu server
      tags: apache,apache2,ubuntu
      apt:
        name: apache2
        state: latest
      when: ansible_distribution == "Ubuntu"

    - name: install apache for CentOS server
      tags: apache,centos,httpd
      yum:
        name: httpd
        state: latest
      when: ansible_distribution == "CentOS"
```
The code block above is the part of the playbook that installs Apache for both Ubuntu and CentOS systems. 
Now if you had used 'tasks' instead of 'pre_tasks' you would not have to repeat the `host`, `become`, and `tasks` parts. It will all be under the first one. 
- `name` describes the name of the task
- `tags` keywords to use in running a specific task by adding `--tags "apache,ubuntu,httpd,apache2"` to the ansible command
- `apt` and `yum` These are modules used to manage Ubuntu and RedHat-based systems
   - `name` This is the name of the package to install. "apache2" 
     for Ubuntu and "httpd" for centOS
   - `state` This ensures the package installed is up to date by 
     setting it to "latest"
- `when` This is a conditional statement that makes sure the tasks run on Ubuntu or CentOS systems. 
This play is you going into the slave nodes to run the command `sudo apt install apache2` or `sudo yum install httpd`

- Starting Apache and Opening Port 80 in the CentOS System

```
#Start and enable apache for CentOS
    - name: start httpd (CentOS)
      tags: apache,centos,httpd
      service:
        name: httpd
        state: started
        enabled: yes
      when: ansible_distribution == "CentOS"

    #Open port 80/tcp for CentOS
    - name: Open port 80/tcp in firewalld
      firewalld:
        service: http
        permanent: yes
        state: enabled
      when: ansible_distribution == "CentOS
      notify: Reload firewalld

    #Reload Firewall for CentOS
  handlers:
    - name: Reload firewalld
      service:
        name: firewalld
        state: reloaded
      when: ansible_distribution == "CentOS"
```
These tasks configure Apache or httpd as it is called for the CentOS system.
Here, you are using a few different modules and their arguments. The new modules used are the `service` module which manages the state of a service and `firewalld` module which manages firewall settings.

- The first task using the service module, starts the service (httpd) and enables it.

- The second task using the firewalld module, enables the http service (which corresponds to port 80), makes the change permanent, and enables it.
It uses notify to alert the handler which is used in the third task that a change has been made. The handler will only run if the change was made.
 
- The third task introduces a new concept called "handlers". It is used to perform actions when notified of a task. It is mostly used to perform actions like restarting services or reloading configurations only when necessary. 
In this case, from the arguments of the service module, it is used to reload the firewalld service.
You can also find out more about the ansible module with the ansible [documentation](https://docs.ansible.com/ansible/2.9/modules/list_of_all_modules.html)

**- Running the Playbook**

Now that you understand what the entire playbook does, it's time to check if it works. The command to run an ansible playbook is:
`ansible-playbook -i inventory playbook.yml`
The "inventory" should be the name of your inventory file and the playbook.yml should be the name of your playbook file.
This command should be run inside the directory that has your playbook and inventory file
If your playbook runs successfully you should get this output

![Playbook output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4p7q284mh8apswpo8pct.PNG)
If you check the play recap at the bottom, you will see that no task failed for each of the slave nodes.

Now if you check the IP address of your slave nodes on your browser to be sure Apache was installed, you should get the Apache default page as shown in the pictures below

Apache default page for Ubuntu
![Apache page for Ubuntu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hygn5z5y04kc56h9uube.PNG)

Apache default page for CentOS
![Apache page for centos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ueutpfhined4on019j94.PNG)

##**- Conclusion**

In this article you have learned what Ansible is, how it works and a few concepts used like the playbook and inventory using a demo. You should also remember that indentations are important when writing your playbooks.

The playbook written in this article is not the best way to write a playbook as there are a few repetitions. We can consolidate it by using variables and Roles. Those will be discussed in the second part of this article!