mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
339 lines
13 KiB
ReStructuredText
339 lines
13 KiB
ReStructuredText
Advanced Playbooks
|
|
==================
|
|
|
|
Here are some advanced features of the playbooks language. Using all of these features
|
|
are not neccessary, but many of them will prove useful. If a feature doesn't seem immediately
|
|
relevant, feel free to skip it. For many people, the features documented in `playbooks` will
|
|
be 90% or more of what they use in Ansible.
|
|
|
|
Accessing Complex Variable Data
|
|
+++++++++++++++++++++++++++++++
|
|
|
|
Some provided facts, like networking information, are made available as nested datastructures. To access
|
|
them a simple '$foo' is not sufficient, but it is still easy to do. Here's how we get an IP address using
|
|
Ansible 0.4 and later::
|
|
|
|
${ansible_eth0.ipv4.address}
|
|
|
|
It is also possible to access variables whose elements are arrays::
|
|
|
|
${somelist[0]}
|
|
|
|
And the array and hash reference syntaxes can be mixed.
|
|
|
|
In templates, the simple access form still holds, but they can also be accessed from Jinja2 in more Python-native ways if
|
|
that is preferred::
|
|
|
|
{{ ansible_eth0["ipv4"]["address"] }}
|
|
|
|
Accessing Information About Other Hosts
|
|
+++++++++++++++++++++++++++++++++++++++
|
|
|
|
If your database server wants to check the value of a 'fact' from another node, or an inventory variable
|
|
assigned to another node, it's easy to do so within a template or even an action line (note: this uses syntax available in 0.4 and later)::
|
|
|
|
${hostvars.hostname.factname}
|
|
|
|
NOTE: No database or other complex system is required to exchange data between hosts. The hosts that you
|
|
want to reference data from must be included in either the current play or any previous play.
|
|
|
|
Magic Variables
|
|
+++++++++++++++
|
|
|
|
Some variables made available to hosts don't come from definitions in a playbook, the inventory file, or discovery from the system. There are only two of these, and are used in special cases that many users won't need.
|
|
|
|
`groups` is a list (array) of all the groups the current host is in. This can be used in templates using Jinja2
|
|
syntax to make template source files that vary based on the group membership (or role) of the host::
|
|
|
|
{% if 'webserver' in groups %}
|
|
# some part of a configuration file that only applies to webservers
|
|
{% endif %}
|
|
|
|
`inventory_hostname` is the name of the hostname as configured in Ansible's inventory host file. This can
|
|
be useful for when you don't want to rely on the discovered hostname `ansible_hostname` or for other mysterious
|
|
reasons. Don't worry about it unless you think you need it.
|
|
|
|
Variable File Seperation
|
|
++++++++++++++++++++++++
|
|
|
|
It's a great idea to keep your playbooks under source control, but
|
|
you may wish to make the playbook source public while keeping certain
|
|
important variables private. Similarly, sometimes you may just
|
|
want to keep certain information in different files, away from
|
|
the main playbook.
|
|
|
|
You can do this by using an external variables file, or files, just like this::
|
|
|
|
---
|
|
- hosts: all
|
|
user: root
|
|
vars:
|
|
favcolor: blue
|
|
vars_files:
|
|
- /vars/external_vars.yml
|
|
tasks:
|
|
- name: this is just a placeholder
|
|
action: command /bin/echo foo
|
|
|
|
This removes the risk of sharing sensitive data with others when
|
|
sharing your playbook source with them.
|
|
|
|
The contents of each variables file is a simple YAML dictionary, like this::
|
|
|
|
---
|
|
# in the above example, this would be vars/external_vars.yml
|
|
somevar: somevalue
|
|
password: magic
|
|
|
|
Prompting For Sensitive Data
|
|
++++++++++++++++++++++++++++
|
|
|
|
You may wish to prompt the user for certain input, and can
|
|
do so with the similarly named 'vars_prompt' section. This has uses
|
|
beyond security, for instance, you may use the same playbook for all
|
|
software releases and would prompt for a particular release version
|
|
in a push-script::
|
|
|
|
---
|
|
- hosts: all
|
|
user: root
|
|
vars:
|
|
from: "camelot"
|
|
vars_prompt:
|
|
name: "what is your name?"
|
|
quest: "what is your quest?"
|
|
favcolor: "what is your favorite color?"
|
|
|
|
There are full examples of both of these items in the github examples/playbooks directory.
|
|
|
|
Passing Variables On The Command Line
|
|
+++++++++++++++++++++++++++++++++++++
|
|
|
|
In addition to `vars_prompt` and `vars_files`, it is possible to send variables over
|
|
the ansible command line. This is particularly useful when writing a generic release playbook
|
|
where you may want to pass in the version of the application to deploy::
|
|
|
|
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
|
|
|
|
Conditional Execution
|
|
+++++++++++++++++++++
|
|
|
|
Sometimes you will want to skip a particular step on a particular host. This could be something
|
|
as simple as not installing a certain package if the operating system is a particular version,
|
|
or it could be something like performing some cleanup steps if a filesystem is getting full.
|
|
|
|
This is easy to do in Ansible, with the `only_if` clause, which actually is a Python expression.
|
|
Don't panic -- it's actually pretty simple::
|
|
|
|
vars:
|
|
favcolor: blue
|
|
is_favcolor_blue: "'$favcolor' == 'blue'"
|
|
is_centos: "'$facter_operatingsystem' == 'CentOS'"
|
|
tasks:
|
|
- name: "shutdown if my favorite color is blue"
|
|
action: command /sbin/shutdown -t now
|
|
only_if: '$is_favcolor_blue'
|
|
|
|
Variables from tools like `facter` and `ohai` can be used here, if installed, or you can
|
|
use variables that bubble up from ansible (0.3 and later). As a reminder,
|
|
these variables are prefixed, so it's `$facter_operatingsystem`, not `$operatingsystem`. Ansible's
|
|
built in variables are prefixed with `ansible_`. The only_if
|
|
expression is actually a tiny small bit of Python, so be sure to quote variables and make something
|
|
that evaluates to `True` or `False`. It is a good idea to use 'vars_files' instead of 'vars' to define
|
|
all of your conditional expressions in a way that makes them very easy to reuse between plays
|
|
and playbooks.
|
|
|
|
|
|
Conditional Imports
|
|
+++++++++++++++++++
|
|
|
|
Sometimes you will want to do certain things differently in a playbook based on certain criteria.
|
|
Having one playbook that works on multiple platforms and OS versions is a good example.
|
|
|
|
As an example, the name of the Apache package may be different between CentOS and Debian,
|
|
but it is easily handled with a minimum of syntax in an Ansible Playbook::
|
|
|
|
---
|
|
- hosts: all
|
|
user: root
|
|
vars_files:
|
|
- "vars/common.yml"
|
|
- [ "vars/$facter_operatingsystem.yml", "vars/os_defaults.yml" ]
|
|
tasks:
|
|
- name: make sure apache is running
|
|
action: service name=$apache state=running
|
|
|
|
Note that a variable (`$facter_operatingsystem`) is being interpolated into the list of
|
|
filenames being defined for vars_files.
|
|
|
|
As a reminder, the various YAML files contain just keys and values::
|
|
|
|
---
|
|
# for vars/CentOS.yml
|
|
apache: httpd
|
|
somethingelse: 42
|
|
|
|
How does this work? If the operating system was 'CentOS', the first file Ansible would try to import
|
|
would be 'vars/CentOS.yml', followed up by '/vars/os_defaults.yml' if that file
|
|
did not exist. If no files in the list were found, an error would be raised.
|
|
On Debian, it would instead first look towards 'vars/Debian.yml' instead of 'vars/CentOS.yml', before
|
|
falling back on 'vars/os_defaults.yml'. Pretty simple.
|
|
|
|
To use this conditional import feature, you'll need facter or ohai installed prior to running the playbook, but
|
|
you can of course push this out with Ansible if you like::
|
|
|
|
# for facter
|
|
ansible -m yum -a "pkg=facter ensure=installed"
|
|
ansible -m yum -a "pkg=ruby-json ensure=installed"
|
|
|
|
# for ohai
|
|
ansible -m yum -a "pkg=ohai ensure=installed"
|
|
|
|
Ansible's approach to configuration -- seperating variables from tasks, keeps your playbooks
|
|
from turning into arbitrary code with ugly nested ifs, conditionals, and so on - and results
|
|
in more streamlined & auditable configuration rules -- especially because there are a
|
|
minimum of decision points to track.
|
|
|
|
Loop Shorthand
|
|
++++++++++++++
|
|
|
|
To save some typing, repeated tasks can be written in short-hand like so::
|
|
|
|
- name: add user $item
|
|
action: user name=$item state=present groups=wheel
|
|
with_items:
|
|
- testuser1
|
|
- testuser2
|
|
|
|
The above would be the equivalent of::
|
|
|
|
- name: add user testuser1
|
|
action: user name=testuser1 state=present groups=wheel
|
|
- name: add user testuser2
|
|
action: user name=testuser2 state=present groups=wheel
|
|
|
|
In a future release, the yum and apt modules will use with_items to execute fewer package
|
|
manager transactions.
|
|
|
|
|
|
Selecting Files And Templates Based On Variables
|
|
++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
Sometimes a configuration file you want to copy, or a template you will use may depend on a variable.
|
|
The following construct (new in 0.4) selects the first available file appropriate for the variables of a given host,
|
|
which is often much cleaner than putting a lot of if conditionals in a template.
|
|
|
|
The following example shows how to template out a configuration file that was very different between, say,
|
|
CentOS and Debian::
|
|
|
|
- name: template a file
|
|
action: template src=$item dest=/etc/myapp/foo.conf
|
|
first_available_file:
|
|
- /srv/templates/myapp/${ansible_distribution}.conf
|
|
- /srv/templates/myapp/default.conf
|
|
|
|
|
|
Asynchronous Actions and Polling
|
|
++++++++++++++++++++++++++++++++
|
|
|
|
By default tasks in playbooks block, meaning the connections stay open
|
|
until the task is done on each node. If executing playbooks with
|
|
a small parallelism value (aka ``--forks``), you may wish that long
|
|
running operations can go faster. The easiest way to do this is
|
|
to kick them off all at once and then poll until they are done.
|
|
|
|
You will also want to use asynchronous mode on very long running
|
|
operations that might be subject to timeout.
|
|
|
|
To launch a task asynchronously, specify its maximum runtime
|
|
and how frequently you would like to poll for status. The default
|
|
poll value is 10 seconds if you do not specify a value for `poll`::
|
|
|
|
---
|
|
- hosts: all
|
|
user: root
|
|
tasks:
|
|
- name: simulate long running op (15 sec), wait for up to 45, poll every 5
|
|
action: command /bin/sleep 15
|
|
async: 45
|
|
poll: 5
|
|
|
|
.. note::
|
|
There is no default for the async time limit. If you leave off the
|
|
'async' keyword, the task runs synchronously, which is Ansible's
|
|
default.
|
|
|
|
Alternatively, if you do not need to wait on the task to complete, you may
|
|
"fire and forget" by specifying a poll value of 0::
|
|
|
|
---
|
|
- hosts: all
|
|
user: root
|
|
tasks:
|
|
- name: simulate long running op, allow to run for 45, fire and forget
|
|
action: command /bin/sleep 15
|
|
async: 45
|
|
poll: 0
|
|
|
|
.. note::
|
|
You shouldn't "fire and forget" with operations that require
|
|
exclusive locks, such as yum transactions, if you expect to run other
|
|
commands later in the playbook against those same resources.
|
|
|
|
.. note::
|
|
Using a higher value for ``--forks`` will result in kicking off asynchronous
|
|
tasks even faster. This also increases the efficiency of polling.
|
|
|
|
Local Playbooks
|
|
+++++++++++++++
|
|
|
|
It may be useful to use a playbook locally, rather than by connecting over SSH. This can be useful
|
|
for assuring the configuration of a system by putting a playbook on a crontab. This may also be used
|
|
to run a playbook inside a OS installer, such as an Anaconda kickstart.
|
|
|
|
To run an entire playbook locally, just set the "hosts:" line to "hosts:127.0.0.1" and then run the playbook like so::
|
|
|
|
ansible-playbook playbook.yml --connection=local
|
|
|
|
Alternatively, a local connection can be used in a single playbook play, even if other plays in the playbook
|
|
use the default remote connection type::
|
|
|
|
hosts: 127.0.0.1
|
|
connection: local
|
|
|
|
Pull-Mode Playbooks
|
|
+++++++++++++++++++
|
|
|
|
The use of playbooks in local mode (above) is made extremely powerful with the addition of `ansible-pull` in the
|
|
0.4 release. A script for setting up ansible-pull is provided in the examples/playbooks directory of the source
|
|
checkout.
|
|
|
|
The basic idea is to use Ansible to set up a remote copy of ansible on each managed node, each set to run via
|
|
cron and update playbook source via git. This interverts the default push architecture of ansible into a pull
|
|
architecture, which has near-limitless scaling potential. The setup playbook can be tuned to change
|
|
the cron frequency, logging locations, and parameters to ansible-pull.
|
|
|
|
This is useful both for extreme scale-out as well as periodic remediation. Usage of the 'fetch' module to retrieve
|
|
logs from ansible-pull runs would be an excellent way to gather and analyze remote logs from ansible-pull.
|
|
|
|
|
|
.. seealso::
|
|
|
|
:doc:`YAMLSyntax`
|
|
Learn about YAML syntax
|
|
:doc:`playbooks`
|
|
Review the basic playbook features
|
|
:doc:`bestpractices`
|
|
Various tips about playbooks in the real world
|
|
:doc:`modules`
|
|
Learn about available modules
|
|
:doc:`moduledev`
|
|
Learn how to extend Ansible by writing your own modules
|
|
:doc:`patterns`
|
|
Learn about how to select hosts
|
|
`Github examples directory <https://github.com/ansible/ansible/tree/master/examples/playbooks>`_
|
|
Complete playbook files from the github project source
|
|
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
|
Questions? Help? Ideas? Stop by the list on Google Groups
|
|
|
|
|