Understanding Playbooks

Playbooks are the heart of Ansible. They are YAML files that define a set of tasks to be executed on specified hosts or groups of hosts. Playbooks provide a reusable, repeatable, and versionable way to orchestrate your infrastructure.

A basic playbook structure looks like this:

---
# my_first_playbook.yml
- name: My first Ansible Playbook
  hosts: webservers
  become: true # Run tasks with elevated privileges (e.g., sudo)
  tasks:
    - name: Ensure Apache is installed
      ansible.builtin.apt:
        name: apache2
        state: present
      when: ansible_os_family == "Debian"

    - name: Ensure Nginx is installed
      ansible.builtin.yum:
        name: nginx
        state: present
      when: ansible_os_family == "RedHat"

    - name: Start and enable Apache service
      ansible.builtin.service:
        name: apache2
        state: started
        enabled: true
      when: ansible_os_family == "Debian"

    - name: Start and enable Nginx service
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true
      when: ansible_os_family == "RedHat"

Let’s break down the components:

  • : Denotes the start of a YAML file.
  • - name: My first Ansible Playbook: A descriptive name for the playbook. Good practice for readability.
  • hosts: webservers: Specifies which hosts from your inventory this playbook will run against. Can be a group (like webservers), a single host, or all.
  • become: true: This is crucial for tasks that require root privileges (like installing packages or managing services). It tells Ansible to use sudo (or equivalent) on the remote host.
  • tasks:: A list of individual actions Ansible will perform.
  • - name: Ensure Apache is installed: A descriptive name for the task.
  • ansible.builtin.apt:: This specifies the module to use. ansible.builtin is the collection where many core Ansible modules reside. apt is for Debian/Ubuntu systems.
  • name: apache2: The package name.
  • state: present: Ensures the package is installed. Other states include absent (remove), latest (ensure latest version).
  • when: ansible_os_family == “Debian”: This is a conditional statement. This task will only run if the ansible_os_family fact (gathered automatically by Ansible) is "Debian". This allows for platform-specific tasks within the same playbook.
  • ansible.builtin.yum:: The module for RedHat/CentOS/Fedora systems.
  • ansible.builtin.service:: The module for managing system services.
  • name: apache2 / nginx: The service name.
  • state: started: Ensures the service is running. Other states: stopped, restarted, reloaded.
  • enabled: true: Ensures the service starts automatically on boot.

Common Modules

Ansible has hundreds of modules. Here are some commonly used ansible.builtin modules:

  • apt, yum, dnf, pacman: For managing packages on various Linux distributions.
  • service: For managing system services (start, stop, restart, enable, disable).
  • copy: For copying files from the control node to the managed node.
  • template: For deploying files using Jinja2 templates (useful for configuration files with variables).
  • file: For managing files and directories (create, delete, change permissions).
  • user: For managing user accounts.
  • group: For managing groups.
  • command, shell: For executing arbitrary commands on the remote host. (Use these sparingly; prefer dedicated modules when available.)
  • debug: For printing messages or variable values during playbook execution.
  • ping: For testing connectivity (as seen in Part 1).

Writing Your First Playbook: Installing Nginx

Let’s create a simple playbook to install and start Nginx on your web servers, assuming they are running a RedHat-based distribution (like CentOS, Rocky Linux, AlmaLinux).

  1. Update your inventory.ini:

Ensure you have at least one host under the [webservers] group that you can SSH into and has sudo privileges.

# inventory.ini
[webservers]
your_webserver_ip_or_hostname
# e.g., web1.example.com or 192.168.1.100

[all:vars]
ansible_user=your_ssh_user
ansible_private_key_file=~/.ssh/id_rsa
  1. Create the playbook file:

Save the following content as nginx_install.yml in the same directory as your inventory.ini.

---
# nginx_install.yml
- name: Install and configure Nginx web server
  hosts: webservers
  become: true # Use sudo for elevated privileges

  tasks:
    - name: Ensure Nginx package is present
      ansible.builtin.yum:
        name: nginx
        state: present
        update_cache: true # Ensures package cache is updated before installing

    - name: Start Nginx service
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true # Ensure Nginx starts on boot

Explanation of new parameters:

  • update_cache: true: For yum/apt modules, this ensures that the package manager’s cache is updated, so it can find the latest package information.

Running Your First Playbook

Now, let’s execute the playbook.

  1. Navigate to your project directory:
cd /path/to/your/ansible/project
  1. Run the playbook using ansible-playbook:
ansible-playbook -i inventory.ini nginx_install.yml
  • ansible-playbook: The command to execute playbooks.
  • -i inventory.ini: Specifies your inventory file.
  • nginx_install.yml: The playbook file to run.

Expected Output

When you run the playbook, Ansible will connect to your target host(s), gather facts, and then execute each task. You’ll see output similar to this:

PLAY [Install and configure Nginx web server] **********************************

TASK [Gathering Facts] *********************************************************
ok: [your_webserver_ip_or_hostname]

TASK [Ensure Nginx package is present] *****************************************
changed: [your_webserver_ip_or_hostname]

TASK [Start Nginx service] *****************************************************
changed: [your_webserver_ip_or_hostname]

PLAY RECAP *********************************************************************
your_webserver_ip_or_hostname : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Understanding the output:

  • PLAY: Indicates the start of a new play defined in your playbook.
  • TASK: Shows the execution of each individual task.
  • ok: The task completed successfully, and no changes were made to the system (e.g., the package was already installed).
  • changed: The task completed successfully, and it made changes to the system (e.g., installed the package, started the service).
  • unreachable: Ansible could not connect to the host.
  • failed: The task encountered an error.
  • PLAY RECAP: A summary of the playbook run for each host, showing the number of ok, changed, unreachable, failed, skipped, rescued, and ignored tasks.

After the playbook completes, you should be able to access Nginx on your web server by navigating to its IP address or hostname in a web browser.

Idempotence

One of Ansible’s core principles is idempotence. This means that running the same playbook multiple times will result in the same system state without causing unintended side effects.

If you run the nginx_install.yml playbook a second time, you’ll notice that the changed count will likely be 0 for the Nginx installation task, as it’s already installed and running. The service task might show ok if it was already running and enabled, or changed if for some reason it wasn’t.

This idempotence is achieved by the modules themselves, which check the current state of the system before attempting to make changes.

Next Steps

In Part 3, we will explore variables and templates, which are essential for creating flexible and reusable playbooks that can adapt to different environments and configurations.