1.. _playbooks_best_practices:
2
3Best Practices
4==============
5
6Here are some tips for making the most of Ansible and Ansible playbooks.
7
8You can find some example playbooks illustrating these best practices in our `ansible-examples repository <https://github.com/ansible/ansible-examples>`_.  (NOTE: These may not use all of the features in the latest release, but are still an excellent reference!).
9
10.. contents:: Topics
11
12.. _content_organization:
13
14Content Organization
15++++++++++++++++++++++
16
17The following section shows one of many possible ways to organize playbook content.
18
19Your usage of Ansible should fit your needs, however, not ours, so feel free to modify this approach and organize as you see fit.
20
21One crucial way to organize your playbook content is Ansible's "roles" organization feature, which is documented as part
22of the main playbooks page.  You should take the time to read and understand the roles documentation which is available here: :ref:`playbooks_reuse_roles`.
23
24.. _directory_layout:
25
26Directory Layout
27````````````````
28
29The top level of the directory would contain files and directories like so::
30
31    production                # inventory file for production servers
32    staging                   # inventory file for staging environment
33
34    group_vars/
35       group1.yml             # here we assign variables to particular groups
36       group2.yml
37    host_vars/
38       hostname1.yml          # here we assign variables to particular systems
39       hostname2.yml
40
41    library/                  # if any custom modules, put them here (optional)
42    module_utils/             # if any custom module_utils to support modules, put them here (optional)
43    filter_plugins/           # if any custom filter plugins, put them here (optional)
44
45    site.yml                  # master playbook
46    webservers.yml            # playbook for webserver tier
47    dbservers.yml             # playbook for dbserver tier
48
49    roles/
50        common/               # this hierarchy represents a "role"
51            tasks/            #
52                main.yml      #  <-- tasks file can include smaller files if warranted
53            handlers/         #
54                main.yml      #  <-- handlers file
55            templates/        #  <-- files for use with the template resource
56                ntp.conf.j2   #  <------- templates end in .j2
57            files/            #
58                bar.txt       #  <-- files for use with the copy resource
59                foo.sh        #  <-- script files for use with the script resource
60            vars/             #
61                main.yml      #  <-- variables associated with this role
62            defaults/         #
63                main.yml      #  <-- default lower priority variables for this role
64            meta/             #
65                main.yml      #  <-- role dependencies
66            library/          # roles can also include custom modules
67            module_utils/     # roles can also include custom module_utils
68            lookup_plugins/   # or other types of plugins, like lookup in this case
69
70        webtier/              # same kind of structure as "common" was above, done for the webtier role
71        monitoring/           # ""
72        fooapp/               # ""
73
74.. note: If you find yourself having too many top level playbooks (for instance you have a playbook you wrote for a specific hotfix, etc), it may make sense to have a playbooks/ directory instead.  This can be a good idea as you get larger.  If you do this, configure your roles_path in ansible.cfg to find your roles location.
75
76.. _alternative_directory_layout:
77
78Alternative Directory Layout
79````````````````````````````
80
81Alternatively you can put each inventory file with its ``group_vars``/``host_vars`` in a separate directory. This is particularly useful if your ``group_vars``/``host_vars`` don't have that much in common in different environments. The layout could look something like this::
82
83    inventories/
84       production/
85          hosts               # inventory file for production servers
86          group_vars/
87             group1.yml       # here we assign variables to particular groups
88             group2.yml
89          host_vars/
90             hostname1.yml    # here we assign variables to particular systems
91             hostname2.yml
92
93       staging/
94          hosts               # inventory file for staging environment
95          group_vars/
96             group1.yml       # here we assign variables to particular groups
97             group2.yml
98          host_vars/
99             stagehost1.yml   # here we assign variables to particular systems
100             stagehost2.yml
101
102    library/
103    module_utils/
104    filter_plugins/
105
106    site.yml
107    webservers.yml
108    dbservers.yml
109
110    roles/
111        common/
112        webtier/
113        monitoring/
114        fooapp/
115
116This layout gives you more flexibility for larger environments, as well as a total separation of inventory variables between different environments. The downside is that it is harder to maintain, because there are more files.
117
118.. _use_dynamic_inventory_with_clouds:
119
120Use Dynamic Inventory With Clouds
121`````````````````````````````````
122
123If you are using a cloud provider, you should not be managing your inventory in a static file.  See :ref:`intro_dynamic_inventory`.
124
125This does not just apply to clouds -- If you have another system maintaining a canonical list of systems
126in your infrastructure, usage of dynamic inventory is a great idea in general.
127
128.. _staging_vs_prod:
129
130How to Differentiate Staging vs Production
131``````````````````````````````````````````
132
133If managing static inventory, it is frequently asked how to differentiate different types of environments.  The following example
134shows a good way to do this.  Similar methods of grouping could be adapted to dynamic inventory (for instance, consider applying the AWS
135tag "environment:production", and you'll get a group of systems automatically discovered named "ec2_tag_environment_production".
136
137Let's show a static inventory example though.  Below, the *production* file contains the inventory of all of your production hosts.
138
139It is suggested that you define groups based on purpose of the host (roles) and also geography or datacenter location (if applicable)::
140
141    # file: production
142
143    [atlanta_webservers]
144    www-atl-1.example.com
145    www-atl-2.example.com
146
147    [boston_webservers]
148    www-bos-1.example.com
149    www-bos-2.example.com
150
151    [atlanta_dbservers]
152    db-atl-1.example.com
153    db-atl-2.example.com
154
155    [boston_dbservers]
156    db-bos-1.example.com
157
158    # webservers in all geos
159    [webservers:children]
160    atlanta_webservers
161    boston_webservers
162
163    # dbservers in all geos
164    [dbservers:children]
165    atlanta_dbservers
166    boston_dbservers
167
168    # everything in the atlanta geo
169    [atlanta:children]
170    atlanta_webservers
171    atlanta_dbservers
172
173    # everything in the boston geo
174    [boston:children]
175    boston_webservers
176    boston_dbservers
177
178.. _groups_and_hosts:
179
180Group And Host Variables
181````````````````````````
182
183This section extends on the previous example.
184
185Groups are nice for organization, but that's not all groups are good for.  You can also assign variables to them!  For instance, atlanta has its own NTP servers, so when setting up ntp.conf, we should use them.  Let's set those now::
186
187    ---
188    # file: group_vars/atlanta
189    ntp: ntp-atlanta.example.com
190    backup: backup-atlanta.example.com
191
192Variables aren't just for geographic information either!  Maybe the webservers have some configuration that doesn't make sense for the database servers::
193
194    ---
195    # file: group_vars/webservers
196    apacheMaxRequestsPerChild: 3000
197    apacheMaxClients: 900
198
199If we had any default values, or values that were universally true, we would put them in a file called group_vars/all::
200
201    ---
202    # file: group_vars/all
203    ntp: ntp-boston.example.com
204    backup: backup-boston.example.com
205
206We can define specific hardware variance in systems in a host_vars file, but avoid doing this unless you need to::
207
208    ---
209    # file: host_vars/db-bos-1.example.com
210    foo_agent_port: 86
211    bar_agent_port: 99
212
213Again, if we are using dynamic inventory sources, many dynamic groups are automatically created.  So a tag like "class:webserver" would load in
214variables from the file "group_vars/ec2_tag_class_webserver" automatically.
215
216.. _split_by_role:
217
218Top Level Playbooks Are Separated By Role
219`````````````````````````````````````````
220
221In site.yml, we import a playbook that defines our entire infrastructure.  This is a very short example, because it's just importing
222some other playbooks::
223
224    ---
225    # file: site.yml
226    - import_playbook: webservers.yml
227    - import_playbook: dbservers.yml
228
229In a file like webservers.yml (also at the top level), we map the configuration of the webservers group to the roles performed by the webservers group::
230
231    ---
232    # file: webservers.yml
233    - hosts: webservers
234      roles:
235        - common
236        - webtier
237
238The idea here is that we can choose to configure our whole infrastructure by "running" site.yml or we could just choose to run a subset by running
239webservers.yml.  This is analogous to the "--limit" parameter to ansible but a little more explicit::
240
241   ansible-playbook site.yml --limit webservers
242   ansible-playbook webservers.yml
243
244.. _role_organization:
245
246Task And Handler Organization For A Role
247````````````````````````````````````````
248
249Below is an example tasks file that explains how a role works.  Our common role here just sets up NTP, but it could do more if we wanted::
250
251    ---
252    # file: roles/common/tasks/main.yml
253
254    - name: be sure ntp is installed
255      yum:
256        name: ntp
257        state: present
258      tags: ntp
259
260    - name: be sure ntp is configured
261      template:
262        src: ntp.conf.j2
263        dest: /etc/ntp.conf
264      notify:
265        - restart ntpd
266      tags: ntp
267
268    - name: be sure ntpd is running and enabled
269      service:
270        name: ntpd
271        state: started
272        enabled: yes
273      tags: ntp
274
275Here is an example handlers file.  As a review, handlers are only fired when certain tasks report changes, and are run at the end
276of each play::
277
278    ---
279    # file: roles/common/handlers/main.yml
280    - name: restart ntpd
281      service:
282        name: ntpd
283        state: restarted
284
285See :ref:`playbooks_reuse_roles` for more information.
286
287
288.. _organization_examples:
289
290What This Organization Enables (Examples)
291`````````````````````````````````````````
292
293Above we've shared our basic organizational structure.
294
295Now what sort of use cases does this layout enable?  Lots!  If I want to reconfigure my whole infrastructure, it's just::
296
297    ansible-playbook -i production site.yml
298
299To reconfigure NTP on everything::
300
301    ansible-playbook -i production site.yml --tags ntp
302
303To reconfigure just my webservers::
304
305    ansible-playbook -i production webservers.yml
306
307For just my webservers in Boston::
308
309    ansible-playbook -i production webservers.yml --limit boston
310
311For just the first 10, and then the next 10::
312
313    ansible-playbook -i production webservers.yml --limit boston[0:9]
314    ansible-playbook -i production webservers.yml --limit boston[10:19]
315
316And of course just basic ad-hoc stuff is also possible::
317
318    ansible boston -i production -m ping
319    ansible boston -i production -m command -a '/sbin/reboot'
320
321And there are some useful commands to know::
322
323    # confirm what task names would be run if I ran this command and said "just ntp tasks"
324    ansible-playbook -i production webservers.yml --tags ntp --list-tasks
325
326    # confirm what hostnames might be communicated with if I said "limit to boston"
327    ansible-playbook -i production webservers.yml --limit boston --list-hosts
328
329.. _dep_vs_config:
330
331Deployment vs Configuration Organization
332````````````````````````````````````````
333
334The above setup models a typical configuration topology.  When doing multi-tier deployments, there are going
335to be some additional playbooks that hop between tiers to roll out an application.  In this case, 'site.yml'
336may be augmented by playbooks like 'deploy_exampledotcom.yml' but the general concepts can still apply.
337
338Consider "playbooks" as a sports metaphor -- you don't have to just have one set of plays to use against your infrastructure
339all the time -- you can have situational plays that you use at different times and for different purposes.
340
341Ansible allows you to deploy and configure using the same tool, so you would likely reuse groups and just
342keep the OS configuration in separate playbooks from the app deployment.
343
344.. _staging_vs_production:
345
346Staging vs Production
347+++++++++++++++++++++
348
349As also mentioned above, a good way to keep your staging (or testing) and production environments separate is to use a separate inventory file for staging and production.   This way you pick with -i what you are targeting.  Keeping them all in one file can lead to surprises!
350
351Testing things in a staging environment before trying in production is always a great idea.  Your environments need not be the same
352size and you can use group variables to control the differences between those environments.
353
354.. _rolling_update:
355
356Rolling Updates
357+++++++++++++++
358
359Understand the 'serial' keyword.  If updating a webserver farm you really want to use it to control how many machines you are
360updating at once in the batch.
361
362See :ref:`playbooks_delegation`.
363
364.. _mention_the_state:
365
366Always Mention The State
367++++++++++++++++++++++++
368
369The 'state' parameter is optional to a lot of modules.  Whether 'state=present' or 'state=absent', it's always best to leave that
370parameter in your playbooks to make it clear, especially as some modules support additional states.
371
372.. _group_by_roles:
373
374Group By Roles
375++++++++++++++
376
377We're somewhat repeating ourselves with this tip, but it's worth repeating. A system can be in multiple groups.  See :ref:`intro_inventory` and :ref:`intro_patterns`.   Having groups named after things like
378*webservers* and *dbservers* is repeated in the examples because it's a very powerful concept.
379
380This allows playbooks to target machines based on role, as well as to assign role specific variables
381using the group variable system.
382
383See :ref:`playbooks_reuse_roles`.
384
385.. _os_variance:
386
387Operating System and Distribution Variance
388++++++++++++++++++++++++++++++++++++++++++
389
390When dealing with a parameter that is different between two different operating systems, a great way to handle this is
391by using the group_by module.
392
393This makes a dynamic group of hosts matching certain criteria, even if that group is not defined in the inventory file::
394
395   ---
396
397    - name: talk to all hosts just so we can learn about them
398      hosts: all
399      tasks:
400        - name: Classify hosts depending on their OS distribution
401          group_by:
402            key: os_{{ ansible_facts['distribution'] }}
403
404    # now just on the CentOS hosts...
405
406    - hosts: os_CentOS
407      gather_facts: False
408      tasks:
409        - # tasks that only happen on CentOS go here
410
411This will throw all systems into a dynamic group based on the operating system name.
412
413If group-specific settings are needed, this can also be done. For example::
414
415    ---
416    # file: group_vars/all
417    asdf: 10
418
419    ---
420    # file: group_vars/os_CentOS
421    asdf: 42
422
423In the above example, CentOS machines get the value of '42' for asdf, but other machines get '10'.
424This can be used not only to set variables, but also to apply certain roles to only certain systems.
425
426Alternatively, if only variables are needed::
427
428    - hosts: all
429      tasks:
430        - name: Set OS distribution dependent variables
431          include_vars: "os_{{ ansible_facts['distribution'] }}.yml"
432        - debug:
433            var: asdf
434
435This will pull in variables based on the OS name.
436
437.. _ship_modules_with_playbooks:
438
439Bundling Ansible Modules With Playbooks
440+++++++++++++++++++++++++++++++++++++++
441
442If a playbook has a :file:`./library` directory relative to its YAML file, this directory can be used to add ansible modules that will
443automatically be in the ansible module path.  This is a great way to keep modules that go with a playbook together.  This is shown
444in the directory structure example at the start of this section.
445
446.. _whitespace:
447
448Whitespace and Comments
449+++++++++++++++++++++++
450
451Generous use of whitespace to break things up, and use of comments (which start with '#'), is encouraged.
452
453.. _name_tasks:
454
455Always Name Tasks
456+++++++++++++++++
457
458It is possible to leave off the 'name' for a given task, though it is recommended to provide a description
459about why something is being done instead.  This name is shown when the playbook is run.
460
461.. _keep_it_simple:
462
463Keep It Simple
464++++++++++++++
465
466When you can do something simply, do something simply.  Do not reach
467to use every feature of Ansible together, all at once.  Use what works
468for you.  For example, you will probably not need ``vars``,
469``vars_files``, ``vars_prompt`` and ``--extra-vars`` all at once,
470while also using an external inventory file.
471
472If something feels complicated, it probably is, and may be a good opportunity to simplify things.
473
474.. _version_control:
475
476Version Control
477+++++++++++++++
478
479Use version control.  Keep your playbooks and inventory file in git
480(or another version control system), and commit when you make changes
481to them.  This way you have an audit trail describing when and why you
482changed the rules that are automating your infrastructure.
483
484.. _best_practices_for_variables_and_vaults:
485
486Variables and Vaults
487++++++++++++++++++++++++++++++++++++++++
488
489For general maintenance, it is often easier to use ``grep``, or similar tools, to find variables in your Ansible setup. Since vaults obscure these variables, it is best to work with a layer of indirection. When running a playbook, Ansible finds the variables in the unencrypted file and all sensitive variables come from the encrypted file.
490
491A best practice approach for this is to start with a ``group_vars/`` subdirectory named after the group. Inside of this subdirectory, create two files named ``vars`` and ``vault``. Inside of the ``vars`` file, define all of the variables needed, including any sensitive ones. Next, copy all of the sensitive variables over to the ``vault`` file and prefix these variables with ``vault_``. You should adjust the variables in the ``vars`` file to point to the matching ``vault_`` variables using jinja2 syntax, and ensure that the ``vault`` file is vault encrypted.
492
493This best practice has no limit on the amount of variable and vault files or their names.
494
495
496.. seealso::
497
498   :ref:`yaml_syntax`
499       Learn about YAML syntax
500   :ref:`working_with_playbooks`
501       Review the basic playbook features
502   :ref:`all_modules`
503       Learn about available modules
504   :ref:`developing_modules`
505       Learn how to extend Ansible by writing your own modules
506   :ref:`intro_patterns`
507       Learn about how to select hosts
508   `GitHub examples directory <https://github.com/ansible/ansible-examples>`_
509       Complete playbook files from the github project source
510   `Mailing List <https://groups.google.com/group/ansible-project>`_
511       Questions? Help? Ideas?  Stop by the list on Google Groups
512