Inventory

The default inventory file for Ansible is /etc/ansible/hosts. Another inventory file can be specified using the -i <path> option on the command line. Multiple inventory file can be used by multiple -i option, in a command line.
A basic inventory contains nodes and group of nodes. The headings in brackets are group names, which are used in classifying systems and deciding what systems you are controlling at what times and for what purpose. The following is an example of a basic INI-like inventory file:
mail.example.com
[webservers]
foo.example.com
bar.example.com
[dbservers]
one.example.com
two.example.com
three.example.com

 

And the following is the YAML version of the same inventory file:
all:
  hosts:
    mail.example.com:
  children:
    webservers:
      hosts:
        foo.example.com:
        bar.example.com:
    dbservers:
      hosts:
        one.example.com:
        two.example.com:
        three.example.com:

 

Variables such as Ports, IP Addresses, Users, Connection types, etc. can be added to inventory and therefore passed to ansible playbook:
[atlanta]
host1
host2
[atlanta:vars]
ntp_server=ntp.atlanta.example.com
proxy=proxy.atlanta.example.com
[southeast:children]
atlanta
raleigh
[southeast:vars]
some_server=foo.southeast.example.com
halon_system_timeout=30
self_destruct_countdown=60
escape_pods=2
[leafs]
leaf01 ansible_host=10.16.10.11 ansible_network_os=vyos ansible_user=my_vyos_user
leaf02 ansible_host=10.16.10.12 ansible_network_os=vyos ansible_user=my_vyos_user
[network:children]
leafs
[servers]
server01 ansible_host=10.16.10.15 ansible_user=my_server_user
server02 ansible_host=10.16.10.16 ansible_user=my_server_user
[datacenter:children]
leafs
servers

 

Full inventory documentation can be found at Ansible official site:  https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html

 

Playbook

An Ansible playbook is an organized unit of scripts that defines work for a server configuration managed by the automation tool Ansible.  The playbook is the core component of any Ansible configuration. An Ansible playbook contains one or multiple plays, each of which define the work to be done for a configuration on a managed server. Ansible plays are written in YAML
Playbooks are Ansible’s configuration, deployment, and orchestration language. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process. Playbooks can declare configurations, but they can also orchestrate steps of any manual ordered process, even as different steps must bounce back and forth between sets of machines in particular orders. They can launch tasks synchronously or asynchronously.
By composing a playbook of multiple ‘plays’, it is possible to orchestrate multi-machine deployments, running certain steps on all machines in the webservers group, then certain steps on the database server group, then more commands back on the webservers group, etc.
“plays” are more or less a sports analogy. You can have quite a lot of plays that affect your systems to do different things. It’s not as if you were just defining one particular state or model, and you can run different plays at different times.
Here is a sample of a simple playbook:
---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum:
      name: httpd
      state: latest
  - name: write the apache config file
    template:
      src: /srv/httpd.j2
      dest: /etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service:
      name: httpd
      state: started
  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted
There are various sections in a playbook:
hosts: Specifies a node or a group of nodes.
vars: Specifies the variables which required to be set for the playbook.
tasks: Specifies one or multiple tasks or plays which affect remote nodes.
handlers: Works like a label and when a change is made on a remote node, additional actions can be enforced by using notify keyword. Notify will refer to handlers section and trigger a specific task that has been notified.
There are also some full sets of playbooks illustrating a lot of these techniques in the ansible-examples repository
A full documentation can be found at Ansible official site: https://docs.ansible.com/ansible/latest/user_guide/playbooks.html
 

Playbook Generator

Playbook Generator is a Web UI tool for building a playbook yaml file. In this tool you can choose the required Packages, Services, Files and Actions you want to have in your playbook.
 

Executing Ad-hoc Commands

An ad-hoc command is something that you might type in to do something really quick, but don’t want to save for later. With this capability you can execute a quick one-liner in Ansible without writing a playbook.
This is a good place to start to understand the basics of what Ansible can do prior to learning the playbooks language - ad-hoc commands can also be used to do quick things that you might not necessarily want to write a full playbook for.
A -a 'COMMAND' option is used for running a system or ansible command:
Rebooting: ansible atlanta -a "/sbin/reboot" 
Gathering Facts: ansible all -m setup
And a -m MODULE option specifies which ansible module is going to be used:
Running a shell command: ansible atlanta -m shell -a 'echo $TERM'
Managing a package: ansible atlanta -m yum -a "name=acme state=present"
                                     ansible atlanta -m yum -a "name=acme state=absent"

Managing a service: ansible atlanta -m service -a "name=httpd state=started"
                                   ansible atlanta -m service -a "name=httpd state=stopped"
Creating a file/directory: ansible atlanta -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory"
                                          ansible atlanta -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=file"
Where atlanta refers to a node or a group of nodes.
A full documentation can be found at Ansible official site: https://docs.ansible.com/ansible/latest/user_guide/intro_adhoc.html

 

Executing Playbooks

It is advised that check a playbook before executing:
ansible-lint veryify-apache.yml
For executing a playbook:
ansible-playbook playbook.yml
To check the syntax of a playbook use --syntax-check flag:
ansible-playbook --syntax-check
To check which hosts would be affected by the playbook, use --list-host flag:
ansible-playbook playbook.yml --list-hosts
To see more information when executing a playbook, use --verbose flag and for even more use -vvv:
ansible-playbook playbook.yml --verbose

 

Error Handling

Ansible normally has defaults that make sure to check the return codes of commands and modules and it fails fast – forcing an error to be dealt with unless you decide otherwise.
Sometimes a command that returns different than 0 isn’t an error. Sometimes a command might not always need to report that it ‘changed’ the remote system. This section describes how to change the default behavior of Ansible for certain tasks so output and error handling behavior is as desired.
Ignoring Failed Commands
Generally playbooks will stop executing any more steps on a host that has a task fail. Sometimes, though, you want to continue on. To do so, write a task that looks like this:
- name: this will not be counted as a failure
  command: /bin/false
  ignore_errors: yes
Note that the above system only governs the return value of failure of the particular task, so if you have an undefined variable used or a syntax error, it will still raise an error that users will need to address. Note that this will not prevent failures on connection or execution issues. This feature only works when the task must be able to run and return a value of ‘failed’.
Resetting Unreachable Hosts
New in version 2.2.
Connection failures set hosts as ‘UNREACHABLE’, which will remove them from the list of active hosts for the run. To recover from these issues you can use meta: clear_host_errors to have all currently flagged hosts reactivated, so subsequent tasks can try to use them again.
Handlers and Failure
When a task fails on a host, handlers which were previously notified will not be run on that host. This can lead to cases where an unrelated failure can leave a host in an unexpected state. For example, a task could update a configuration file and notify a handler to restart some service. If a task later on in the same play fails, the service will not be restarted despite the configuration change.
You can change this behavior with the --force-handlers command-line option, or by including force_handlers: True in a play, or force_handlers = True in ansible.cfg. When handlers are forced, they will run when notified even if a task fails on that host. (Note that certain errors could still prevent the handler from running, such as a host becoming unreachable.)
Controlling What Defines Failure
Ansible lets you define what “failure” means in each task using the failed_when conditional. As with all conditionals in Ansible, lists of multiple failed_when conditions are joined with an implicit and, meaning the task only fails when all conditions are met. If you want to trigger a failure when any of the conditions is met, you must define the conditions in a string with an explicit or operator.
You may check for failure by searching for a word or phrase in the output of a command:
- name: Fail task when the command error output prints FAILED
  command: /usr/bin/example-command -x -y -z
  register: command_result
  failed_when: "'FAILED' in command_result.stderr"

 

or based on the return code:

- name: Fail task when both files are identical
  raw: diff foo/file1 bar/file2
  register: diff_cmd
  failed_when: diff_cmd.rc == 0 or diff_cmd.rc >= 2
 
In previous version of Ansible, this can still be accomplished as follows:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  ignore_errors: True
- name: fail the play if the previous command did not succeed
  fail:
    msg: "the command failed"
  when: "'FAILED' in command_result.stderr"
 
You can also combine multiple conditions for failure. This task will fail if both conditions are true:
- name: Check if a file exists in temp and fail task if it does
  command: ls /tmp/this_should_not_be_here
  register: result
  failed_when:
    - result.rc == 0
    - '"No such" not in result.stdout'
 
If you want the task to fail when only one condition is satisfied, change the failed_when definition to:
failed_when: result.rc == 0 or "No such" not in result.stdout
 
If you have too many conditions to fit neatly into one line, you can split it into a multi-line yaml value with >:
- name: example of many failed_when conditions with OR
  shell: "./myBinary"
  register: ret
  failed_when: >
    ("No such file or directory" in ret.stdout) or
    (ret.stderr != '') or
    (ret.rc == 10)

 

Overriding The Changed Result
When a shell/command or other module runs it will typically report “changed” status based on whether it thinks it affected machine state.
Sometimes you will know, based on the return code or output that it did not make any changes, and wish to override the “changed” result such that it does not appear in report output or does not cause handlers to fire:
tasks:
- shell: /usr/bin/billybass --mode="take me to the river"
    register: bass_result
    changed_when: "bass_result.rc != 2"
# this will never report 'changed' status
  - shell: wall 'beep'
    changed_when: False
 
You can also combine multiple conditions to override “changed” result:
- command: /bin/fake_command
  register: result
  ignore_errors: True
  changed_when:
    - '"ERROR" in result.stderr'
    - result.rc == 2

 

Aborting the play
Sometimes it’s desirable to abort the entire play on failure, not just skip remaining tasks for a host.
The any_errors_fatal play option will mark all hosts as failed if any fails, causing an immediate abort:
- hosts: somehosts
  any_errors_fatal: true
  roles:
    - myrole
 
for finer-grained control max_fail_percentage can be used to abort the run after a given percentage of hosts has failed.
Using blocks
Most of what you can apply to a single task (with the exception of loops) can be applied at the Blocks level, which also makes it much easier to set data or directives common to the tasks. Blocks also introduce the ability to handle errors in a way similar to exceptions in most programming languages. Blocks only deal with ‘failed’ status of a task. A bad task definition or an unreachable host are not ‘rescuable’ errors:
tasks:
- name: Handle the error
  block:
    - debug:
        msg: 'I execute normally'
    - name: i force a failure
      command: /bin/false
    - debug:
        msg: 'I never execute, due to the above task failing, :-('
  rescue:
    - debug:
        msg: 'I caught an error, can do stuff here to fix it, :-)'
 
This will ‘revert’ the failed status of the outer block task for the run and the play will continue as if it had succeeded. See Blocks error handling for more examples.
 
Blocks
Blocks allow for logical grouping of tasks and in play error handling. Most of what you can apply to a single task (with the exception of loops) can be applied at the block level, which also makes it much easier to set data or directives common to the tasks. This does not mean the directive affects the block itself, but is inherited by the tasks enclosed by a block. i.e. a when will be applied to the tasks, not the block itself.
Block example with named tasks inside the block
 tasks:
   - name: Install, configure, and start Apache
     block:
       - name: install httpd and memcached
         yum:
           name:
           - httpd
           - memcached
           state: present
- name: apply the foo config template
         template:
           src: templates/src.j2
           dest: /etc/foo.conf
       - name: start service bar and enable it
         service:
           name: bar
           state: started
           enabled: True
     when: ansible_facts['distribution'] == 'CentOS'
     become: true
     become_user: root
     ignore_errors: yes
 
In the example above, each of the 3 tasks will be executed after appending the when condition from the block and evaluating it in the task’s context. Also they inherit the privilege escalation directives enabling “become to root” for all the enclosed tasks. Finally, ignore_errors: yes will continue executing the playbook even if some of the tasks fail.
Names for tasks within blocks have been available since Ansible 2.3. We recommend using names in all tasks, within blocks or elsewhere, for better visibility into the tasks being executed when you run the playbook.
Blocks error handling
Blocks also introduce the ability to handle errors in a way similar to exceptions in most programming languages. Blocks only deal with ‘failed’ status of a task. A bad task definition or an unreachable host are not ‘rescuable’ errors.
Block error handling example
 tasks:
 - name: Handle the error
   block:
     - debug:
         msg: 'I execute normally'
     - name: i force a failure
       command: /bin/false
     - debug:
         msg: 'I never execute, due to the above task failing, :-('
   rescue:
     - debug:
         msg: 'I caught an error, can do stuff here to fix it, :-)'
 
This will ‘revert’ the failed status of the task for the run and the play will continue as if it had succeeded.
There is also an always section, that will run no matter what the task status is.
Block with always section
 - name: Always do X
   block:
     - debug:
         msg: 'I execute normally'
     - name: i force a failure
       command: /bin/false
     - debug:
         msg: 'I never execute :-('
   always:
     - debug:
         msg: "This always executes, :-)"
 
They can be added all together to do complex error handling.
Block with all sections
- name: Attempt and graceful roll back demo
  block:
    - debug:
        msg: 'I execute normally'
    - name: i force a failure
      command: /bin/false
    - debug:
        msg: 'I never execute, due to the above task failing, :-('
  rescue:
    - debug:
        msg: 'I caught an error'
    - name: i force a failure in middle of recovery! >:-)
      command: /bin/false
    - debug:
        msg: 'I also never execute :-('
  always:
    - debug:
        msg: "This always executes"
 
The tasks in the block would execute normally, if there is any error the rescue section would get executed with whatever you need to do to recover from the previous error. The always section runs no matter what previous error did or did not occur in the block and rescue sections. It should be noted that the play continues if a rescue section completes successfully as it ‘erases’ the error status (but not the reporting), this means it won’t trigger max_fail_percentage nor any_errors_fatal configurations but will appear in the playbook statistics.
Another example is how to run handlers after an error occurred :
Block run handlers in error handling
 tasks:
   - name: Attempt and graceful roll back demo
     block:
       - debug:
           msg: 'I execute normally'
         changed_when: yes
         notify: run me even after an error
       - command: /bin/false
     rescue:
       - name: make sure all handlers run
         meta: flush_handlers
 handlers:
    - name: run me even after an error
      debug:
        msg: 'This handler runs even on error'
 
Ansible also provides a couple of variables for tasks in the rescue portion of a block:
ansible_failed_task
The task that returned ‘failed’ and triggered the rescue. For example, to get the name use ansible_failed_task.name.
ansible_failed_result
The captured return result of the failed task that triggered the rescue. This would equate to having used this var in the register keyword.