Book Image

Ansible Playbook Essentials

By : Gourav Shah, GOURAV JAWAHAR SHAH
Book Image

Ansible Playbook Essentials

By: Gourav Shah, GOURAV JAWAHAR SHAH

Overview of this book

Ansible Playbook Essentials will show you how to write a blueprint of your infrastructure, encompassing multitier applications using Ansible's playbooks. Beginning with basic concepts such as plays, tasks, handlers, inventory, and YAML Ain't Markup Language (YAML) syntax that Ansible uses, you'll understand how to organize your code into a modular structure. Building on this, you will study techniques to create data-driven playbooks with variables, templates, logical constructs, and encrypted data, which will further strengthen your application skills in Ansible. Adding to this, the book will also take you through advanced clustering concepts, such as discovering topology information about other nodes in the cluster and managing multiple environments with isolated configurations. As you approach the concluding chapters, you can expect to learn about orchestrating infrastructure and deploying applications in a coordinated manner. By the end of this book, you will be able to design solutions to your automation and orchestration problems using playbooks quickly and efficiently.
Table of Contents (20 chapters)
Ansible Playbook Essentials
Credits
About the Author
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Setting Up the Learning Environment
References
Index

Our first playbook


Equipped with the basic rules explained previously and assuming readers have done a quick dive into YAML fundamentals, we will now begin writing our first playbook. Our problem statement includes the following:

  1. Create a devops user on all hosts. This user should be part of the devops group.

  2. Install the "htop" utility. Htop is an improved version of top—an interactive system process monitor.

  3. Add the Nginx repository to the web servers and start it as a service.

Now, we will create our first playbook and save it as simple_playbook.yml containing the following code:

---
- hosts: all
  remote_user: vagrant
  sudo: yes
  tasks:
  
  - group:
      name: devops
      state: present
  - name: create devops user with admin privileges
    
    user:
      name: devops
      comment: "Devops User"
      uid: 2001
      group: devops
  - name: install htop package
    action: apt name=htop state=present update_cache=yes

- hosts: www
  user: vagrant
  sudo: yes
  tasks:
  - name: add official nginx repository
    apt_repository:
      repo: 'deb http://nginx.org/packages/ubuntu/ lucid nginx'
  - name: install nginx web server and ensure its at the latest version
    apt:
      name: nginx
      state: latest
  - name: start nginx service
    service:
      name: nginx
      state: started

Our playbook contains two plays. Each play consists of the following two important parts:

  • What to configure: We need to configure a host or group of hosts to run the play against. Also, we need to include useful connection information, such as which user to connect as, whether to use sudo command, and so on.

  • What to run: This includes the specification of tasks to be run, including which system components to modify and which state they should be in, for example, installed, started, or latest. This could be represented with tasks and later on, by roles.

Let's now look at each of these briefly.

Creating a host inventory

Before we even start writing our playbook with Ansible, we need to define an inventory of all hosts that need to be configured, and make it available for Ansible to use. Later, we will start running plays against a selection of hosts from this inventory. If you have an existing inventory, such as cobbler, LDAP, a CMDB software, or wish to pull it from a cloud provider, such as ec2, it can be pulled from Ansible using the concept of a dynamic inventory.

For text-based local inventory, the default location is /etc/ansible/hosts. For our learning environment, however, we will create a custom inventory file customhosts in our working directory, the contents of which are shown as follows. You are free to create your own inventory file:

#customhosts
#inventory configs for my cluster
[db]
192.168.61.11  ansible_ssh_user=vagrant

[www]
www-01.example.com ansible_ssh_user=ubuntu
www-02 ansible_ssh_user=ubuntu

[lb]
lb0.example.com

Now, when our playbook maps a play to the group, the www (hosts: www), hosts in that group will be configured. The all keywords will match to all hosts from the inventory.

The following are the guidelines to for creating inventory files:

  • Inventory files follow INI style configurations, which essentially include configuration blocks that start with host group/class names included in "[ ]". This allows the selective execution on classes of systems, for example, [namenodes].

  • A single host can be part of multiple groups. In such cases, host variables from both the groups will get merged, and the precedence rules apply. We will discuss variables and precedence in detail later.

  • Each group contains a list of hosts and connection details, such as the SSH user to connect as, the SSH port number if non-default, SSH credentials/keys, sudo credentials, and so on. Hostnames can also contain globs, ranges, and more, to make it easy to include multiple hosts of the same type, which follow some naming patterns.

Tip

After creating an inventory of the hosts, it's a good idea to validate connectivity using Ansible's ping module (for example, ansible -m ping all).

Patterns

In the preceding playbook, the following lines decide which hosts to select to run a specific play:

- hosts: all
- hosts: www

The first code will match all hosts, and the second code will match hosts which are part of the www group.

Patterns can be any of the following or their combinations:

Pattern Types

Examples

Group name

namenodes

Match all

all or *

Range

namenode[0:100]

Hostnames/hostname globs

*.example.com, host01.example.com

Exclusions

namenodes:!secondaynamenodes

Intersection

namenodes:&zookeeper

Regular expressions

~(nn|zk).*\.example\.org

Tasks

Plays map hosts to tasks. Tasks are a sequence of actions performed against a group of hosts that match the pattern specified in a play. Each play typically contains multiple tasks that are run serially on each machine that matches the pattern. For example, take a look at the following code snippet:

- group:
    name:devops
    state: present
- name: create devops user with admin privileges
  user:
    name: devops
    comment: "Devops User"
    uid: 2001
    group: devops

In the preceding example, we have two tasks. The first one is to create a group, and second is to create a user and add it to the group created earlier. If you notice, there is an additional line in the second task, which starts with name:. While writing tasks, it's good to provide a name with a human-readable description of what this task is going to achieve. If not, the action string will be printed instead.

Each action in a task list can be declared by specifying the following:

  • The name of the module

  • Optionally, the state of the system component being managed

  • The optional parameters

Tip

With newer versions of Ansible (0.8 onwards), writing an action keyword is now optional. We can directly provide the name of the module instead. So, both of these lines will have a similar action, that is,. installing a package with the apt module:

action: apt name=htop state=present update_cache=yes
apt: name=nginx state=latest

Ansible stands out from other configuration management tools, with its batteries-included included approach. These batteries are "modules." It's important to understand what modules are before we proceed.

Modules

Modules are the encapsulated procedures that are responsible for managing specific system components on specific platforms.

Consider the following example:

  • The apt module for Debian and the yum module for RedHat helps manage system packages

  • The user module is responsible for adding, removing, or modifying users on the system

  • The service module will start/stop system services

Modules abstract the actual implementation from users. They expose a declarative syntax that accepts a list of the parameters and states of the system components being managed. All this can be declared using the human-readable YAML syntax, using key-value pairs.

In terms of functionality, modules resemble providers for those of you who are familiar with Chef/Puppet software. Instead of writing procedures to create a user, with Ansible we declare which state our component should be in, that is, which user to create, its state, and its characteristics, such as UID, group, shell, and so on. The actual procedures are inherently known to Ansible via modules, and are executed in the background.

Tip

The Command and Shell modules are special ones. They neither take key-value pairs as parameters, nor are idempotent.

Ansible comes preinstalled with a library of modules, which ranges from the ones which manage basic system resources to more sophisticated ones that send notifications, perform cloud integrations, and so on. If you want to provision an ec2 instance, create a database on the remote PostgreSQL server, and get notifications on IRC, then Ansible has a module for it. Isn't this amazing?

No need to worry about finding an external plugin, or struggle to integrate with cloud providers, and so on. To find a list of modules available, you can refer to the Ansible documentation at http://docs.ansible.com/list_of_all_modules.html.

Ansible is extendible too. If you do not find a module that does the job for you, it's easy to write one, and it doesn't have to be in Python. A module can be written for Ansible in the language of your choice. This is discussed in detail at http://docs.ansible.com/developing_modules.html.

The modules and idempotence

Idempotence is an important characteristic of a module. It is something which can be applied on your system multiple times, and will return deterministic results. It has built-in intelligence. For instance, we have a task that uses the apt module to install Nginx and ensure that it's up to date. Here is what happens if you run it multiple times:

  • Every time idempotance is run multiple times, the apt module will compare what has been declared in the playbook versus the current state of that package on the system. The first time it runs, Ansible will determine that Nginx is not installed, and will go ahead with the installation.

  • For every consequent run, it will skip the installation part, unless there is a new version of the package available in the upstream repositories.

This allows executing the same task multiple times without resulting in the error state. Most of the Ansible modules are idempotent, except for the command and shell modules. Users will have to make these modules idempotent.

Running the playbook

Ansible comes with the ansible-playbook command to launch a playbook with. Let's now run the plays we created:

$ ansible-playbook simple_playbook.yml -i customhosts

Here is what happens when you run the preceding command:

  • The ansible-playbook parameter is the command that takes the playbook as an argument (simple_playbook.yml) and runs the plays against the hosts

  • The simple_playbook parameter contains the two plays that we created: one for common tasks, and the other for installing Nginx

  • The customhosts parameter is our host's inventory, which lets Ansible know which hosts, or groups of hosts, to call plays against

Launching the preceding command will start calling plays, orchestrating in the sequence that we described in the playbook. Here is the output of the preceding command:

Let's now analyze what happened:

  • Ansible reads the playbooks specified as an argument to the ansible-playbook command and starts executing plays in the serial order.

  • The first play that we declared, runs against the "all" hosts. The all keyword is a special pattern that will match all hosts (similar to *). So, the tasks in the first play will be executed on all hosts in the inventory we passed as an argument.

  • Before running any of the tasks, Ansible will gather information about the systems that it is going to configure. This information is collected in the form of facts.

  • The first play includes the creation of the devops group and user, and installation of the htop package. Since we have three hosts in our inventory, we see one line per host being printed, which indicates whether there was a change in the state of the entity being managed. If the state was not changed, "ok" will be printed.

  • Ansible then moves to the next play. This is executed only on one host, as we have specifed "hosts:www" in our play, and our inventory contains a single host in the group "www".

  • During the second play, the Nginx repository is added, the package is installed, and the service is started.

  • Finally, Ansible prints the summary of the playbook run in the "PLAY RECAP" section. It indicates how many modifications were made, if any of the hosts were unreachable, or execution failed on any of the systems.

Tip

What if a host is unresponsive, or fails to run tasks? Ansible has built-in intelligence, which will identify such issues and take the failed host out of rotation. It will not affect the execution on other hosts.