Ansible

Introduction

  • push-based config management, also supports pull
  • copies a generated script to the managed host via ssh and then executes it
  • simple for small setups, powerful enough for complex situations
  • connects to several hosts in parallel (but largest scale only via pull mode)
  • master node only requires ansible, no other server (HTTP etc)
  • managed nodes need no client, only ssh and python
  • written in python, uses yaml playbooks and Jinja2 templates
  • extensible with modules

Further documentation

Commands

ansible all -m ping                         # test all hosts by invoking ping module
ansible some-host -m setup                  # display all gathered facts (system info)
ansible some-host -a uptime                 # run a given command
ansible some-host -s -a "tail some-file"    # run a given command with sudo
ansible some-host -s -m apt -a name=htop    # install given package using the apt module
ansible-playbook some-playbook.yml          # run a given playbook
ansible-playbook -C -D some-playbook.yml    # run in dry-run mode (--check) and show diff
ansible-playbook --step playbook.yml        # prompt before running each task
ansible-playbook --limit ahost playbook.yml # run playbook only on given host
ansible-playbook --syntax-check file.yml    # check the syntax of a given playbook
ansible-playbook --list-hosts playbook.yml  # list hosts affected by a given playbook
ansible-playbook --list-tasks playbook.yml  # list tasks defined in a given playbook
ansible-playbook --list-tags playbook.yml   # list tags defined in a given playbook
ansible -i someinv all --list-hosts         # list all hosts in given inventory
ansible-inventory -i someinv --list         # list hostnames, groups and variables of inventory
ansible-inventory -i someinv --graph        # show ascii graph of host groups
ansible-config dump                         # show ansible config paramaters and non-default values
ansible-doc -l                              # list ansible commands with a short description
ansible-doc some-module                     # read documentation of a given module
ansible <command> -vvvv                     # run command with verbose output for debugging
export ANSIBLE_KEEP_REMOTE_FILES=1          # don't delete remote script files in ~/.ansible/

Configuration

Minimal setup

inventory:

hostname.example.com

playbook.yml:

- hosts: all
  remote_user: root
  tasks:
  - name: install htop
    apt: name=htop state=present

Command to run playbook

ansible-playbook -i inventory playbook.yml

Typical folder structure

├── ansible.cfg
├── handlers/
├── host_vars/
├── group_vars/
├── inventory
├── roles/
│   └── webserver/
│       ├── defaults/
│       ├── files/
│       ├── handlers/
│       ├── tasks/
│       ├── templates/
│       └── vars/
└── playbook.yml

Variable precedence

-e arg > role > play > host > group > defaults

ansible.cfg

Example ansible.cfg file to configure ansible

[defaults]
hostfile = hosts
sudo_flags = -HE
retry_files_enabled = False
gathering = smart
hash_behaviour = merge

where the hash_behaviour setting enables merging of hash variables for instance from host_vars and group_vars.

In some cases you may want to add force_handlers = true to trigger handlers even if a failure occured on that host.

inventory

Example inventory file

vagrant1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=2222 ansible_ssh_user=vagrant ansible_ssh_private_key_file=/path_to_VM/.vagrant/machines/default/virtualbox/private_key

[server]                # group
vagrant1

[web]
vagrant1

[webserver:children]    # group of groups
web
server

Ansible also supports the concept of a dynamic inventory, i.e. an executable script that returns the hosts in JSON format.

Callbacks

Ansible offers various callback plugins.

ansible-doc -t callback -l      # show list of callback plugins
ansible-doc -t callback <name>  # show documentation of given callback

Default callback

The normal ansible output is generated by the ansible.builtin.default callback, which can be customized with env variables.

export ANSIBLE_DISPLAY_OK_HOSTS=no      # hide unchanged tasks
export ANSIBLE_DISPLAY_SKIPPED_HOSTS=no # hide skipped tasks

Runtime profiling

Some callbacks can be used to measure the runtime performance.

  • profile_tasks: show runtime of each tasks and summary with the list of slowest tasks
  • profile_roles: show summary with runtime of the roles
  • timer: show total playbook runtime

Enable the callbacks you need in the [default] section of your ansible.cfg.

[default]

callbacks_enabled = profile_tasks, profile_roles

Enabling the verbose debug output may shed some light on what exactly Ansible is doing that takes so much time.

export ANSIBLE_DEBUG=1

Playbook Debugger

Ansible can enter an interactive debugger shell if a playbook has an error.

export ANSIBLE_ENABLE_TASK_DEBUGGER=True

Debugger commands:

p host                      # print host
p result                    # print task result
p task                      # print task name
p task.args                 # print task arguments
p task_vars                 # print task variables
p task_vars['some_key']     # print given task variable
task_vars['some_key'] = 1   # update value of task variable (similarly args)
u                           # update task after modification
r                           # redo task
c                           # continue
q                           # quit

Retry files

Ansible can generate a .retry file with the hostnames that failed (or where offline) during a playbook run. You can then limit the next run to only those hosts.

export ANSIBLE_RETRY_FILES_ENABLED=1
ansible-playbook playbooks/play.yml
ansible-playbook playbooks/play.yml --limit @playbooks/play.retry

Force handlers

By default, if a task fails, ansible stops the further execution and skips running all handlers, even if they were notified earlier in the playbook by succeeding tasks. As explained under handlers and failure use the --force-handlers command-line option or force_handlers: True to change this behaviour.

Illustrating playbook:

- hosts: localhost
  force_handlers: True

  handlers:
  - name: handler1
    debug:
      msg: forced handler
  - name: handler2
    debug:
      msg: skipped handler

  tasks:
  - name: successful task
    debug:
      msg: successful
    changed_when: True  # otherwise `debug` reports no change and handlers are not triggered
    notify: handler1

  - name: failed task
    fail:
      msg: failed
    changed_when: True
    notify: handler2

  - name: skipped task
    debug:
      msg: skipped