- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Ansible Handlers và Notifications Part 1
Bạn đã biết cách sử dụng handlers cơ bản để restart service. Nhưng trong thực tế, bạn sẽ gặp các tình huống phức tạp hơn: restart nhiều service theo thứ tự, chỉ restart khi cần, hoặc xử lý dependencies giữa các service.
Trong bài học này, chúng ta sẽ tìm hiểu sâu hơn về:
- Cách handlers hoạt động thực sự
- Quản lý thứ tự thực thi handlers
- Handlers trong roles
- Meta handlers và flush handlers
- Patterns và best practices nâng cao
Handlers Hoạt Động Như Thế Nào?
Luồng Thực Thi
Task 1 (changed) → notify Handler A
Task 2 (ok) → không notify
Task 3 (changed) → notify Handler A, Handler B
Task 4 (changed) → notify Handler B
---
Tất cả tasks hoàn thành
---
Handler A chạy (1 lần duy nhất)
Handler B chạy (1 lần duy nhất)
Nguyên Tắc Quan Trọng
- ⚠️ Handlers chỉ chạy 1 lần dù được notify nhiều lần
- ⚠️ Handlers chạy sau tất cả tasks trong play
- ⚠️ Handlers không chạy nếu play bị lỗi (trừ khi dùng force)
- ⚠️ Handlers chạy theo thứ tự được định nghĩa, không theo thứ tự notify
Ví Dụ Cơ Bản Ôn Tập
---
- name: Cấu hình web server
hosts: webservers
become: yes
tasks:
- name: Cập nhật file cấu hình Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Cập nhật SSL certificate
copy:
src: server.crt
dest: /etc/nginx/ssl/server.crt
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
Trong ví dụ này, dù 2 tasks đều notify, Nginx chỉ restart 1 lần.
Notify Nhiều Handlers
Một task có thể trigger nhiều handlers:
tasks:
- name: Cập nhật cấu hình application
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify:
- Restart application
- Restart nginx
- Clear cache
- Send notification
handlers:
- name: Restart application
service:
name: myapp
state: restarted
- name: Restart nginx
service:
name: nginx
state: restarted
- name: Clear cache
command: redis-cli FLUSHALL
- name: Send notification
uri:
url: https://hooks.slack.com/services/xxx
method: POST
body_format: json
body:
text: "Application restarted on {{ inventory_hostname }}"
Quản Lý Thứ Tự Handlers
Vấn Đề: Thứ Tự Quan Trọng
Ví dụ, bạn cần:
- Stop service trước
- Update database schema
- Start service lại
tasks:
- name: Deploy new application version
copy:
src: app-v2.jar
dest: /opt/app/app.jar
notify:
- Stop application
- Update database
- Start application
handlers:
# Thứ tự này quan trọng!
- name: Stop application
service:
name: myapp
state: stopped
- name: Update database
command: /opt/app/migrate.sh
- name: Start application
service:
name: myapp
state: started
Handlers sẽ chạy theo thứ tự: Stop → Update → Start.
Sử Dụng Listen Để Gọi Nhiều Handlers
Thay vì notify từng handler, dùng listen để group:
tasks:
- name: Update application config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: "restart app stack"
handlers:
- name: Stop application service
service:
name: myapp
state: stopped
listen: "restart app stack"
- name: Clear application cache
file:
path: /var/cache/app
state: absent
listen: "restart app stack"
- name: Start application service
service:
name: myapp
state: started
listen: "restart app stack"
Chỉ cần notify "restart app stack", tất cả handlers có listen đó sẽ chạy.
Meta: flush_handlers
Đôi khi bạn cần chạy handlers ngay lập tức, không đợi đến cuối play.
Vấn Đề
tasks:
- name: Update Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Test Nginx configuration
command: nginx -t
# Lỗi! Nginx chưa restart, config cũ vẫn đang chạy
Giải Pháp: flush_handlers
tasks:
- name: Update Nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Restart nginx
- name: Force handlers to run now
meta: flush_handlers
- name: Test Nginx configuration
command: nginx -t
# OK! Nginx đã restart với config mới
- name: Deploy website
copy:
src: index.html
dest: /var/www/html/index.html
Handlers Với Dependencies
Scenario: Database và Application
tasks:
- name: Update database config
template:
src: mysql.cnf.j2
dest: /etc/mysql/mysql.cnf
notify: restart database
- name: Update app config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: restart application
handlers:
- name: restart database
service:
name: mysql
state: restarted
- name: restart application
service:
name: myapp
state: restarted
# Application phụ thuộc vào database
# Cần đảm bảo database restart trước
Thứ tự handlers trong file quyết định thứ tự thực thi!
Handlers Trong Roles
Cấu Trúc
roles/
└── webserver/
├── tasks/
│ └── main.yml
└── handlers/
└── main.yml
roles/webserver/tasks/main.yml
---
- name: Install Nginx
apt:
name: nginx
state: present
- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Configure SSL
copy:
src: ssl.conf
dest: /etc/nginx/conf.d/ssl.conf
notify:
- restart nginx
- reload firewall
roles/webserver/handlers/main.yml
---
- name: restart nginx
service:
name: nginx
state: restarted
- name: reload nginx
service:
name: nginx
state: reloaded
- name: reload firewall
command: ufw reload
Pattern: Reload vs Restart
Một số service hỗ trợ reload (không downtime):
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
# Reload: áp dụng config mới mà không ngắt kết nối
- name: restart nginx
service:
name: nginx
state: restarted
# Restart: stop hoàn toàn rồi start lại
Khi Nào Dùng Gì?
| Thay đổi | Handler |
|---|---|
| Sửa config nhỏ | reload |
| Sửa config lớn | restart |
| Update binary | restart |
| Thêm SSL cert | reload |
| Thay đổi port | restart |
Handlers Có Điều Kiện
Đôi khi bạn muốn handler chỉ chạy trong điều kiện cụ thể:
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
when: ansible_distribution == "Ubuntu"
- name: restart apache
service:
name: httpd
state: restarted
when: ansible_distribution == "CentOS"
Pattern: Graceful Restart
Restart service mà không làm mất request:
handlers:
- name: graceful restart nginx
shell: |
nginx -t && systemctl reload nginx || systemctl restart nginx
# Thử reload trước, nếu fail mới restart
- name: zero-downtime restart app
block:
- name: Remove from load balancer
uri:
url: "http://lb.example.com/api/remove/{{ inventory_hostname }}"
method: POST
- name: Wait for connections to drain
wait_for:
timeout: 30
- name: Restart application
service:
name: myapp
state: restarted
- name: Wait for app to be ready
wait_for:
port: 8080
delay: 5
- name: Add back to load balancer
uri:
url: "http://lb.example.com/api/add/{{ inventory_hostname }}"
method: POST
Handlers Chạy Khi Play Bị Lỗi
Mặc định, nếu play bị lỗi, handlers không chạy. Để force handlers chạy:
Cách 1: force_handlers
---
- name: Deploy application
hosts: appservers
force_handlers: yes # Handlers sẽ chạy dù có lỗi
tasks:
- name: Update config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: restart app
- name: This might fail
command: /some/command/that/might/fail
handlers:
- name: restart app
service:
name: myapp
state: restarted
Cách 2: meta: flush_handlers + block/rescue
tasks:
- block:
- name: Update config
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: restart app
- name: Flush handlers before risky task
meta: flush_handlers
- name: Risky task
command: /risky/command
rescue:
- name: Rollback
copy:
src: app.conf.backup
dest: /etc/app/app.conf
notify: restart app
Debugging Handlers
Kiểm Tra Handlers Nào Được Trigger
ansible-playbook playbook.yml --step
# Chạy từng task một và hỏi có continue không
Dry Run
ansible-playbook playbook.yml --check
# Xem handlers nào sẽ được notify
Verbose Mode
ansible-playbook playbook.yml -vv
# Xem chi tiết handlers execution
Tiếp tục part 2...
