The idea is to always update this post with important concepts I use in Ansible, basically for future reference.

Hosts file

filename: hosts

This file is responsible for put together information about your servers. In this file you will have information about the location of the servers (ip) how to access them (ssh user & ssh key). You also give your servers names.

Example:

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

As you can see, as you get more servers inside ansible, the verbosity of this file can be enormous. You likely want to have a ansible.cfg file which will help you to set as default some of this data.

Ansible config file

filename: ansible.cfg

You can define this file in different directories. They have a precedence load order:

1. ANSIBLE_CONFIG environment variable
2. ./ansible.cfg
3. ~/.ansible.cfg
4. /etc/ansible/ansible.cfg

I usually want the ansible.cfg inside my project.

[defaults]
hostfile = hosts
remote_user = vagrant
private_key_file = .vagrant/machines/default/virtualbox/private_key

Then you can reduce the definitions of your hosts file to something like:

production_server  ansible_ssh_host=xxx.yyy.zzz.kkk
staging_server     ansible_ssh_host=xxx.yyy.zzz.kkk
development_server ansible_ssh_host=xxx.yyy.zzz.kkk

Grouping servers

You can create group of servers:

webserver1 ansible_ssh_host=xxx.yyy.zzz.kkk
webserver2 ansible_ssh_host=xxx.yyy.zzz.kkk
db ansible_ssh_host=xxx.yyy.zzz.kkk

[web]
webserver1
webserver2

[db]
webserver1
webserver2
db

Then you can execute ansible commands in a specific server or group:

ansible web -a "date"

Host and group variables

  • filename: host_vars/{{host_name}}.
  • filename: group_vars/{{group_name}}.

Ex:

  • host_vars/webserver1
  • host_vars/webserver2
  • group_vars/production
  • group_vars/db,
  • group_vars/production/mysql

These files should be either in the playbook directory or in the same directory as the inventory file (hosts). You can define variables inside these files as .ini or .yaml format. My preferred is yaml because it is more flexible: supports booleans, strings, lists and dictionaries.

Playbooks

A playbook is a list of tasks to be executed on hosts. They use the YAML format and they are executed by using the command line tool ansible-playbook.

Executing a playbook

Here’s a minimalist playbook (filename: helloworld.yml):

- hosts: ubuntu
  tasks:
    - debug: msg="Hello World!"

To execute this playbook:

ansible-playbook helloworld.yml

Variables

Playbook variables

Inside a playbook, just define a dictionary called vars. Example:

- name: defining variables
    vars:
      greeting: "hello"
    tasks:
      - name: output a message
        debug: msg="{{ greeting }}"

Registering variables

Register the output of a task to a variable

- name: capture output
  command: whoami
  register: output
- debug: msg="{{ output.stdout }}"

Command line variables

ansible-playbook example.yml -e my_name=arthur
ansible-playbook example.yml -e 'my_name="my name is arthur"'

Built-in variables

hostvars, inventory_hostname, group_names, groups, play_hosts, ansible_version

- name: Checking built-in variables
  hosts: server1
  tasks:
    - debug: msg="{{ inventory_hostname }}"
    - debug: msg="{{ group_names }}"
    - debug: msg="{{ groups }}"
    - debug: msg="{{ play_hosts }}"
    - debug: msg="{{ ansible_version }}"
Hostvars - getting global facts from other hosts.

If a host needs to get any information from the others hosts, this can be done through the hostvars builtin variable.

- debug: msg="{{ hostvars['server1'].ansible_eth1.ipv4.address }}"

Options

Serial

Serial limits the number of concurrent executing hosts. On the case below, it’ll execute on 2 hosts at a time.

- name: Execute only 2 hosts in parallel
  hosts: ubuntu
  serial: 2
  tasks:
    - debug: msg="{{ ansible_version }}"

Running only once

Sometimes a task should run just once, even if you’re executing tasks in multiple hosts.

- name: execute migrations
  run_once: true
  command: /project/execute_migrations

Filters

Filters added of register variables can be used to detect the state of a command or module executed.

- shell: /usr/bin/foo
  register: result
  ignore_errors: True

- debug: msg="it failed"
  when: result|failed

- debug: msg="it changed"
  when: result|changed

- debug: msg="it succeeded"
  when: result|success

- debug: msg="it was skipped"
  when: result|skipped

Code above was copied from the filters documentation.

Catching Errors

Register the result to a variable, ignore errors in case it happens and then display a debug message in case an error happened.

- hosts: ubuntu
  tasks:
  - name: Run inexistent program to emulate error
    command: /opt/inexistent
    register: err
    ignore_errors: true

  - debug: msg="Stop running the playbook - /opt/existent has failed -- {{ err.msg }}"
    failed_when: err|failed

Lookups

Ansible provide a way of accessing data on external resources.

file: lookup.yml

- hosts: all
  vars:
    ssh_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') | default('') }}"
    email: "fake@gmail.com"
  tasks:
    - debug: msg="{{ ssh_key  }}"
    - debug: msg="{{ lookup('pipe', 'git branch') }}"
    - debug: msg="{{ lookup('env', 'PATH') }}"
    - debug: msg="{{ lookup('template', './my_template.j2') }}"

file: my_template.j2

Sending an email to {{ email }}

Loops

Check out the ‘Loops’ docs.

Modules

Executing commands - docs

- command: whoami

Getting information about the server (or Facts)

ansible server1 -m setup
ansible server1 -m setup -a "filter=*ipv4*"

Vaults

Ansible has a way of dealing with sensible data, like passwords. You can encrypt files using the tool ansible-vault, most of the cases variable files which sensible data.

ansible-vault encrypt encrypted_vars.yml

ansible-playbook tasks.yml --ask-vault-pass
ansible-playbook tasks.yml --vault-password-file password.txt

ansible-vault view encrypted_vars.yml
ansible-vault edit encrypted_vars.yml
ansible-vault decrypt encrypted_vars.yml
ansible-vault rekey encrypted_vars.yml

Roles

Roles are a way of scaling Ansible up by splitting playbooks in different files. A role has at least a main task named main.yml. Optionally, It can also have different files with different responsibilities:

roles/role_name/tasks/main.yml    # main task
roles/role_name/files/            # files uploaded to the remote host
roles/role_name/templates/        # j2 templates
roles/role_name/handlers/main.yml # handlers
roles/role_name/vars/main.yml     # variables not to be overridden
roles/role_name/defaults/main.yml # default variables
roles/role_name/meta/main.yml     # dependencies

Importing roles

- hosts: all
  roles:
    - base
    - webserver
    - database

Dependencies

file: meta/main.yml

dependencies:
    - { role: web }
    - { role: db }

Ansible Galaxy

Ansible Galaxy - reuse and share ansible playbooks.

# Install & uninstall
ansible-galaxy install username.rolename
ansible-galaxy install username.role -p ./roles
ansible-galaxy remove username.rolename

# Scaffolding a new role
ansible-galaxy init rolename

# Search
ansible-galaxy search elasticsearch
ansible-galaxy info username.role_name

Trial n’ Error

List of stuff that I racked my brains, but once got working.

Create an new user with a password

user_password and user_password_salt are variables I keep inside a vault.

- name: Generating user's password
  command: "/usr/bin/python -c 'import crypt; print crypt.crypt(\"{{ user_password }}\", \"{{ user_password_salt }}\")'"
  register: encrypted_password

- name: Create application user
  user: name={{ user_name }} password={{ encrypted_password }} group={{ group_name }} groups="sudo" shell=/bin/bash home={{ user_home_path }}