ITcompressor IT — compressed and to the point
← Back to articles

Ansible

Ansible is a configuration and deployment automation tool: tasks are described as YAML playbooks and executed over SSH without agents on the target machines. The scenario of this article: an inventory with webservers/dbservers groups, and we set up nginx on them.

Installation#

The Ubuntu repository ships a badly lagging version — it is livelier to take the official PPA:

sudo apt update
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible

ansible --version

An alternative that gives the freshest releases: pipx install ansible (or pip in a virtual environment). Do not mix both methods on one machine.

Inventory#

The inventory describes hosts and their groups. A inventory.ini file next to the playbooks:

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.example.com
db2.example.com

[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_rsa

By default Ansible takes the inventory from ansible.cfg in the playbook directory:

# ansible.cfg
[defaults]
inventory = ./inventory.ini

otherwise every command call has to be decorated with -i inventory.ini.

First playbook#

A playbook is YAML with a list of tasks. File update.yml:

---
- name: Update system packages
  hosts: all
  become: yes
  tasks:
    - name: Refresh the package cache
      apt:
        update_cache: yes

    - name: Install the basic set
      apt:
        name:
          - nginx
          - git
          - curl
        state: present

Run it:

ansible-playbook update.yml

Ansible tasks are idempotent: rerunning a playbook does nothing redundant — modules compare the current state with the desired one and change only what diverges.

Modules#

A module is an atomic action. The most-used ones cover most tasks:

Module What it does
apt packages on Debian/Ubuntu
yum, dnf packages on the RHEL family
copy a file to a remote host
template a file from a Jinja2 template with variable substitution
file permissions, owners, directories
service start/stop/restart a service
user, group users and groups

Full module names carry the collection prefix: ansible.builtin.copy, ansible.builtin.service. Help on any of them: ansible-doc copy; documentation at docs.ansible.com.

Variables and handlers#

Inside a playbook — vars; in templates — substitution via {{ … }}. File nginx.conf.j2 next to the playbook:

server {
    listen {{ http_port }};
    root {{ document_root }};
}

File webserver.yml:

---
- name: Web server setup
  hosts: webservers
  become: yes
  vars:
    http_port: 80
    document_root: /var/www/html
  tasks:
    - name: nginx configuration from a template
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Restart nginx   # fires only if the file actually changed

  handlers:
    - name: Restart nginx
      service:
        name: nginx
        state: restarted

notify is a handler: it runs once at the end of the run and only when a task actually changed something. A run without changes does not restart services.

Roles#

A role is a reusable component with its own directory structure:

ansible-galaxy init roles/webserver
roles/webserver/
├── tasks/main.yml        # tasks of the role
├── handlers/main.yml
├── templates/            # nginx.conf.j2 — goes here
├── defaults/main.yml     # default variables
└── vars/main.yml

Use it in a playbook instead of listing tasks:

---
- name: Web servers
  hosts: webservers
  become: yes
  roles:
    - webserver

Ready-made community roles — Ansible Galaxy (ansible-galaxy install geerlingguy.nginx); put your own into roles/ next to the playbook.

Everyday commands#

# Check host reachability (ping module)
ansible all -m ping

# Only one group
ansible webservers -m ping

# One-off command on hosts
ansible all -a "uptime"

# Playbook with superuser on the target machines
ansible-playbook webserver.yml --become

# Show the task list without executing anything
ansible-playbook webserver.yml --list-tasks

# Dry run: changes are computed but not applied
ansible-playbook webserver.yml --check --diff

# Verbose output (v, -vv, -vvv — more verbose)
ansible-playbook webserver.yml -v

# Restrict the run to a single host
ansible-playbook webserver.yml --limit web1.example.com

Keep secrets (passwords, API keys) in encrypted variables: ansible-vault encrypt_string — and use them in a playbook as a regular variable; decryption asks for the vault password during the run.

← Back to articles