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:
- Handlers run once at the end of the play, even if multiple
tasks
notify: them.
- 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.