Comprehensive Ansible

Comprehensive Ansible
Photo by Mohammad Rahmani / Unsplash

This guide will show you how to use Ansible, starting from a single, simple playbook and progressing to a professional, role-based structure. We'll deploy common infrastructure, including Docker, WordPress, and n8n.

Prerequisites

  • A control node (your machine) with Ansible installed (sudo apt install ansible or pip install ansible).
  • One or more target nodes (e.g., Ubuntu Linux VMs) with SSH access configured (key-based authentication recommended).
  • Basic understanding of YAML syntax.

Part 1: The Basics - Inventory and Ad-Hoc Commands

Before writing playbooks, Ansible needs to know where to run them. This is defined in an inventory file.

1. Create an Inventory

Create a file named inventory.ini:

[webservers]
# Replace with your actual server IP or hostname
192.168.1.100

[webservers:vars]
# Tell Ansible which user to connect as
ansible_user=ubuntu
# Tell Ansible to use python3
ansible_python_interpreter=/usr/bin/python3

2. The ansible CLI (Ad-Hoc Commands)

Use the ansible command for quick tasks without writing a full playbook.

Ping the servers to test connectivity:

ansible webservers -i inventory.ini -m ping
  • -i inventory.ini: Specifies the inventory file.
  • -m ping: Uses the ping module (verifies Python connectivity, not ICMP ping).

Check system uptime:

ansible webservers -i inventory.ini -a "uptime"
  • -a: Passes arguments (a raw command in this case) to the default command module.

Part 2: The Single Playbook (ansible-playbook)

Ad-hoc commands are great for quick checks, but playbooks (.yml files) are the core of Ansible, allowing for repeatable, complex automation.

1. Write the Playbook

Create a file named setup_docker.yml. We will install Docker, a common requirement for modern deployments.

---
- name: Install and Configure Docker
  # Target the group from our inventory
  hosts: webservers
  # Run commands with sudo
  become: yes
  
  tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600 # Don't update if updated in last hour

    - name: Install required system packages
      ansible.builtin.apt:
        pkg:
          - apt-transport-https
          - ca-certificates
          - curl
          - software-properties-common
          - python3-pip
        state: latest

    - name: Add Docker GPG apt Key
      ansible.builtin.apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present

    - name: Add Docker Repository
      ansible.builtin.apt_repository:
        repo: deb https://download.docker.com/linux/ubuntu focal stable
        state: present

    - name: Install Docker CE
      ansible.builtin.apt:
        name: docker-ce
        state: latest

    - name: Ensure Docker service is running and enabled
      ansible.builtin.systemd:
        name: docker
        state: started
        enabled: yes

    - name: Install Docker module for Python (required for Ansible docker modules)
      ansible.builtin.pip:
        name: docker

2. Run the Playbook

Use the ansible-playbook command:

ansible-playbook -i inventory.ini setup_docker.yml

Ansible will connect to 192.168.1.100, run through the tasks sequentially, and report the status (ok, changed, or failed).

Part 3: Structuring with Roles

As playbooks grow, putting everything in one file becomes messy. Roles allow you to break complex playbooks into reusable components (vars, tasks, handlers).

Let's restructure our project to deploy Docker, WordPress, and n8n using roles.

1. Create the Directory Structure

Ansible has a built-in tool to generate role skeletons: ansible-galaxy.

mkdir my-ansible-project
cd my-ansible-project

# Initialize the roles directory
mkdir roles
cd roles

# Create roles
ansible-galaxy init docker
ansible-galaxy init wordpress
ansible-galaxy init n8n
cd ..

Your structure will look roughly like this:

my-ansible-project/
├── inventory.ini
├── site.yml          <-- Our main playbook
└── roles/
    ├── docker/
    │   ├── tasks/
    │   │   └── main.yml
    │   └── ...
    ├── wordpress/
    │   ├── tasks/
    │   │   └── main.yml
    │   └── ...
    └── n8n/
        ├── tasks/
        │   └── main.yml
        └── ...

2. The docker Role

Move the tasks from our previous single playbook into roles/docker/tasks/main.yml.

# roles/docker/tasks/main.yml
---
# (Paste the tasks from setup_docker.yml here, removing the top-level playbook structure)
- name: Update apt cache
  ansible.builtin.apt:
    update_cache: yes
    cache_valid_time: 3600
# ... (rest of the Docker tasks) ...

3. The wordpress Role (using Docker Compose)

Instead of installing PHP/Nginx manually, let's deploy WordPress using Docker.

# roles/wordpress/tasks/main.yml
---
- name: Create WordPress directory
  ansible.builtin.file:
    path: /opt/wordpress
    state: directory
    mode: '0755'

- name: Create docker-compose.yml for WordPress
  ansible.builtin.copy:
    dest: /opt/wordpress/docker-compose.yml
    content: |
      version: '3.1'
      services:
        wordpress:
          image: wordpress:latest
          restart: always
          ports:
            - 8080:80
          environment:
            WORDPRESS_DB_HOST: db
            WORDPRESS_DB_USER: exampleuser
            WORDPRESS_DB_PASSWORD: examplepass
            WORDPRESS_DB_NAME: exampledb
          volumes:
            - ./wp-data:/var/www/html
        db:
          image: mysql:5.7
          restart: always
          environment:
            MYSQL_DATABASE: exampledb
            MYSQL_USER: exampleuser
            MYSQL_PASSWORD: examplepass
            MYSQL_RANDOM_ROOT_PASSWORD: '1'
          volumes:
            - ./db-data:/var/lib/mysql

- name: Start WordPress containers
  community.docker.docker_compose:
    project_src: /opt/wordpress
    state: present

4. The n8n Role (using Docker Compose)

n8n is a popular workflow automation tool.

# roles/n8n/tasks/main.yml
---
- name: Create n8n directory
  ansible.builtin.file:
    path: /opt/n8n
    state: directory
    mode: '0755'

- name: Create docker-compose.yml for n8n
  ansible.builtin.copy:
    dest: /opt/n8n/docker-compose.yml
    content: |
      version: "3"
      services:
        n8n:
          image: docker.n8n.io/n8nio/n8n
          restart: always
          ports:
            - "5678:5678"
          volumes:
            - n8n_data:/home/node/.n8n
      volumes:
        n8n_data:

- name: Start n8n container
  community.docker.docker_compose:
    project_src: /opt/n8n
    state: present

5. The Master Playbook (site.yml)

Now, tie it all together in site.yml in the root of your project:

# site.yml
---
- name: Provision Production Server
  hosts: webservers
  become: yes
  
  roles:
    - docker
    - wordpress
    - n8n

6. Run the Full Stack

Execute the master playbook:

ansible-playbook -i inventory.ini site.yml

Ansible will now sequentially apply the docker role, then the wordpress role, and finally the n8n role, ensuring your server has all components installed and running via Docker Compose.

Summary of Progression

  1. Ad-Hoc (ansible): Good for one-off commands (checking uptime, restarting a service).
  2. Single Playbook (ansible-playbook): Good for simple, linear setups (installing a few packages).
  3. Roles (ansible-playbook with Roles): The standard for production. It promotes code reuse, cleaner file structures, and easier maintenance as your infrastructure grows.

Subscribe to Experiment Lab

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe