19. Best practices

  • keep it simple

  • if something feels complicated, then we are probably doing it the wrong way

  • think about, what you would like to achieve and don’t think about, what you’ve done so far...

  • version pinning

    • yum -> name: 'docker-{{ docker_version }}'
    • apt -> name: 'ntp={{ ntp_version }}'
    • application deployment with SCM e.g git -> use tags as version:
    • use version in requirements.yml for Ansible galaxy
  • keep your inventory file and variables in a git repo (or other version control). this is an excellent way to track changes to your inventory and variables.

  • If you write the same tasks in multiple playbooks, create a role.

  • use the prefix <rolename>_ in role variables

  • roles have defaults variables, that can be overwritten by group_vars

  • all is for global variables.

  • create a README.md for every role and describe the usage and variables -> maybe use the ansible-galaxy init test command and take that readme

  • share your roles for other projects (internal via SCM/HTTP or external via Ansible Galaxy)

  • try to avoid tasks with the command or shell module -> Ansible modules are designed to be safely repeatable -> Idempotency!

    • Use the file module rather than command with rm, mkdir, rmdir etc
    • Other modules that replace shell commands include synchronize, unarchive, git, hg, svn
    • if you use command/shell module, have a look at the creates parameter (only run, if the file is not in place)
    • command vs shell -> https://blog.confirm.ch/ansible-modules-shell-vs-command/
  • Idempotency / Error Handling

  • use verbosity with the Debug module for messages

- debug:
    msg: "This only displays with ansible-playbook -vv+"
    verbosity: 2
  • Testing Strategies / ansible-ci / ansible-lint

  • create tests with tags and modules like wait_for

  • don’t use the short forms of tasks / lists / dictionaries in YAML -> git diff, human readable

  • with git -> use tags instead of branches as version for SCM checkouts

  • useful tags of tasks

    • configuration
    • packages
    • installation
    • service
    • scm
    • deployment
    • critical
    • notification
    • debug
  • if your group_vars / host_vars file is to big -> use folders with sub-files

  • only parameterize variables you also need in a template or role

  • use fixed versions for role dependencies

  • there are ways to automate the role installation via SCM or ansible-galaxy

    • a task with local_action in a playbook
    • git submodule
  • create a “layer of indirection” for ansible vault variables. See Ansible Docs

  • Ansible Porting Guide

  • Ansible Release and Maintenance Page

  • You can learn something, if you check out other roles / examples from the Ansible example repo, Ansible Galaxy or the Debops Project

  • you can use force: no to transfer a file only, if the destination does not exist (initial configuration). it’s available in some modules like copy / template

  • have a look at the callback plugins

# actionable callback plugin, to only see changed/failed tasks
ANSIBLE_STDOUT_CALLBACK=actionable ansible-playbook playbooks/config_backup.yml -l backup1.pvt.confirm.ch --tags iscsi

# profile_tasks, adds time information to tasks
ANSIBLE_STDOUT_CALLBACK=profile_tasks ansible-playbook playbooks/config_backup.yml -l backup1.pvt.confirm.ch --tags iscsi

# profile_roles, adds timing information to roles
ANSIBLE_STDOUT_CALLBACK=profile_roles ansible-playbook playbooks/config_backup.yml -l backup1.pvt.confirm.ch --tags iscsi

# yaml - yaml-ized Ansible screen output (since Ansible v2.5)
ANSIBLE_STDOUT_CALLBACK=yaml ansible-playbook playbooks/config_backup.yml -l backup1.pvt.confirm.ch --tags iscsi
  • decide, if you permanently set the become value in the configuration, or per playbook or per task. But be aware of the file owner/group-ship.
  • have a look at ssh key signing
  • have a look at ansible-console
  • make sure, the control machine(s) uses the same Ansible version. Maybe you can achieve this with Python Virtualenvs.
  • have a look at meta “module” (flush_handlers, refresh_inventory, reset_connection, ...)
ansible-console

# run a command
ls -lh
whoami

# run a module
package name=tree state=latest
  • use validation for configuration changes (if possible)
validate: '/usr/sbin/apache2ctl -f %s -t'
validate: '/usr/sbin/sshd -t -f %s'
validate: 'visudo -cf %s'
validate: 'grep ntp %s'

19.1. Tuning

19.2. Styling

  • use spaces around jinja2 variable names -> {{ var }} and not {{var}}
  • All yaml files should use 2 space indents and end with .yml
  • choose your quoting style (single path: '/etc/some.conf' / double path: "/etc/some.conf")
  • decide to quote not needed stuff like path: /etc/some.conf

19.3. Useful commands

# save facts from all hosts
ansible -m setup --tree /tmp/setup all
ls -l /tmp/setup

# show all variables
ansible -m debug -a "msg='{{ vars }}'" localhost,

19.4. Useful snippets

# fileglob
- name: Copy each file over that matches the given pattern
  copy:
    src: "{{ item }}"
    dest: "/etc/ssl/ca"
    owner: "root"
    mode: 0600
  with_fileglob:
    - "/playbooks/files/fooapp/*.pem"

{% if inventory_hostname in groups['dbservers'] %}
-A INPUT -p tcp  --dport 3306 -j  ACCEPT
{% endif %}

19.5. Useful modules

Have a look at this modules.