There are also many jumping off points after you learn playbooks, so hop back to the documentation
index after you're done with this section.
.._playbook_language_example:
Playbook Language Example
`````````````````````````
Playbooks are expressed in YAML format (see :doc:`YAMLSyntax`) and have a minimum of syntax, which intentionally
tries to not be a programming language or script, but rather a model of a configuration or a process.
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, which you should have learned about in earlier chapters.
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.
For starters, here's a playbook that contains just one play::
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: pkg=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
Below, we'll break down what the various features of the playbook language are.
.._playbook_basics:
Basics
``````
.._playbook_hosts_and_users:
Hosts and Users
+++++++++++++++
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 :doc:`intro_patterns`
documentation. The `remote_user` is just the name of the user account::
---
- hosts: webservers
remote_user: root
..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
..Note::
The `remote_user` parameter for tasks was added in 1.4.
Support for running things from sudo is also available::
---
- hosts: webservers
remote_user: yourname
sudo: yes
You can also use sudo on a particular task instead of the whole play::
---
- hosts: webservers
remote_user: yourname
tasks:
- service: name=nginx state=started
sudo: yes
You can also login as you, and then sudo to different users than root::
---
- hosts: webservers
remote_user: yourname
sudo: yes
sudo_user: postgres
If you need to specify a password to sudo, run `ansible-playbook` with ``--ask-sudo-pass`` (`-K`).
If you run a sudo playbook and the playbook seems to hang, it's probably stuck at the sudo prompt.
Just `Control-C` to kill it and run it again with `-K`.
..important::
When using `sudo_user` to a user other than root, the module
arguments are briefly written into a random tempfile in /tmp.
These are deleted immediately after the command is executed. This
only occurs when sudoing from a user like 'bob' to 'timmy', not
when going from 'bob' to 'root', or logging in directly as 'bob' or
'root'. If this concerns you that this data is briefly readable
(not writable), avoid transferring uncrypted passwords with
`sudo_user` set. In other cases, '/tmp' is not used and this does
not come into play. Ansible also takes care to not log password
parameters.
.._tasks_list:
Tasks list
++++++++++
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 are 'idempotent', meaning if you run them
again, they will make only the changes they must in order to bring the
system to the desired state. This makes it very safe to rerun
the same playbook multiple times. They won't change things
unless they have to change things.
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 output for humans, so it is
nice to have reasonably 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=running
The `command` and `shell` modules are the one modules that just takes a list
of arguments, and don't use the key=value form. This makes
them work just like you would expect. Simple::
tasks:
- name: disable selinux
command: /sbin/setenforce 0
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
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.
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 <https://github.com/ansible/ansible-examples/blob/master/language_features/ansible_pull.yml>`_ available to using ansible in push mode to configure ansible-pull via a crontab!