Ansible 101


2014-05-02 · 4 min read

Ansible is an open-source software for infrastructure management i.e. configuring and managing computers. Ansible is agent-less: a single controlling machine manages nodes over SSH connection; those nodes do not require additional software nor any background daemons. It has minimal software requirements i.e. Python 2.4 or later. It uses YAML as a description language which makes it self-documenting and easier to read than custom DSLs (additional layer of abstraction). In result, it is easier to use than similar, but older tools such as Puppet, Chef or Saltstack.

Install

You need Python 2.4 or later. The best way to install is through pip package manager.

pip install ansible

Hands-on

Make a directory

mkdir foo && cd foo

Create an inventory file

# ~/foo/hosts
[local]
127.0.0.1

Create a playbook

# ~/foo/playbook.yml

- hosts: local
  user: root
  tasks:
    - name: install curl
      apt: pkg=curl state=present

    - name: write to a file
      shell: echo hello > /tmp/foo

Run the playbook (Debian/Ubuntu specific)

ansible-playbook -i hosts playbook.yml

Concepts

Playbooks are collections of steps, i.e. configuration policies to be applied to defined groups of hosts, written in YAML and executed in order they are written.

Playbooks are declarative and idempotent while shell scripts are imperative. It’s difficult to replay a shell script because it is very hard to write idempotent commands. Contrarily, Ansible only performs the tasks in the playbook that need to be performed: if the condition is already satisfied, there's nothing to do.

---
- hosts: localhost
  connection: local
  vars:
    - foo: Hello
  tasks:
    - command: echo “{{foo}}”

An inventory defines a list of hosts; they can be defined on a system level with /etc/ansible/hosts or with the environment variable ANSIBLE_HOSTS=<path-to-inventory>, or per invocation with -i <path-to-inventory> parameter.

An inventory file can define host groups using square brackets. If you declare a host in the inventory by hostname, it must be resolvable either in your /etc/hosts file, or by a DNS lookup. E.g.

[webservers]
192.168.100.1 set_hostname=foo # how to set per-host variables.

[dbservers]
dbserver-1

Modules control system resources: services, files, commands e.g. apt/yum, file/copy, command/shell/script, service, ec2, etc.

---
- name: Install Python PIP & boto library
  user: ubuntu
  sudo: true
  hosts: all
  tasks:
    - name: Install python-pip
      apt: pkg=python-pip state=latest

    - name: Install boto w/ PIP
      pip: name=boto state=latest

Usage

Task Mode

In task mode, an Ansible module command is executed on a target node. A module is specified with -m parameter along with its arguments specified with -a parameter.

ansible web -m copy -a “src=script.sh dest=/usr/bin/script owner=root group=root mode=0755”
ansible web -m service -a “name=nginx state=restarted”

Groups can be combined

  • A:B designates the union of groups A and B
  • A:&B designates the intersection of groups A and B
  • A:!B designates the difference, all from A without those in B
ansible -m ping web
ansible -m ping web:db
ansible -m ping web:&db
ansible -m ping web:!db

Playbook Mode

In playbook mode, commands are executed according to specified playbook. When Ansible runs a playbook, it will execute the tasks in the order they appear in that playbook - the process is meant to be repeated without differences.

For better organisation, a playbook should be composed of smaller, reusable parts. Those parts can be combined into a playbook either using include statement or with roles.

An include statement specifies a file with plays to include into that playbook.

# site.yml
---
- name: Install Python PIP & boto library
  user: ubuntu
  sudo: true
  hosts: all
  tasks:
    - include: install-python.yml
# install-python.yml
- name: install python-pip
  apt: pkg=python-pip state=latest

- name: install boot with PIP
  pip: name=boto state=latest

Roles are built around include statement (file references handling and opinionated structure) and provide the best way to organise playbooks. They encapsulate configuration/management steps related to a single thing. In general, a role consists of the following subdirectories, "files", “vars”, "handlers", "meta", "tasks" and "templates”.

# site.yml
---
- name: Install Redis
  user: ubuntu
  sudo: true
  hosts: all
  roles:
    - redis
# roles/redis/tasks/main.yml
---
 - name: Add the Redis PPA
   apt_repository: repo='ppa:rwky/redis' update_cache=yes
 - name: Install Redis from PPA
   apt: pkg=redis-server state=installed
 - name: Start Redis
   service: name=redis state=started

Loop-like abstraction

with_item allows to pass a list of elements into a module.

- name: install default packages
  apt: pkg={{ item }} state=installed
  with_items:
    - curl
    - vim
    - htop

Private key

A private key used to log in into nodes can be specified with —private-key parameter.

ansible-playbook acme.yml --private-key=/home/smith/.ssh/acme.priv