Using playbooks

Playbooks basically allow us to manage the configuration of the deployment that we are going to make in the nodes. In them, we describe the configuration, but they also allow us to orchestrate a series of steps or tasks to follow.

In the playbook definition, we can use tasks, groups of machines, and variables; group variables; assign values to variables, conditional, loops, facts (information obtained by Ansible); get notifications and execution of actions based on them, apply labels to tasks; do includes; use templates (for the configuration files of the services, for example, of Apache or MySQL), wait for conditions, encrypt files that contain sensitive information, and include those files in a version control tool without risk of compromising the information; and we can use roles that apply all these things according to the function that we want a machine to have.

This is the basic structure of a playbook:

- name: Configure webserver with git
hosts: webserver
become: true
vars:
package: git
tasks:
- name: install git
apt: name={{ package }} state=present

Each playbook must contain the following elements:

  • A set of hosts to configure
  • A list of tasks to execute on those hosts

You can think of a playbook as the way to connect hosts with tasks. In addition to specifying hosts and tasks, the playbook also supports a number of optional configurations. Here are two common ones:

  • name: A comment that describes what the work is about. Ansible will print this when the work begins to run.
  • vars: A list of variables and values.

A playbook specifies a set of tasks to be run and which hosts to run them on. To demonstrate Ansible playbook execution, we'll automate the installation of the Apache server. Following is the file configuration used for this use case.

You can find the following code in the apache_server_playbook.yml file:


- hosts: test-servers
remote_user: username
become: true
vars:
project_root: /var/www/html
tasks:
- name: Install Apache Server
yum: pkg=httpd state=latest
- name: Place the index file at project root
copy: src=index.html dest={{ project_root }}/index.html owner=apache group=apache mode=0644
- name: Enable Apache on system reboot
service: name=httpd enabled=yes
notify: restart apache
handlers:
- name: restart apache
service: name=httpd state=restarted

For each task, you can specify the group of target nodes and the remote user that will execute each operation. The tasks are executed in order, one at a time, against the nodes described in the hosts section. It is important to note that if any node fails to execute the task, it will be removed from the list.

The objective of each task is to execute a module. The modules will only be executed when they have something to modify. If we run the playbook again and again, we can guarantee that the module will only be executed when there is something to modify.

If there are actions that need be executed at the end of each task in the playbook, we can use the notify keyword. This action will only be executed once, even when they are called by different tasks. In the previous playbook, we are using notify: restart apache to restart the Apache service.

In this playbook, we can see the use of variables set by the vars key. This key takes a key-value format, where the key is the variable name and the value is the actual value of the variable. This variable will overwrite other variables that are set by a global variable file or from an inventory file.

To run a playbook, we use the ansible-playbook command. To execute the previous playbook, simply run the following command:

$ ansible-playbook apache_server_playbook.yml -f 2

We can also make use of options when running the playbook. For example, the -syntax-check option checks the syntax before running the playbook. This is the output of the previous command:

PLAY [test-servers]
***************************************************************************************************************************

TASK [setup]
***************************************************************************************************************************
ok: [192.168.1.161]

TASK [Install Apache Server]
***************************************************************************************************************************
changed: [192.168.1.161]

TASK [Place the index file at project root]
***************************************************************************************************************************
changed: [192.168.1.161]

TASK [Enable Apache on system root]
***************************************************************************************************************************
changed: [192.168.1.161]

RUNNING HANDLER[restart apache]
***************************************************************************************************************************
changed: [192.168.1.161]

PLAY RECAP *******************************************************************************************
192.168.1.161 : ok=5 changed=4 unreachable=0 failed=0

The next playbook will just execute the ping module (https://docs.ansible.com/ansible/latest/modules/ping_module.html#ping-module) on all our hosts.

You can find the following code in the ping_playbook.yml file:

- hosts: all
tasks:
- name: ping all hosts
ping:

In this playbook, we are going to install Python 3 and NGINX in all machines defined in the inventory file.

You can find the following code in the install_python_ngnix.yml file:

- hosts: all
tasks:
- name: Install Nginx
apt: pkg=nginx state=installed update_cache=true
notify: Start Nginx
- name: Install Python 3
apt: pkg=python3-minimal state=installed
handlers:
- name: Start Nginx
service: name=nginx state=started

The playbook has a hosts section where the hosts of the inventory file are specified. In this case, we are processing all (hosts: all) machines introduced in the inventory file. Then there is a task section with two tasks that install NGINX and Python 3. Finally, there is a handlers section where NGINX starts after its installation. In this example, we are passing the static inventory to ansible-playbook with the ansible-playbook -i path/to/static-inventory-file myplaybook.yml command:

$ ansible-playbook -i hosts install_python_ngnix.yml --sudo

This is the output of the previous command:

PLAY ***************************************************************************

TASK [setup] *******************************************************************

ok: [192.168.1.160]
ok: [192.168.1.161]
ok: [192.168.1.162]

TASK [Install Nginx] ***********************************************************
changed: [192.168.1.160]
changed: [192.168.1.161]
changed: [192.168.1.162]

TASK [Install Python 3] ********************************************************
changed: [192.168.1.160]
changed: [192.168.1.161]
changed: [192.168.1.162]

RUNNING HANDLER [Start Nginx] **************************************************
changed: [192.168.1.160]
changed: [192.168.1.161]
changed: [192.168.1.162]

PLAY RECAP *********************************************************************
192.168.1.160 : ok=4 changed=3 unreachable=0 failed=0
192.168.1.161 : ok=4 changed=3 unreachable=0 failed=0
192.168.1.162 : ok=4 changed=3 unreachable=0 failed=0

We can also install multiple packages in a single task, as follows:

- name: Installing Nginx and python
apt: pkg={{ item }}
with_items:
- ngnix
- python3-minimal

Ansible also provides a Python API for running an Ansible playbook programmatically.

In this example, we are using VariableManager from the ansible.vars.manager package and InventoryManager from the ansible.inventory.manager package. VariableManager takes care of merging all the different sources to give you a unified view of the variables available in each context. InventoryManager uses the path of the hosts configuration file as a source. We use PlaybookExecutor from ansible.executor.playbook_executor to execute the playbook defined in the playbook_path variable.

You can find the following code in the execute_playbook.py file:

!/usr/bin/env python3

from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.playbook_executor import PlaybookExecutor

def execute_playbook():
playbook_path = "playbook_template.yml"
inventory_path = "hosts"

Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff', 'listhosts', 'listtasks', 'listtags', 'syntax'])
loader = DataLoader()
options = Options(connection='local', module_path='', forks=100, become=None, become_method=None, become_user=None, check=False,
diff=False, listhosts=False, listtasks=False, listtags=False, syntax=False)
passwords = dict(vault_pass='secret')

After importing the required modules, we define the execute_playbook method to initialize options, where we initialize our inventory using the inventory path. To execute the playbook, we use the PlaybookExecutor class and pass the playbook path, inventory, loader, and options objects as parameters. Finally, we use the run() method to execute the playbook:

    inventory = InventoryManager(loader=loader, sources=['inventory'])
variable_manager = VariableManager(loader=loader, inventory=inventory)
executor = PlaybookExecutor(
playbooks=[playbook_path], inventory=inventory, variable_manager=variable_manager, loader=loader,
options=options, passwords=passwords)
results = executor.run()
print(results)

if __name__ == "__main__":
execute_playbook()

With the Python API, we have the ability to run tasks in the same way we execute playbooks.

In this example, we create an inventory using the path of the hosts configuration file as a source and the variable manager takes care of merging all the different sources to give you a unified view of the variables available in each context.

Then we create a data structure dictionary that represents our play, including tasks, which is basically what our YAML loader does internally. In this case, the tasks include executing ping module for all hosts defined in the inventory.

We create a play object and execute the load() method from playbook object. This method will also automatically create the task objects from the information provided in the play_source variable.

To execute tasks, we need to instantiate TaskQueueManager from the ansible.executor.task_queue_manager package, which configures all objects to iterate over the host list and execute the ping module. For stdout_callback, we use the default callback plugin, which prints to stdout.

You can find the following code in thrun_tasks_playbook.py file:

#!/usr/bin/env python3

from collections import namedtuple
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase

Options = namedtuple('Options', ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 'check', 'diff'])

# initialize objects
loader = DataLoader()
options = Options(connection='local', module_path='', forks=100, become=None, become_method=None, become_user=None, check=False,
diff=False)
passwords = dict(vault_pass='secret')

# create inventory
inventory = InventoryManager(loader=loader, sources=['/etc/ansible/hosts'])
variable_manager = VariableManager(loader=loader, inventory=inventory)

# create play with tasks
play_source = dict(name = "myplaybook",hosts = 'all',gather_facts = 'no',
tasks = [dict(action=dict(module='ping')),])
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

After objects initialization, we create the inventory and create a playbook with tasks in a programmatic way. We can now execute the playbook using the TaskQueueManager class, passing as parameters the variables created in the previous block of code:

# execution
task = None
try:
task = TaskQueueManager(inventory=inventory,variable_manager=variable_manager,
loader=loader,options=options,passwords=passwords,stdout_callback='default')
result = task.run(play)
finally:
if task is not None:
task.cleanup()
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset