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

๐Ÿงช Hands-on lab · 60 min

Ansible โ€” Structuring Playbooks

  1. 1. import vs include
  2. 2. Your first role
  3. 3. Tags
  4. 4. Tuning ansible.cfg

import_tasks vs include_tasks

Once your playbook grows past 50 lines, pull groups of tasks into separate files.

| Statement | Resolved at | Conditionals | Loops | |-------------------|--------------|------------------|-------| | import_tasks | parse | applied to each | no | | include_tasks | runtime | applied to the include | yes |

Rule of thumb: prefer import_* for static structure (you almost always want it), reach for include_* when you need to iterate or gate the entire group of tasks.

Create tasks/setup.yml:

---
- name: Ensure marker dir exists
  file:
    path: /etc/cac-structuring
    state: directory
    mode: '0755'

- name: Drop README
  copy:
    dest: /etc/cac-structuring/README
    content: "Imported from tasks/setup.yml\n"
    mode: '0644'
...

And tasks/cleanup.yml:

---
- name: Remove a (probably-missing) file
  file:
    path: /etc/cac-doesnt-exist
    state: absent
...

Then the entry-point structure.yml:

---
- name: Composed playbook
  hosts: linux
  gather_facts: false
  tasks:
    - import_tasks: tasks/setup.yml
    - include_tasks: tasks/cleanup.yml
...

Both forms point at a YAML file with a list of tasks. The difference matters when you want to loop the include or wrap it in a conditional โ€” include_tasks will resolve the loop/condition at runtime per item, while import_tasks rewrites the playbook at parse time and the loop/condition would apply to each imported task individually.

Run it:

ansible-playbook structure.yml

Click Verify step once /etc/cac-structuring/README exists on every host.

Hint

`import_tasks` resolves at parse time; `include_tasks` resolves at runtime and can be looped or gated.

Your first role

A role is the canonical reusable bundle: tasks + handlers + templates + defaults + metadata in one directory.

Scaffold one with ansible-galaxy:

cd /root/ansible
ansible-galaxy init roles/webserver
tree roles/webserver

Edit roles/webserver/tasks/main.yml:

---
- name: Drop a placeholder index
  copy:
    dest: /etc/cac-webserver.html
    content: "<h1>Served by {{ inventory_hostname }} โ€” role webserver</h1>\n"
    mode: '0644'
...

Edit roles/webserver/defaults/main.yml:

---
webserver_owner: "ops@cloudaicampus.local"
...

Now consume the role from a play. Write site.yml:

---
- name: Apply the webserver role to every linux host
  hosts: linux
  gather_facts: false
  roles:
    - webserver
...

Run it:

ansible-playbook site.yml

The roles: keyword runs all of the role's tasks. Variables from defaults/ are automatically merged into the play's scope, so {{ webserver_owner }} would work inside any of the role's templates.

Click Verify step once /etc/cac-webserver.html lands on every host.

Hint

`ansible-galaxy init <name>` scaffolds the directory; `roles:` on a play consumes it.

Tags

tags: annotate tasks (or whole roles) so you can run a slice:

  • ansible-playbook site.yml --tags setup,deploy runs only those.
  • ansible-playbook site.yml --skip-tags slow,destructive excludes those.
  • --list-tags enumerates every tag in the playbook.

Add tags to your site.yml:

---
- name: Apply roles with tags
  hosts: linux
  gather_facts: false
  tasks:
    - name: Drop fast marker
      file:
        path: /etc/cac-tag-fast
        state: touch
        mode: '0644'
      tags: [fast]

    - name: Drop slow marker
      command: sh -c 'sleep 1; touch /etc/cac-tag-slow'
      tags: [slow]
      changed_when: false
...

Now run only the fast tag:

ansible-playbook site.yml --tags fast

Only /etc/cac-tag-fast should appear; the slow task is skipped.

Then run only the slow tag:

ansible-playbook site.yml --tags slow

--list-tags is handy when a playbook gets bigger:

ansible-playbook site.yml --list-tags

Click Verify step once both markers exist on every host.

Hint

Add `tags: [setup]` to tasks; run `ansible-playbook ... --tags setup` to filter.

Tuning ansible.cfg

ansible.cfg is a layered configuration file. Order of precedence (highest first):

  1. ANSIBLE_* environment variables
  2. ./ansible.cfg in the current directory
  3. ~/.ansible.cfg
  4. /etc/ansible/ansible.cfg

The two highest-impact knobs for big inventories:

  • forks = N โ€” how many hosts the controller talks to in parallel (default 5).
  • strategy = free โ€” let each host run through the whole play independently instead of synchronizing on each task (strategy = linear is the default).

Update /root/ansible/ansible.cfg:

[defaults]
inventory          = hosts
host_key_checking  = False
retry_files_enabled = False
forks              = 10
strategy           = free

Write bench.yml to feel the difference:

---
- name: Strategy benchmark
  hosts: linux
  gather_facts: false
  tasks:
    - name: Stagger the work
      command: sh -c 'sleep {{ groups.linux.index(inventory_hostname) }}; true'
      changed_when: false

    - name: Mark complete
      file:
        path: /etc/cac-strategy.done
        state: touch
        mode: '0644'
...

Time it:

time ansible-playbook bench.yml

With strategy = free, hosts that finish their sleep quickly move on to the file task without waiting for ubuntu3 (which sleeps 5s) to finish. With strategy = linear (try toggling it back) the play takes ~max(sleeps) per task.

Click Verify step once /etc/cac-strategy.done is present on every host.

Hint

Increase `forks` for parallelism; set `strategy=free` so slow hosts don't block fast ones.

© 2026 Cloud AI Campus