Ansible Playbook

Playbooks, comparing to Ansible ad hoc commands, have different approach to configuration management. The real power of configuration management and multiple multi-machine deployment lies in Ansible playbooks. Playbook usually consists of individual plays, which in other hand have one or more tasks. Tasks are Python modules that do modifications on managed nodes.

Tasks and plays are executed in the order written in the playbook. For each play, you can define user(s) and managed nodes on which tasks and plays will execute. The modules that task invokes are idempotent, which means that no matter how many times they are executed, change will be made only once. Each time the playbook runs the configuration remains the same, no changes are made to the system. The exceptions are shell and command modules which make changes every time started.

Playbooks are written in YAML format using yml extension and have two rules only:

  • Elements which belong to the same hierarchical level must have  the same indentation
  • Elements that are children of the other element must be indented more than the parent (two spaces)

Example:

– name: Configure important user consistently 
  hosts: testlab1.test.com
  tasks:  
    – name: Create user test    
      user:       
         name: test     
         state: present

In this example, we created new test user on managed node -testlab1.test.com. Every playbook starts with —. It is good practice to start with the name of the first play, which is also the only play in this example. In hosts section, we defined the name of the managed nodes that were previously defined in the inventory file:

testlab1.test.com 
testlab2.test.com 

The inventory file contains two managed nodes, but for our needs, we need only one. We can include second node in same playbook for another play, it totally up to us. In other words, we don’t need to include all hosts in inventory file. There is only one task included which creates new user test on managed node.  We used module user with the name and state parameters. Finally we can see that indented rules are in place. The name of the playbook is playbook.xml.

Running playbooks

We run playbook with following command:

#ansible-playbook playbook.yml

[testuser@ansiblewks playbook]$ ansible-playbook playbook.yml –become

OUTPUT:

PLAY [Configure important user consistently]

**********************************************************************************

TASK [Gathering Facts] **********************************************************************************

ok: [testlab1.test.com]

TASK [Create user test] **********************************************************************************

changed: [testlab1.test.com]

PLAY RECAP **********************************************************************************

testlab1.test.com          : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

We can see several items in the output of the Ansible playbook. Gathering facts task runs by default. This task collects various information about managed nodes such as hostname, IP address, memory and many more. Collected data can be very useful when running tasks based on various conditions of the hosts. For example, if host does not meet minimum memory requirements, play will not start. Task Create user test runs next which is task defined in playbook.

We see that the status of the playbook has changed which means that a change on the managed node has been made. Finally, the status of the completed tasks is wrapped up (2 tasks completed, 1 changed). There are 2 completed tasks which include default gathering task and task defined in playbook. The only task defined in playbook has status changed.

Let’s check the idempotent feature in action. The idempotent feature ensures that the playbook can be executed two or more times without affecting the environment. If we run the playbook a second time we see that the change has not been made:

[testuser@ansiblewks playbook]$ ansible-playbook playbook.yml -become

PLAY [Configure important user consistently] **********************************************************************************

TASK [Gathering Facts] **********************************************************************************

ok: [testlab1.test.com]

TASK [Create user test] **********************************************************************************

ok: [testlab1.test.com]

PLAY RECAP **********************************************************************************

testlab1.test.com          : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Finally, we see that the status changed is 0 which means if a change has already been made, changes will not be performed for a second time.

* There is an exception for shell and command modules that make changes each time because these modules are executed directly through the shell bypassing python modules.

Let’s take a look at one more play:


– name: Install and configure web service
  hosts: testlab1.test.com 
  tasks:  
    – name: Web server installation     
      yum:
        name: httpd     
        state: latest

    – name: Web server service enable
      service:
      name: httpd
         enabled: true
         state: started

  – name: Configure web service through firewall
    firewalld:
    service: httpd
    permanent: true
    state: enabled
    immediate: yes

In this playbook, we have installed and started http service on managed hosts defined in the inventory file. At the end we allowed the http service through the firewall.

Pulling playbooks from external source(GitLab)

If you want to have a central place for your Ansible playbooks and avoid manage node, you can use Git repository.

Multiple plays

Ansible Playbooks can contain more than one play. This is very useful for large and complex environments where we want to run different tasks for different hosts. Writing multiple plays is very easy. The only thing to pay attention is the same level of indentation:

Example:

–# this is a simple playbook with two plays
–  name: play1  
   hosts: testlab1.test.com  
   tasks:
       – name: this is task   
         yum:      
            name: chronyd      
            status: latest

 –  name: play2  
    hosts: testlab2.test.com  
    tasks:
       – name: this is task    
          yum:      
          name: ntpd    
          status: latest

First play installs chrony service on testlab1.test.com and second play ntpd service on testlab2.test.com.

Verbosity

Running playbooks by default does not include a detailed printout of all the steps that are performed. If we want to monitor the execution of playbooks in more detail, we use switch -v. There are four categories that differ in the level of detail:

-v (Task results and Ansible configuration file path)

-vv (Task configuration)

-vvv (Managed hosts(s) connection info)

-vvvv (plugins used, users connection, which scripts are executed)

Example:

[testuser@ansiblewks playbook]$ ansible-playbook playbook.yml -v

Using /home/testuser/playbook/ansible.cfg as config file

PLAY [Configure important user con sistently] **********************************************************************************

TASK [Gathering Facts] **********************************************************************************

ok: [testlab1.test.com]

TASK [Create user test] **********************************************************************************

ok: [testlab1.test.com] => {“append”: false, “changed”: false, “comment”: “”, “group”: 2025, “home”: “/home/test”, “move_home”: false, “name”: “test”, “shell”: “/bin/bash”, “state”: “present”, “uid”: 2025}

PLAY RECAP **********************************************************************************testlab1.test.com          : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

 Using the one -v switch, we can see additional information – path for the Ansible configuration file and the details of the tasks performed.

Syntax verification

It is good practice to check the playbook syntax before launching Ansible playbook. The switch used for this operation is –syntax-check.

Example:

[testuser@ansiblewks playbook]$ ansible-playbook –syntax-check playbook.yml

playbook: playbook.yml

Only the name of the playbook is shown in the output, which means that the syntax is OK. When we make a deliberate mistake, the print looks like this:

[testuser@ansiblewks playbook]$ ansible-playbook –syntax-check playbook.yml

ERROR! Syntax Error while loading YAML.

  did not find expected ‘-‘ indicator

The error appears to be in ‘/home/testuser/playbook/playbook.yml’: line 4, column 2, but may

be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

  hosts: testlab1.test.com

 tasks:

 ^ here

Output shows that the identity error was detected:

[testuser@ansiblewks playbook]$ cat playbook.yml

– name: Configure important user consistently
  hosts: testlab1.test.com

tasks:
    – name: Create user test
      user:
      name: test
      state: present

Dry run

Another interesting switch is –C which defines the so-called dry run, execution of a playbook in draft mode. Draft mode shows the changes that would be made if the playbook was actually executed.

In other words, dry run doesn’t really run the playbook just simulates the execution. It would be handy if we want to see the playbook output.

Example:

[testuser@ansiblewks playbook]$ ansible-playbook -C playbook.yml

PLAY [Configure important user consistently] **********************************************************************************

TASK [Gathering Facts] **********************************************************************************

ok: [testlab1.test.com]

TASK [Create user test] **********************************************************************************

ok: [testlab1.test.com]

PLAY RECAP **********************************************************************************

testlab1.test.com          : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0