Sunday, April 23, 2017

Playbooks

A Play is a list of tasks and roles that should be run. A Play can also define vars that should be used for that play. A Playbook is a list of plays. It can contain a single play, or many. 


The goal of a play is to map a group of hosts to some well defined roles, represented by things ansible calls tasks. At a basic level, a task is nothing more than a call to an ansible module (see About Modules).

“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.

For each play in a playbook, you get to choose which machines in your infrastructure to target and what remote user to complete the steps (called tasks) as.

The hosts line is a list of one or more groups or host patterns, separated by colons, as described in the Patterns documentation. The remote_user is just the name of the user account:

---
- hosts: webservers
  remote_user: root

or each play in a playbook, you get to choose which machines in your infrastructure to target and what remote user to complete the steps (called tasks) as.

At a basic level, playbooks can be used to manage configurations of and deployments to remote machines. At a more advanced level, they can sequence multi-tier rollouts involving rolling updates, and can delegate actions to other hosts, interacting with monitoring servers and load balancers along the way.

While there’s a lot of information here, there’s no need to learn everything at once. You can start small and pick up more features over time as you need them.

A playbook is structured like so:
---
- name: play 1
  hosts: all
  become: true
  pre_tasks:
  - name: do something before roles
    debug: msg="this is run before a role"
    roles:
  - install_role
- name: play 2
  hosts: group2
  roles:

  - config_role

Playbook Language Example

Playbooks are expressed in YAML format.

The hosts line is a list of one or more groups or host patterns, separated by colons. The remote_user is just the name of the user account:
'
How Play executes

Each play contains a list of tasks. Tasks are executed in order, one at a time, against all machines matched by the host pattern, before moving on to the next task. It is important to understand that, within a play, all hosts are going to get the same task directives. It is the purpose of a play to map a selection of hosts to tasks.

When running the playbook, which runs top to bottom, hosts with failed tasks are taken out of the rotation for the entire playbook. If things fail, simply correct the playbook file and rerun.

The goal of each task is to execute a module, with very specific arguments. Variables, as mentioned above, can be used in arguments to modules.

Modules should be idempotent, that is, running a module multiple times in a sequence should have the same effect as running it just once. One way to achieve idempotency is to have a module check whether its desired final state has already been achieved, and if that state has been achieved, to exit without performing any actions. If all the modules a playbook uses are idempotent, then the playbook itself is likely to be idempotent, so re-running the playbook should be safe.

The command and shell modules will typically rerun the same command again, which is totally ok if the command is something like chmod or setsebool, etc. Though there is a creates flag available which can be used to make these modules also idempotent.

Every task should have a name, which is included in the output from running the playbook. This is human readable output, and so it is useful to provide good descriptions of each task step. If the name is not provided though, the string fed to ‘action’ will be used for output.

Tasks can be declared using the legacy action: module options format, but it is recommended that you use the more conventional module: options format. This recommended format is used throughout the documentation, but you may encounter the older format in some playbooks.

Here is what a basic task looks like. As with most modules, the service module takes key=value arguments:
tasks:
  - name: make sure apache is running
    service: name=httpd state=started

The command and shell modules are the only modules that just take a list of arguments and don’t use the key=value form. This makes them work as simply as you would expect:
tasks:
  - name: enable selinux
    command: /sbin/setenforce 1

The command and shell module care about return codes, so if you have a command whose successful exit code is not zero, you may wish to do this:

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand || /bin/true

Or this:

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/somecommand
    ignore_errors: True

If the action line is getting too long for comfort you can break it on a space and indent any continuation lines:

tasks:
  - name: Copy ansible inventory file to client
    copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
            owner=root group=root mode=0644

Variables can be used in action lines. Suppose you defined a variable called vhost in the vars section, you could do this:

tasks:
  - name: create a virtual host file for {{ vhost }}
    template: src=somefile.j2 dest=/etc/httpd/conf.d/{{ vhost }}

Those same variables are usable in templates, which we’ll get to later.

Note : The remote_user parameter was formerly called just user. It was renamed in Ansible 1.4 to make it more distinguishable from the user module (used to create users on remote systems).

Remote users can also be defined per task:
---
- hosts: webservers
  remote_user: root
  tasks:
    - name: test connection
      ping:
      remote_user: yourname

YAML Basics

Every YAML file starts with a list. Each item in the list is a list of key/value pairs, commonly called a “hash” or a “dictionary”. So, we need to know how to write lists and dictionaries in YAML.

All YAML files (regardless of their association with Ansible or not) can optionally begin with --- and end with .... This is part of the YAML format and indicates the start and end of a document.

All members of a list are lines beginning at the same indentation level starting with a "- " (a dash and a space):
---
# A list of tasty fruits
fruits:
    - Apple
    - Orange
    - Strawberry
    - Mango
...
A dictionary is represented in a simple key: value form (the colon must be followed by a space):

# An employee record
martin:
    name: Martin D'vloper
    job: Developer
    skill: Elite

Ansible doesn’t really use these too much, but you can also specify a boolean value (true/false) in several forms:

create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false

3) Values can span multiple lines using | or >. Spanning multiple lines using a | will include the newlines. Using a > will ignore newlines; it’s used to make what would otherwise be a very long line easier to read and edit. In either case the indentation will be ignored. 

Examples are:

include_newlines: |
            exactly as you see
            will appear these three
            lines of poetry
ignore_newlines: >
            this is really a
            single line of text
            despite appearances

4) Dictionaries and lists can also be represented in an abbreviated form if you really want to:
---
martin: {name: Martin D'vloper, job: Developer, skill: Elite}
fruits: ['Apple', 'Orange', 'Strawberry', 'Mango']

WHAT IS HANDLERS

The things listed in the notify section of a task are called handlers.

Handlers are lists of tasks, not really any different from regular tasks, that are referenced by a globally unique name, and are notified by notifiers. If nothing notifies a handler, it will not run. Regardless of how many tasks notify a handler, it will run only once, after all of the tasks complete in a particular play.
Here’s an example handlers section:

Each playbook is composed of one or more ‘plays’ in a list.

The goal of a play is to map a group of hosts to some well defined roles, represented by things ansible calls tasks. At a basic level, a task is nothing more than a call to an ansible module (see About Modules).

Roles are described later on, but it’s worthwhile to point out that:
handlers notified within pre_tasks, tasks, and post_tasks sections are automatically flushed in the end of section where they were notified;
handlers notified within roles section are automatically flushed in the end of tasks section, but before any tasks handlers.

HOW TO RUN PLAYBOOK

Now that you’ve learned playbook syntax, how do you run a playbook? It’s simple. Let’s run a playbook using a parallelism level of 10:

ansible-playbook playbook.yml -f 10
Ansible-Pull

Should you want to invert the architecture of Ansible, so that nodes check in to a central location, instead of pushing configuration out to them, you can.

The ansible-pull is a small script that will checkout a repo of configuration instructions from git, and then run ansible-playbook against that content.

Assuming you load balance your checkout location, ansible-pull scales essentially infinitely.
Run ansible-pull --help for details.

There’s also a clever playbook available to configure ansible-pull via a crontab from push mode.

Tips and Tricks

To check the syntax of a playbook, use ansible-playbook with the --syntax-check flag. This will run the playbook file through the parser to ensure its included files, roles, etc. have no syntax problems.

Look at the bottom of the playbook execution for a summary of the nodes that were targeted and how they performed. General failures and fatal “unreachable” communication attempts are kept separate in the counts.

If you ever want to see detailed output from successful modules as well as unsuccessful ones, use the --verbose flag. This is available in Ansible 0.5 and later.

Ansible playbook output is vastly upgraded if the cowsay package is installed. Try it!

To see what hosts would be affected by a playbook before you run it, you can do this:
ansible-playbook playbook.yml --list-hosts

Task versus Play includes

Tasks and plays both use the include keyword, but implement the keyword differently. The difference between them is determined by their positioning and content. If the include is inside a play it can only be a ‘task’ include and include a list of tasks; if it is at the top level, it can only include plays. For example:

# this is a 'play' include
- include: intro_example.yml
- name: another play
  hosts: all
  tasks:
    - debug: msg=hello
    # this is a 'task' include
    - include: stuff.yml

A task include file simply contains a flat list of tasks, like so:
---
# possibly saved as tasks/foo.yml
- name: placeholder foo
  command: /bin/foo
- name: placeholder bar
  command: /bin/bar
Include directives look like this, and can be mixed in with regular tasks in a playbook:

tasks:
  - include: tasks/foo.yml

You can also pass variables into includes. We call this a ‘parameterized include’.

For instance, to deploy to multiple wordpress instances, I could encapsulate all of my wordpress tasks in a single wordpress.yml file, and use it like so:
tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob

A Role is an organizational unit for tasks, vars, files, etc. Instead of having to list all of your tasks for a play directly in the playbook, you can reference a role, which contains tasks, vars, files, templates, and handlers in one nice, portable package.

Working with Ansible

Inventory File

This file has the list of hosts where the ansible commands or playbook need to be executed. Default inventory file will be under /etc/ansible/hosts.

Sample Entries

[test-servers]
192.168.0.1
192.168.0.2
192.168.0.3

[appserver]
one.example.com
two.example.com
three.example.com

We can put system name in more than one group, for an instance a server name can be in both test-servers and a appserver group.

Different Port number 

If you have hosts that run on non-standard SSH ports you can mention the port number after the hostname with a colon.


four.example.com:5309

Adding a lot of hosts? 

If you have a lot of hosts following similar patterns you can do this rather than listing each hostname:

[test-servers]
test[01:50].example.com

For numeric patterns, leading zeros can be included or removed, as desired. Ranges are inclusive. You can also define alphabetic ranges:

[appservers]
db-[a:f].example.com

Note: The ‘test-servers‘ in the brackets indicates group names, it is used in differentiate the server deciding which systems you are going to controlling at what times and for what reason.

Now let’s check our all 3 servers by just doing a ping from my localhost (Master Server).

To perform the action we need to use the command ‘ansible‘ with options ‘-m‘ (module) and ‘-all‘ (group of servers).

# ansible -m ping test-servers

Note : If the inventory file is in the default path then we no need to mention the complete path of inventory file. You can specify a different inventory file using the -i <path> option on the command line.

other2.example.com     ansible_connection=ssh        ansible_user=mdehaan

host1 http_port=80 maxRequestsPerChild=808

Testing with Simple modules.

Now we will test with module called ‘command‘, which is used to execute list of commands (like, date, uptime, whoami etc.) on all selected remote hosts at one go, for example watch out few examples shown below.

a)  To check the partitions on all remote hosts
# ansible -m command -a "df -h" test-servers

- m -  Module name

- a  - Command line arguments

b) Check memory usage on all remote hosts.
# ansible -m command -a "free -mt" test-servers

c) Checking Uptime for all 3 servers.
# ansible -m command -a "uptime" test-servers

d) Check for hostname and Architecture.
# ansible -m command -a "arch" test-servers
# ansible -m shell -a "hostname" test-servers

e) If we need the output to any file we can redirect as below.
# ansible -m command -a "uptime" web-servers > /u01/uptime_output.txt

Like this way, we can run many shell commands using ansible as what we have run the above steps.

Executing Commands as other user

By default, Ansible will attempt to remote connect to the machines using your current user name, just like SSH would. To override the remote user name, just use the ‘-u’ parameter.

If you would like to access sudo mode, there are also flags to do that:

# as bruce
$ ansible all -m ping -u bruce

# as bruce, sudoing to root
$ ansible all -m ping -u bruce --sudo

# as bruce, sudoing to batman
$ ansible all -m ping -u bruce --sudo --sudo-user batman

# With latest version of ansible `sudo` is deprecated so use become 
$ ansible all -m ping -u bruce -b

# as bruce, sudoing to batman
$ ansible all -m ping -u bruce -b --become-user batman

(The sudo implementation is changeable in Ansible’s configuration file if you happen to want to use a sudo replacement. Flags passed to sudo (like -H) can also be set there.)

To Run command on all nodes irrespective of your server groups

If we use -all option then ansible will try to ping all the hosts in the inventory file irrespective of the group.

# ansible all -a "/bin/echo hello"

–all Option

# ansible -m ping –all

What is Ansible and How it Works

What is Ansible

Ansible is a IT automation, configuration management tool to manage the infrastructure

How Ansible Works

Ansible is agent-less (that means no need of any agent installation on remote nodes) tool and uses SSH protocol to deploy modules on remote nodes. These modules are stored temporarily on remote nodes and communicate with the Ansible machine through a JSON connection. 

The location of nodes are specified by controlling machine through its inventory.

Ansible can handle 100’s of nodes from a single system over SSH connection and the entire operation can be handled and executed by one single command ‘ansible’. 

But, in some cases, where you required to execute multiple commands for a deployment, here we can build playbooks.

Playbooks are bunch of commands which can perform multiple tasks and each playbooks are in YAML file format.

Pre - Requistes for Ansible


Operating System: RHEL/CentOS/Fedora and Ubuntu/Debian/Linux Mint

Packages

Jinja2: A modern, fast and easy to use stand-alone template engine for Python.
PyYAML: A YAML parser and emitter for the Python programming language.
parmiko: A native Python SSHv2 channel library.
httplib2: A comprehensive HTTP client library.
sshpass: A non-interactive ssh password authentication.

Managed Node Requirements

On the managed nodes, you need a way to communicate, which is normally ssh. By default this uses sftp. If that’s not available, you can switch to scp in ansible.cfg. You also need Python 2.6 or later.


Remote Connection Information

By default, Ansible 1.3 and later will try to use native OpenSSH for remote communication when possible. This enables ControlPersist (a performance feature), Kerberos, and options in ~/.ssh/config such as Jump Host setup. However, when using Enterprise Linux 6 operating systems as the control machine (Red Hat Enterprise Linux and derivatives such as CentOS), the version of OpenSSH may be too old to support ControlPersist. On these operating systems, Ansible will fallback into using a high-quality Python implementation of OpenSSH called ‘paramiko’. If you wish to use features like Kerberized SSH and more, consider using Fedora, OS X, or Ubuntu as your control machine until a newer version of OpenSSH is available for your platform – or engage ‘accelerated mode’ in Ansible.