Cloud AI Campus
  • Career paths
  • Learning paths
  • Hands-on Labs
Log in Sign up

๐Ÿงช Hands-on lab · 60 min

Ansible โ€” Playbooks, Introduction

  1. 1. Your first playbook
  2. 2. Variables
  3. 3. Facts
  4. 4. Jinja2 templates
  5. 5. Handlers

Your first playbook

A playbook is a YAML file that describes a list of plays, each of which is a set of tasks to run against a set of hosts.

Create hello.yml in /root/ansible/:

---
- name: First play โ€” print a greeting
  hosts: centos
  gather_facts: false
  tasks:
    - name: Say hi
      debug:
        msg: "Hello from {{ inventory_hostname }}"
...

The --- and ... markers are YAML document boundaries; you don't strictly need them but they're the convention. Notice:

  • hosts: centos โ€” limits the play to the centos group.
  • gather_facts: false โ€” skips the implicit setup task for now.
  • debug is a module that prints things to stdout.
  • {{ inventory_hostname }} is a built-in variable Ansible replaces with each target's name as the task runs.

Run it:

ansible-playbook hello.yml

You should see three Hello from centosN lines. Click Verify step.

Hint

`ansible-playbook hello.yml` should print a debug message on every centos host.

Variables

Variables let you separate what changes between runs from how the playbook works. They can live in a vars: section on the play, in external vars_files:, in group_vars/<name>.yml, in host_vars/, or be passed on the command line with -e.

Replace hello.yml with:

---
- name: Greet by message
  hosts: linux
  gather_facts: false
  vars:
    greeting: "Hi from the lab"
  tasks:
    - name: Print the greeting
      debug:
        msg: "{{ greeting }} โ€” running on {{ inventory_hostname }}"
...

Run it and check the output uses your greeting:

ansible-playbook hello.yml

Now override the variable from the command line โ€” this is how you'd parameterise a deployment:

ansible-playbook hello.yml -e 'greeting=overridden'

-e (extra-vars) wins over vars: blocks. Click Verify step when the override appears in the output.

Hint

Put `vars:` under the play, or use a `vars_files:` reference.

Facts

Before running a play, Ansible can run a hidden setup task that gathers facts โ€” properties of the target host (distro, IPs, hardware, kernel, etc.). Facts become available as variables like ansible_distribution, ansible_default_ipv4.address, ansible_user_id.

Look at every fact for a single host:

ansible centos1 -m setup | less

(Press q to quit less.)

Now write a facts.yml playbook that prints a fingerprint of each host:

---
- name: Print a fingerprint per host
  hosts: linux
  tasks:
    - name: Identify host
      debug:
        msg: "{{ inventory_hostname }} ({{ ansible_distribution }} {{ ansible_distribution_version }}) IP={{ ansible_default_ipv4.address }}"
...

Notice we dropped gather_facts: false โ€” the default is true, and those ansible_* variables require it.

The lab workers are all the same Alpine-based image regardless of hostname, so every host reports ansible_distribution: "Alpine". On real centos/ubuntu hosts you'd see those names โ€” try this same playbook against your own infrastructure to see it in action.

ansible-playbook facts.yml

Click Verify step when each line shows distro + version + IP.

Hint

`ansible_distribution`, `ansible_default_ipv4.address` are gathered by setup.

Jinja2 templates

The template module renders a Jinja2 template into a file on the target. Templates can use any variable Ansible knows about, including facts.

Create motd.j2 in /root/ansible/:

Welcome to {{ ansible_hostname }}
  Distro:   {{ ansible_distribution }} {{ ansible_distribution_version }}
  IPv4:     {{ ansible_default_ipv4.address }}
  Managed:  by Ansible

Now motd.yml that places it at /etc/motd on every host:

---
- name: Deploy MOTD
  hosts: linux
  tasks:
    - name: Render /etc/motd from template
      template:
        src: motd.j2
        dest: /etc/motd
        mode: '0644'
...

Apply it:

ansible-playbook motd.yml

Confirm one of the hosts received the rendered file:

ssh centos1 cat /etc/motd

Click Verify step once /etc/motd on every host contains the rendered template.

Hint

The `template` module renders `<src>.j2` โ†’ `<dest>`.

Handlers

A handler is a task that runs only when notified by another task that actually changed state. The classic use is "restart a service after its config file was touched."

Create nginx.yml:

---
- name: Install + configure nginx
  hosts: linux
  tasks:
    - name: Install nginx
      package:
        name: nginx
        state: present
      notify: Restart nginx

    - name: Ensure the docroot exists
      file:
        path: /usr/share/nginx/html
        state: directory
        mode: '0755'

    - name: Drop a welcome page
      copy:
        dest: /usr/share/nginx/html/index.html
        content: "Hello from {{ inventory_hostname }}\n"
        mode: '0644'
      notify: Restart nginx

  handlers:
    - name: Restart nginx
      command: nginx -s reload
...

The two key behaviours:

  1. Handlers run once at the end of the play, even if multiple tasks notify: them.
  2. They run only on hosts where at least one notifying task reported a change โ€” so re-running the playbook on a host that's already configured won't bounce nginx.

Run it twice:

ansible-playbook nginx.yml
ansible-playbook nginx.yml

The second run should show changed=0 (or close to it) for every host, and the Restart nginx handler should NOT fire.

Verify nginx is up on one host:

ssh ubuntu1 curl -s http://localhost

Click Verify step once nginx serves the welcome page on every host.

Hint

Tasks `notify:` a handler by name; handlers run once at end of play if notified.

© 2026 Cloud AI Campus