Ansible – Variables & Notify/Handlers
1. Ansible Variables
Variables store values that can be reused across tasks, playbooks, and roles.
1.1 Defining Variables
In a Playbook (vars block)
- name: Demo playbook
hosts: webservers
vars:
http_port: 80
app_name: myapp
tasks:
- name: Print app name
debug:
msg: "App is {{ app_name }} on port {{ http_port }}"
- name: demo
become: yes
hosts: ubuntu
tasks:
- name: Print app name
ansible.builtin.debug:
msg: "App is {{ app_name }} on port {{ http_port }}"
ansible-playbook -i ubuntu --user ansible -e "app_name=demo-vars http_port=8080" demo.yaml
In Separate Variable Files
# vars/main.yml
db_host: localhost
db_port: 5422
db_name: production
# Reference in playbook
- name: Setup DB
hosts: dbservers
vars_files:
- vars/main.yml
tasks:
- debug:
msg: "DB at {{ db_host }}:{{ db_port }}"
ansible-playbook playbook.yml -e "@vars_file.yml"
1.2 Variable Precedence (Low → High)
| Priority | Source |
|---|---|
| 1 (lowest) | Role defaults (defaults/main.yml) |
| 2 | Inventory group vars |
| 2 | Inventory host vars |
| 4 | Playbook group_vars/ |
| 5 | Playbook host_vars/ |
| 6 | Playbook vars: block |
| 7 | vars_files: |
| 8 | vars_prompt: |
| 9 | Task vars: |
| 10 | set_fact / registered vars |
| 11 | Extra vars -e (highest) |
Rule of thumb:
-e(extra vars) always wins.
1.3 group_vars and host_vars Directory Structure
inventory/
├── hosts # inventory file
├── group_vars/
│ ├── all.yml # applies to ALL hosts
│ ├── webservers.yml # applies to webservers group
│ └── dbservers.yml
└── host_vars/
├── web1.yml # applies to host web1 only
└── web2.yml
# group_vars/webservers.yml
nginx_version: "1.24"
max_workers: 4
1.4 Registering Variables
Capture the output of a task into a variable.
- name: Check disk usage
command: df -h /
register: disk_output
- name: Show disk info
debug:
msg: "{{ disk_output.stdout }}"
Common register attributes:
| Attribute | Description |
|---|---|
.stdout |
Standard output as string |
.stdout_lines |
Output as list of lines |
.stderr |
Standard error output |
.rc |
Return code (0 = success) |
.failed |
Boolean – did the task fail? |
.changed |
Boolean – did something change? |
1.5 set_fact – Dynamic Variables
- name: Set a computed variable
set_fact:
full_url: "http://{{ ansible_host }}:{{ http_port }}/{{ app_name }}"
- name: Use the computed variable
debug:
msg: "URL is {{ full_url }}"
1.6 vars_prompt – Interactive Variables
- name: Deploy app
hosts: all
vars_prompt:
- name: deploy_env
prompt: "Enter environment (dev/staging/prod)"
private: no
tasks:
- debug:
msg: "Deploying to {{ deploy_env }}"
1.7 Extra Vars (Command Line)
# Pass single variable
ansible-playbook deploy.yml -e "version=2.1"
# Pass multiple variables
ansible-playbook deploy.yml -e "version=2.1 env=prod"
# Pass a vars file
ansible-playbook deploy.yml -e "@vars/prod.yml"
1.8 Variable Filters (Jinja2)
vars:
names: ["alice", "bob", "charlie"]
score: 85.6789
tasks:
- debug:
msg: "{{ names | join(', ') }}" # alice, bob, charlie
- debug:
msg: "{{ score | round(2) }}" # 85.68
- debug:
msg: "{{ undefined_var | default('N/A') }}" # N/A if not defined
- debug:
msg: "{{ app_name | upper }}" # MYAPP
2. Notify & Handlers
Handlers are tasks that run only when notified — used to trigger actions like restarting services after config changes.
2.1 Basic Notify + Handler Pattern
- name: Configure Nginx
hosts: webservers
tasks:
- name: Copy nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart Nginx # ← triggers handler IF task changes
handlers:
- name: Restart Nginx # ← must match notify string exactly
service:
name: nginx
state: restarted
Key behaviour:
- Handler runs only if the task reports
changed - Handler runs once at the end of the play — even if notified multiple times
- If a task doesn’t change anything, the handler is NOT called
2.2 Notifying Multiple Handlers
tasks:
- name: Update app config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify:
- Restart App
- Reload Firewall
handlers:
- name: Restart App
service:
name: myapp
state: restarted
- name: Reload Firewall
service:
name: firewalld
state: reloaded
2.2 Handler Triggered by Multiple Tasks
tasks:
- name: Update nginx.conf
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart Nginx
- name: Update SSL cert
copy:
src: cert.pem
dest: /etc/nginx/ssl/cert.pem
notify: Restart Nginx # same handler notified twice
handlers:
- name: Restart Nginx # runs ONCE at end of play
service:
name: nginx
state: restarted
2.4 Forcing Handler Execution with meta
tasks:
- name: Update config
copy:
src: app.conf
dest: /etc/app/app.conf
notify: Restart App
- name: Flush handlers NOW (don't wait for end of play)
meta: flush_handlers
- name: Run smoke test (after restart)
uri:
url: http://localhost:8080/health
status_code: 200
meta: flush_handlersforces all pending handlers to run immediately at that point.
2.5 Handlers in Roles
roles/
└── webserver/
├── tasks/
│ └── main.yml
└── handlers/
└── main.yml ← handlers live here in roles
# roles/webserver/handlers/main.yml
- name: Restart Nginx
service:
name: nginx
state: restarted
- name: Reload Nginx
service:
name: nginx
state: reloaded
2.6 Listening to Multiple Notifications
handlers:
- name: Restart web stack
service:
name: nginx
state: restarted
listen: "web stack changed" # alias topic name
tasks:
- name: Update nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: "web stack changed" # notifies by topic
- name: Update PHP config
copy:
src: php.ini
dest: /etc/php/php.ini
notify: "web stack changed" # same topic
2.7 changed_when — Controlling Handler Triggering
- name: Run custom script
command: /opt/scripts/check_config.sh
register: result
changed_when: result.rc == 1 # Only "changed" if exit code is 1
notify: Apply new config
- name: This task never triggers handlers
command: /opt/scripts/read_only.sh
changed_when: false
---
- name: demo
become: yes
hosts: localhost
tasks:
- ansible.builtin.debug:
msg: "Installing tree package"
- name: Install apache httpd (state=present is optional)
ansible.builtin.apt:
name: apache2
update_cache: yes
state: present
notify: restart service apcahe
- name: Install unzip
ansible.builtin.apt:
name: unzip
state: present
- name: Download gaming html
ansible.builtin.get_url:
url: https://templatemo.com/tm-zip-files-2020/templatemo_589_lugx_gaming.zip
dest: /tmp/templatemo_589_lugx_gaming.zip
mode: '0755'
notify: restart service apcahe
- name: unzip templated in apache2 html
ansible.builtin.unarchive:
src: /tmp/templatemo_589_lugx_gaming.zip
dest: /tmp/
remote_src: yes
- name: copy files
ansible.builtin.copy:
src: /tmp/templatemo_589_lugx_gaming/
dest: /var/www/html/
remote_src: yes
owner: www-data
group: www-data
mode: '0755'
notify: restart service apcahe
- ansible.builtin.debug:
msg: "completed tasks"
handlers:
- name: restart service apcahe
ansible.builtin.systemd_service:
name: apache2
enabled: yes
state: restarted
Task:
- update existing playbook with variable
- create new playbook add print message disk space all nodes
- update playbook use handlers which will required when any change happend.
