Wednesday, 18 February 2015

Automating IT infrastructure with Ansible- PART 3

Automating IT infrastructure with Ansible- PART 3 
- Working with conditions

In Part 1 and Part 2 of this tutorial series we saw how to install and configure Ansible in a few easy steps along with how to get started with writing simple playbooks. 

This tutorial will look into some of Ansible's conditional statements and also provide you with a quick insight on how to develop playbooks that support multiple operating systems. 

To get started, let us first understand the scenario. I have 2 remote Hosts in my environment: Host 1 (CentOS :: 192.168.0.15) and Host 3 (Ubuntu :: 192.168.0.25). Both these systems are out of sync and I want to sync them up to a NTP server. The solution is simple... write a playbook that will:
    1) Set the correct timezone (based on what the end users specify
    2) Check whether the NTP packages are installed on both the systems or not. If not then it will install the NTP packages based on that specific OS type.
    3) Add the NTP servers to the /etc/ntp.conf file based on the specific OS type
    4) Start the NTP service
    5) Sync the NTP date and time to a common NTP server  

So without further adieu, let's get started!! First up, create a folder for hosting our playbooks. Here, I'm using the following folder structure for this tutorial:

/etc/ansible/playbooks/bootstrap/ntp  


NOTE: Here, bootstrap is my master playbook, and the ntp acts as a Role. You can alternatively create your NTP directory as a separate playbook as well.

Create the Playbook
The folder structure of this playbook looks something like this:

bootstrap/
   |___ hosts                ~~> Contains the inventory info
   |___ main.yml          ~~> master main.yml file
   |___ ntp/                  ~~> ntp role
             |__ defaults/
             |__ handlers/
             |__ tasks/
             |__ templates/
             |__ vars/



First off, lets create some OS specific variables for our play. In this case, Im going to create two files, namely RedHat.yml and Debian.yml and populate them with the NTP related info as shown. But why name these files RedHat and Debian?? 

Ansible provides and exploits some really cool variables/ facts such as :

{{ ansible_distribution }}-{{ ansible_distribution_version }}
{{ ansible_distribution }}
{{ ansible_os_family }}


In non-ansible terms, these three actually mean:

CentOS-6.5
CentOS
RedHat

[or]

Ubuntu-12.04
Ubuntu
Debian 
We will be using {{ ansible_os_family }} to call/ import the different variable files in our main.yml. But first, create and assign the variables as shown below:

# For RedHat:
ntp_package:
      name: ntp
      state: installed
ntp_service: ntpd
ntp_config: /etc/ntp.conf
ntp_servers:
   - 0.centos.pool.ntp.org iburst
   - 1.centos.pool.ntp.org iburst
   - 2.centos.pool.ntp.org iburst
   - 3.centos.pool.ntp.org iburst



# For Debian:
ntp_package:
      name: ntp
      state: installed
ntp_service: ntp
ntp_config: /etc/ntp.conf
ntp_servers:
   - 0.ubuntu.pool.ntp.org
   - 1.ubuntu.pool.ntp.org
   - 2.ubuntu.pool.ntp.org
   - 3.ubuntu.pool.ntp.org

Save and close the files when done.



Next, we move on to the main tasks file for our playbook. Create the ntp/tasks/main.yml file as shown:

---
# tasks file for ntp

- name: Include OS-specific variables
  include_vars: "{{ ansible_os_family }}.yml"

- name: Set the correct timezone
  file: src=/usr/share/zoneinfo/{{ ntp_timezone }}  dest=/etc/localtime state=link force=yes

- name: Install NTP (RedHat)
  yum: name={{ ntp_package.name }} state={{ ntp_package.state }}
  when: ansible_os_family == 'RedHat'

notify:

     - Sync NTP

- name: Install NTP (Debian)
  apt: name={{ ntp_package.name }} state={{ ntp_package.state }}
  when: ansible_os_family == 'Debian'

notify:

     - Sync NTP

- name: Configure NTP
  template: src={{ item }} dest={{ ntp_config }}
  with_first_found:
   - "../templates/{{ ansible_distribution }}-{{ ansible_distribution_version }}.ntp.conf.j2"
   - "../templates/{{ ansible_distribution }}.ntp.conf.j2"
   - "../templates/{{ ansible_os_family }}.ntp.conf.j2"
   - "../templates/ntp.conf.j2"

  notify:

     - Start NTP Service


Lets have a close look at what we have configured:
1) include_vars: "{{ ansible_os_family }}.yml"
The include_vars will search for a .yml file in the vars directory. In this case, the include_vars will call two files: RedHat.yml and Debian.yml using the {{ ansible_os_family }} variable/ fact.

2) {{ ntp_timezone }} 
Here, we are using the {{ ntp_timezone }} variable as a "role default variable". These variables are created in the /defaults/main.yml file and can be easily overridden by a variable declared in the /vars directory.

3) {{ ntp_package.name }} 
We earlier declared a variable called {{ ntp_package }} in our two variable files. This is just a easier way of calling the sub-attributes of the same variable.

4) when: ansible_os_family == 'RedHat'
"when" statement is a condition control statement in Ansible and is used to execute a particular task only when a particular condition is met. If the condition is not met, this task is simply avoided.

5) {{ item }}.... with_first_found
{{ item }} statements can be used as loops to iterate through various items in a list. In this case, we are iterating through a files list and using with_first_found parameter. This parameter is useful when you want to iterate through a list of files and use the first available file from the list.



Moving on, create the handlers as well in the /handlers/main.yml file. Make sure the names of both the handlers match with the [notify] attribute in the /tasks/main.yml file.



Create a j2 template file that will populate the NTP servers list based on the different OS used.

In this template file we have made use of the [for] loop. 

{% for ntp_server in ntp_servers %}
server {{ ntp_server }}

{% endfor %}

Each [for] loop ends with a [endfor] statement. In this case, the particular [for] statement will loop and print the NTP servers list from the two variable files that we created in the earlier steps



Dont forget to add the custom variables in the defaults/main.yml file. 



Edit the bootstrap main.yml (master yml file) and make sure the ntp role is added to it as shown




Run the playbook
Once the playbook is created, we can now run it against all the hosts in our inventory. First off, let's check the actual date and time on our remote hosts using Ansible itself.

# ansible all -m shell -a "date"

As you can see, both the servers are out of sync, in both time, date and timezone.



Run the playbook.

# ansible-playbook -i hosts main.yml



Run the date command once the playbook completes its execution. The servers are now synced to a particular NTP server and are showing the correct time, date and timezone details as required.



Here's the directory structure of the final playbook.



So, let's quickly recap what all we got to learn from this tutorial:
-- We got to see how to create playbooks that have multi-OS support
-- We learnt about using ansible facts and variables in the form of {{ ansible_os_family }}
-- We looked at the [when] statement and its uses
-- We saw two loop statements as well in the form of {{ item }} and {{% for %}}

Still more to come on Ansible so stay tuned for further posts!! 

Cheers!



No comments :

Post a Comment