Skip to the content.

Ansible Best practices

ansible logo

Directory Layout

At the beginning: put everything in one Git repository. I suggest this structure.

inventories/
    group_vars/
        group1.yml              <--- here we assign variables to particular groups
        group2/                 <--- if lot of variables you can split in yaml files (name of folder = name of group )
            stuffs.yml
            others_stuffs.yml
    host_vars/
        hostname1.yml           <--- if a host need specific variables, put them here
        hostname2.yml
    staging
    production

playbooks/
    site.yml
    webservers.yml
    dbservers.yml

roles/
    common/
    monitoring/
    fooapp/

If you struggle with a mono repository you can create one Git repository per role or use collections.


Optimize your Ansible content for readability

Name all tasks

Make your playbooks and roles more readable. Always - name: yours Playbooks and Tasks

- name: "Create  systemd service file"
  template:
    src: "springboot_service.j2"
    dest: "/lib/systemd/system/.service"
  notify: reload systemd service
  with_items: ""
  become: yes

Use Native YAML Syntax

(Bad) Here is an example of a task using the key=value shorthand

- name: install telegraf
  apt:
    name: "telegraf-" state=present
           update_cache=yes disable_gpg_check=yes enablerepo=telegraf
  notify: restart telegraf

(Good) Now the same task using native YAML syntax

- name: install telegraf
  apt:
    name: "telegraf-"
    state: present
    update_cache: yes
    disable_gpg_check: yes
    enablerepo: telegraf
  notify: restart telegraf

Variables

Proper variable names can make plays more readable and avoid variable name conflicts between roles.

Prefix role varaibles

Use prefixes and human meaningful names with variables and Role variables should be prefixed with the role name

apache_max_keepalive: 25
apache_port: 80
tomcat_port: 8080

You can also compose variables

wildfly_mode: standalone
wildfly_home_path: "/usr/local/wildfly"
wildfly_conf_path: "//configuration"

Varaibles advice

varaibles filters / plugins

You can use jinja2 builtin filters in tasks or additionals ansible filters



mode: 
  or  192.0.2.1/24

upper, lower, parse_cli, password_hash, regex_search, quote, join  ...and more

Lookup plugins

Allow Ansible to access data from outside sources (password files, passwordstore, ini, csv, dns, redis,…)

Example for password generation

wildfly_management_users:
  - name: admin
    password: ''

use facts

List all facts for one host :

$ ansible -m setup hostname

Roles

One role –> one goal

Avoid tasks within a role that are not related to each others

Use galaxy command for create roles structure

$ ansible-galaxy init rolename

role directory layout

    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies

Add meta info and use roles dependencies

galaxy_info:
  author: Demis Rizzotto
  description: Setup and configure Wildfly runtime
  company: acme corp
  license: BSD

  min_ansible_version: 2.1

  galaxy_tags:
    - wildfly

dependencies:
  - { role: java-jdk, java_jdk_version: oracle-java8-jdk }
  - { role: company-apt-repo }
  - role: debops.secret

You can also use roles dependencies.

other advices


Tasks

Idempotency done right.

Avoid skipping items.

Use modules before run/shell commands

If no module does what you want, you can create your own

if no choice use changed_when

- name: Run drush to finish setting it up
  command: ""
  register: drush_result
  changed_when: "'Execute a drush command' not in drush_result.stdout"
  become: no

Tests

Test your roles

$ ansible-playbook foo.yml -i staging --check --diff --limit foo.example.com

Molecule

automate development and testing of Ansible roles. It integrates with Docker, Vagrant, and OpenStack to run roles in a virtualized environment and works with popular infra testing tools like ServerSpec and TestInfra.


Tools

Search roles in Ansible galaxy Some editor have syntax higthlight and linters.

Personally I use VScode with plugins :

ansible-lint

checks playbooks for practices and behaviour that could potentially be improved

[ANSIBLE0013] Use shell only when shell functionality is required
/ansible/roles/common/tasks/admins.yml:34
Task/Handler: Check if infrastructuremanagers user exists

[ANSIBLE0011] All tasks should be named
/ansible/roles/common/tasks/exim4.yml:2
Task/Handler: stat __file__=/ansible/roles/common/tasks/exim4.yml __line__=2 path=/etc/postfix/main.cf

ansible-doc

is very useful for finding the syntax of a module without having to look it up online

ansible-doc mysql_user

Use verstion control (git) (.gitignore : *.retry)

and also for test syntax before merge code


ansible-vault

Feature of ansible that allows keeping sensitive data such as passwords or keys in encrypted files.

$ ansible-vault create foo.yml
$ ansible-vault edit foo.yml

$ ansible-vault encrypt foo.yml bar.yml baz.yml # encrypt existing file
$ ansible-vault decrypt foo.yml bar.yml baz.yml # remove encrypt
$ ansible-vault view foo.yml bar.yml baz.yml

The file is complete encrypted in AES256

ecrypted variables

Use encrypt_string to create encrypted variables to embed in yaml

Example: Create encrypted variable

$ ansible-vault encrypt_string --vault-id prod@prod-password 'supersecret' --name 'password'

add in vars.yml

servername: server1
username: "foo.bar"
password: !vault |
  $ANSIBLE_VAULT;1.2;AES256;prod
  36613430616336633961363865396264373262646165323063303830346566346266656262656264
  6566663639316564613766343234353432613738303833660a636433353630623265386564313635
  62373163303666643435366465633863643261336566613262653939323062383464336333653866
  6436616431383833650a326562343162313764353464333235363234623064666539383338326232
  3235

Test

$ ansible-playbook --vault-id prod@prod-password debug.yml
TASK [print secure variable] >  "msg": "prod_password : supersecret"
TASK [print standard variable] > "msg": "username : foo.bar"

encrypt string available since Ansible 2.3. vault-id since 2.4 > you can’t edit or decrypt file with vault cli for the moment


Inventories


Execution

  --step
  --check
  --diff
  --start-at-task
  --list-tasks
  --list-tags
  --list-hosts
  --syntax-check