Intermediate · Configuration management with Ansible, and YAML · topic 36 of 53 · 35 min
Ansible playbooks
Explanation
From ad-hoc to playbooks
Ad-hoc commands are one-off. A playbook is a file you keep in Git and run again and again. It is written in YAML. A playbook contains plays; a play says which hosts to target and lists tasks; a task calls one module with arguments and has a name that shows up in the output.
---
- name: Set up web servers for the college website
hosts: web
become: true
vars:
site_name: college.edu
packages:
- nginx
- ufw
tasks:
- name: Install packages
ansible.builtin.apt:
name: "{{ packages }}"
state: present
update_cache: true
- name: Deploy the nginx site config
ansible.builtin.template:
src: templates/site.conf.j2
dest: /etc/nginx/sites-available/{{ site_name }}
mode: "0644"
notify: Reload nginx
- name: Enable the site
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ site_name }}
dest: /etc/nginx/sites-enabled/{{ site_name }}
state: link
notify: Reload nginx
- name: Copy the home page
ansible.builtin.copy:
src: files/index.html
dest: /var/www/html/index.html
owner: www-data
mode: "0644"
- name: Make sure nginx is running and starts at boot
ansible.builtin.service:
name: nginx
state: started
enabled: true
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloadedUseful modules
| Module | What it does |
|---|---|
| ansible.builtin.apt / ansible.builtin.dnf | Install, upgrade or remove packages (Debian and Ubuntu / Fedora and RHEL family) |
| ansible.builtin.copy | Copy a file from the control node, or write given content |
| ansible.builtin.template | Render a Jinja2 template with variables, then copy it |
| ansible.builtin.service / ansible.builtin.systemd_service | Start, stop, restart, reload and enable services |
| ansible.builtin.user | Create or remove users, set groups and shells |
| ansible.builtin.file | Create folders, links, set owners and permissions, delete files |
| ansible.builtin.lineinfile | Make sure one line is present (or absent) in a file |
Variables
A variable is a named value used as {{ name }} (Jinja2 syntax). You can set variables in a play under vars, in files group_vars/web.yml (for every host in the web group), group_vars/all.yml (for every host), and host_vars/web1.college.edu.yml (for one host). Ansible has a long precedence order; the short version is: more specific wins (host over group, group over all), and extra vars on the command line (-e) win over everything. Role defaults have the lowest priority.
Facts
At the start of each play, Ansible gathers facts: information about every host, such as the OS family, distribution, IP addresses, memory and CPUs. They are available as ansible_facts, for example ansible_facts['os_family'] is "Debian" on Ubuntu. Run ansible web1.college.edu -m ansible.builtin.setup to see them all. Set gather_facts: false to skip this step when you do not need facts and want speed.
Handlers and notify
You want to reload nginx only when its config actually changed, not on every run. A handler is a task that runs only when notified. A task notifies a handler with notify, and the notification counts only if the task reported changed. Handlers run once, at the end of the play (after all tasks), even if several tasks notified them, and they run in the order they are listed in the handlers section.
Conditionals and loops
- name: Install nginx on Debian-family hosts
ansible.builtin.apt:
name: nginx
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Create staff accounts
ansible.builtin.user:
name: "{{ item }}"
groups: www-data
append: true
loop:
- asha
- ravi
- meenaCheck mode and diff
ansible-playbook -i inventory.ini site.yml --check --diff # dry run: what would change, and how
ansible-playbook -i inventory.ini site.yml --limit web1.college.edu # run on one host only
ansible-playbook -i inventory.ini site.yml --syntax-checkCommon mistakes
- Restarting a service in a normal task on every run. Use a handler with notify so it happens only after a change.
- Writing when: "{{ x }} == 1". when is already an expression: write when: x == 1.
- Forgetting become: true when tasks need root.
- Wrong indentation under a module: its arguments must be indented one level below the module name.
- Unquoted file modes such as mode: 0644. Quote them as "0644" so they are not read as a different number.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Ansible playbooks · 1/7
In this lesson you will write Ansible playbooks, files you keep in Git and run again and again.
Prefer a video by a teacher? Find free videos on “ansible playbook tutorial handlers variables loops nginx” on YouTube ↗
In real life: Rolling out a college website to two web servers
The IT cell of an engineering college in Coimbatore runs its public website on two Ubuntu VMs behind a load balancer. The playbook above installs nginx, deploys the site config from a template, copies the home page and keeps nginx running. When the web team changes the server_name in the template, only the template task reports changed, and the Reload nginx handler runs once per server at the end. Before each run on production they use --check --diff to see the exact lines that will change.
TASK [Deploy the nginx site config] ****
changed: [web1.college.edu]
changed: [web2.college.edu]
RUNNING HANDLER [Reload nginx] ****
changed: [web1.college.edu]
changed: [web2.college.edu]
PLAY RECAP ****
web1.college.edu : ok=6 changed=2 unreachable=0 failed=0Practice (0/3 done)
Which keyword in a play runs its tasks with sudo?
Simulate handlers. handlers is the list of handler names in the order they are defined. results is a list of task results, each a dict like {"changed": True, "notify": ["Reload nginx"]} (notify may be missing). Write handlers_to_run(handlers, results) that returns the handlers that will run at the end: each one only once, only if notified by a task that changed, in the order of the handlers list.
Write a playbook users.yml for the hosts group "lab" that: installs git and htop with apt, creates the users asha and ravi with a loop, adds the line "PermitRootLogin no" to /etc/ssh/sshd_config with lineinfile, and restarts ssh through a handler only when that line changed. Run it with --check --diff first (in a VM or with Vagrant/Multipass if you have one).
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Roles, templates, Galaxy and VaultConfiguration management with Ansible, and YAML · same module
- Variables, facts and registered resultsLinux and Red Hat (RHCSA to RHCE) · Ansible in depth
- Writing and running playbooksLinux and Red Hat (RHCSA to RHCE) · Ansible fundamentals
- Everyday modules and handlersLinux and Red Hat (RHCSA to RHCE) · Ansible fundamentals
- Ad-hoc commands and ansible-docLinux and Red Hat (RHCSA to RHCE) · Ansible fundamentals
- Roles, collections and ansible-galaxyLinux and Red Hat (RHCSA to RHCE) · Ansible in depth
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Two tasks notify "Reload nginx" and both report changed. How many times does the handler run on that host?
Answer: Once, at the end of the play
Handlers are deduplicated and run once, after the tasks of the play.
Which line is a correct condition?
Answer: when: ansible_facts['os_family'] == "Debian"
when is already a Jinja2 expression, so no {{ }} is needed.
What does ansible-playbook site.yml --check --diff do?
Answer: Shows what would change, with file differences, without changing anything
Check mode is a dry run; diff shows the before and after of files.
Where do you put variables that apply to every host in the group "web"?
Answer: group_vars/web.yml
group_vars/GROUPNAME.yml applies to all hosts in that group.
name: {{ packages }} fails to parse. Why?
Answer: A value starting with { must be quoted in YAML
YAML reads an unquoted { as the start of a map. Write "{{ packages }}".
Which module gathers facts manually?Related
Answer: ansible.builtin.setup
setup is what Gathering Facts runs.
What does -C do with ansible-playbook?Related
Answer: Check mode: predict changes without making them
It is a dry run.
Three tasks notify the same handler and all change. How many times does it run?Related
What does -b mean in an ad-hoc command?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Configuration management with Ansible, and YAML” before its exam: Roles, templates, Galaxy and Vault.
- Module 8 exam: Configuration management with Ansible, and YAML · 13 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Monitoring and logging. 53 topics left in the course before the final exam.