Install Ansible and Write Your First Playbook: Inventory, Modules and Roles

· 2 min read · Ansible Tutorials

What you will build #

Ansible connects to servers over SSH and applies a desired state described in YAML. You need nothing installed on the servers except Python and SSH. In this tutorial you install Ansible, write an inventory, run a command on all hosts and write a playbook that installs and configures NGINX.

For test servers, create three VMs with Vagrant and VirtualBox or use EC2 instances created with Terraform.

Install #

On the control machine (your laptop or a bastion). pipx keeps Ansible isolated:

sudo apt update && sudo apt install -y pipx
pipx ensurepath
pipx install --include-deps ansible
ansible --version

On macOS: brew install ansible. Windows is supported only as the control node through WSL 2.

Inventory #

The inventory lists the hosts, grouped:

inventory.ini
[web]
node1 ansible_host=192.168.56.11
node2 ansible_host=192.168.56.12

[db]
node3 ansible_host=192.168.56.13

[all:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=~/.ssh/id_ed25519

For cloud servers that change all the time, see dynamic inventory. Authentication uses SSH keys, as in public key authentication.

Ad hoc commands #

ansible all -i inventory.ini -m ping
ansible web -i inventory.ini -m command -a "uptime"
ansible web -i inventory.ini -b -m apt -a "name=htop state=present update_cache=true"

-m selects the module, -a its arguments and -b (become) uses sudo.

Your first playbook #

web.yml
- name: Configure web servers
  hosts: web
  become: true
  vars:
    site_title: "Hello from Ansible"

  tasks:
    - name: Install NGINX
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Publish the home page
      ansible.builtin.template:
        src: index.html.j2
        dest: /var/www/html/index.html
        mode: "0644"
      notify: Reload NGINX

    - name: Make sure NGINX is running and enabled
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Reload NGINX
      ansible.builtin.service:
        name: nginx
        state: reloaded
templates/index.html.j2
<h1>{{ site_title }}</h1>
<p>Served by {{ inventory_hostname }}</p>

Run it:

ansible-playbook -i inventory.ini web.yml

The result shows changed for what Ansible modified. Run it a second time: everything is ok and nothing changes. That is idempotence, the main property of a good playbook. Use --check --diff to preview the changes without applying them.

Handlers, variables and conditions #

  • A handler runs once at the end, and only if a task notified it (the reload happens only when the page changed).
  • Variables come from the playbook, the inventory (group_vars/ and host_vars/) or -e name=value.
  • when: ansible_facts['os_family'] == "Debian" restricts a task. loop: repeats it.
  • Encrypt secrets with ansible-vault encrypt_string.

Move it to a role #

When a playbook grows, split it into a role:

ansible-galaxy init roles/nginx

Put the tasks in roles/nginx/tasks/main.yml, the template in roles/nginx/templates/, the handler in roles/nginx/handlers/main.yml and the defaults in roles/nginx/defaults/main.yml. Then the playbook becomes:

- hosts: web
  become: true
  roles:
    - nginx

Follow the roles best practices and the playbook structure, and keep several environments with separate inventories.

Next steps #

Build a complete Kubernetes cluster with the Ansible and Vagrant tutorial.

#Ansible #Automation