Ansible Best Practices

ansible logo

This is a conversion from a presentation/pdf by Tim Appnel.

I attached a copy here too.

Roles and Modules

Complexity Kills Productivity

That's not just a marketing slogan. We really mean it and believe that. We strive to reduce complexity in how we've designed Ansible tools and encourage you to do the same. Strive for simplification in what you automate.

Optimize for Readbility

If done properly, it can be the documentation of your workflow automation.

Think Declaratively

Ansible is a desired state engine by design. If you're trying to "write code" in your plays and roles, you're setting yourself up for failure. Our YAML-based playbooks were never meant to be for programming.

Roles + Modules

Use the right tool for the job:

Roles Modules
Self-contained portable units of Ansible automation Small programs that perform actions on remote hosts or on their behalf
Expressed in YAML and bundled with associated assets Expressed as code
- i.e. Python, PowerShell
Decoupled from assumptions made by plays Called by an Ansible tas
- Modules do all of the heavy lifting in Ansible

Roles

Roles are Ansible Content

The same best practices for your plays still apply:

  • Use native YAML syntax
  • Version control your Ansible content
  • Use command modules sparingly
  • Always seek out a module first
  • Name your plays, blocks and tasks
  • Use human meaningful names with variables, hosts, etc.
  • Clean up your debugging messages

Role Design

Keep the purpose and function of a role self-contained and focused to do one thing well

  • Think about the full life-cycle of a service, microservice or container — not a whole stack or environment
  • Keep provisioning separate from configuration and app deployment
  • Roles are not classes or object or libraries - those are programming constructs
  • Keep roles loosely-coupled - limit hard dependencies on other roles or external variables

EXHIBIT A

# blackbox_role_playbook.yml
---
- hosts: all
  roles:
  - umbrella_corp_stack

EXHIBIT B

# componentized_roles_playbook.yml
---
- hosts: localhost
  roles:
  - azure_provisioner
- hosts: all
  roles:
  - system_security
- hosts: webservers
  roles:
  - python_common
  - python_django
  - nginx_uwsgi
  - racoon_app
- hosts: databases
  roles:
  - pgsql-replication

Maximize your role design for portability and reuse

  • Use ansible-galaxy to install your roles
  • Use a roles files (i.e. requirements.yml) to manifest your project roles
  • When using a shared role always declare a specific version such as a tag or commit
# requirements.yml
---
- src: nginxinc.nginx
  version: 0.8.0
- src: samdoran.pgsql-replication
  version: b5013e6
- src: geerlingguy.firewall
  version: 2.4.

Role Usability

Roles should run with as few, if any, parameter variables as possible

  • Practice convention over configuration
  • Provide sane defaults
  • Use variable parameters to modify default behaviour
  • Easier to develop, test and use quickly & securely
  • A role should always be more than a single task file

EXHIBIT A

# defaults_no_playbook.yml
---
- hosts: webservers
  roles:
- role: apache_simple
  apache_http_port: 80
  apache_doc_root: /var/www/html
  apache_user: apache
  apache_group: apache
- role: apache_simple
  apache_http_port: 8080
  apache_doc_root: /www/example.com
  apache_user: apache
  apache_group: apache

EXHIBIT B

# defaults_yes_playbook.yml
---
- hosts: webservers
  roles:
- role: apache_simple
- role: apache_simple
  apache_http_port: 8080
  apache_doc_root: /www/example.com
# default/main.yml
---
apache_http_port: 80
apache_doc_root: /var/www/html
apache_user: apache
apache_group: apache

Use variables in your roles appropriately

  • defaults/ are easy to override and most commonly used to modify behavior
    • i.e. port number or default user
      # default/main.yml
      ---
      apache_http_port: 80
      apache_doc_root: /var/www/html
      apache_user: apache
      apache_group: apache
  • vars/ are used by the role and not likely to be changed
    • i.e. a list of packages, lookup table of machine images by region
      
      # vars/main.yml
      ---
      apache_packages:
      redhat:
    • httpd
    • mod_wsgi debian:
    • apache2
    • libapache2-mod-wsgi

Automate the testing of your roles

Use molecule, a testing framework designed to aid in the development and testing of Ansible Roles.

Initially developed by the community, led by John Dewey of Cisco, and adopted by Red Hat as an official Ansible project.

https://github.com/ansible/molecule

Use ansible-lint, a command-line static analysis tool that checks playbooks and roles for identifying behaviour that could be improved.

Initially developed by Will Thames and recently adopted by Red Hat as an official Ansible project.

https://github.com/ansible/ansible-lint

HINT: ansible-lint can be run as part of your Molecule test runs.

Role Readability

Still using command modules a lot?

- name: check cert
  shell: certify --list --name={{ cert_name }} --cert_store={{ cert_store }} | grep "{{ cert_name }}"
  register: check_output
- name: create cert
  command: certify --create --user=chris --name={{ cert_name }} --cert_store={{ cert_store }}
  when: check_output.stdout.find(cert_name) != -1
  register: create_output
- name: sign cert
  command: certify --sign --name={{ cert_name }} --cert_store={{ cert_store }}
  when: create_output.stdout.find("created") != -1

Develop your own module

- name: create and sign cert
  certify:
    state: present
    sign: yes
    user: chris
    name: "{{ cert_name }}"
    cert_store: "{{ cert_store }}

Modules

Module Design

Good modules are user-centric

  • Modules are how Ansible balances simple and powerful
  • They implement common automation tasks for a user
  • They make easy tasks easier and complicated tasks possible
  • They abstract users from having to know the details to get things done
  • They are not one-to-one mapping of an API or command line tool interface
    • This is why you should not auto-generate your modules
  • They are not monolithic does everything modules that are hard to understand and complicated to use correctly

Module Implementation

Making the powerful simple starts with the implementation

  • No side-effects with multiple runs
  • Err on the side of safety
    • i.e. use a temporary file and atomic_move when writing to a file
  • Fail fast -- immediately detect and report failure conditions
  • Support check mode
  • Minimal use of dependencies

Module Interface

Modules should provide a predictable user interface

  • Think desired state, think declaratively
  • Avoid action or command parameters
  • Keep parameters focused and narrowly defined - refrain from parameters that take complex data structures
  • Parameter names should be in lowercase and use underscores:
    • update_cache # YES
    • UpdateCache # NO
    • updateCache # NO
  • Normalize common parameter names with other modules such as:
    • name
    • state
    • dest
    • src
    • path
    • username
    • password

Modules in the wild

For your consideration...

  • kubernetes
    • monolithic and requires expert knowledge of k8s
  • ansible-kubernetes-modules
    • fine grained API mapping that is autogenerated
  • k8s
    • better implementation but complex parameters abound and expert knowledge still required
  • k8s_scale
    • more focused on a specific task — more of this please

Module Responses

Provide informative and consistent responses

  • Be consistent in what you return
  • Make response data reusable by a play or role
  • Return only relevant output — no logs files please
  • Accurately report changed status
  • Support diff mode if applicable - and return the diff conditionally

Module Exception Handling

Handle errors gracefully and predictably

  • Apply defensive programming
  • Fail fast — validate upfront and use the built-in argument spec function
  • Fail predictably and informatively when errors happen
  • Avoid catch all exceptions

Module Implementation

Don't reinvent the weheel

  • Make use of module_utils/ -- they’re your friends
    • basic.py
    • api.py
    • facts/
    • urls.py
    • six.py
    • noteworthy others: ec2.py, docker.py, database.py, mysql.py, powershell.ps1 — and many many more!

Module Documentation

Documentation is a requirement

  • Examples should include the most common and real world usage
  • Examples should be in native YAML syntax
  • Return responses must be included and described
  • Document your dependencies in the requirements section

Module Testing

Test before you commit and push your code

  • Utilize the testing tools in ansible/hacking/
    • test-module
    • ansible-test sanity
  • Create roles and playbooks and to test and verify all your documented examples
    • molecule
  • Test locally - not with the CI/CD system

More Modules in the wild

For your consideration...

  • sysctl
    • a master class in writing a “best practice” module
  • ping
    • the hello world of Ansible
  • cron
    • module implementing an interface to a command line tool
  • get_url
    • module implementing an interface to a python library

Module and Action Plugins

Sometimes modules need something more

  • Local controller execution of a module entirely possible or required...
  • Special setup requirements before the module is dispatched to the host...
  • Need to supplement a module with the services of another core module such as copy and a role won’t cut it...

An Action Plugin executes on the controller and perform logic before dispatching a module

More resources

Ansible Developers Guide

http://docs.ansible.com/ansible/devel/dev_guide/index.html