Lab 7 — Ansible Handlers & Idempotency
Mục tiêu: Sau Lab này, người học hiểu được Idempotency, biết sử dụng Handlers để chỉ reload/restart service khi configuration thực sự thay đổi, và bắt đầu hình thành tư duy automation theo mô hình Desired State trong môi trường production.
1. Bối cảnh
Ở Lab 6, chúng ta đã biết sử dụng:
Variables
↓
Jinja2 Template
↓
Configuration file
Ví dụ:
server_name: todo.example.com
app_port: 8080
Template:
server {
listen 80;
server_name {{ server_name }};
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
}
}
Ansible tạo ra:
/etc/nginx/sites-available/todo.conf
Nhưng có một vấn đề.
Sau khi configuration thay đổi, Nginx cần được reload:
systemctl reload nginx
Một người mới có thể viết:
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
Nhìn qua có vẻ đúng.
Nhưng hãy chạy:
ansible-playbook playbook.yml
Lần 1:
Template changed
↓
Restart nginx
Lần 2:
Template không thay đổi
↓
Restart nginx
Lần 3:
Template không thay đổi
↓
Restart nginx
Vấn đề là:
Nginx bị restart dù configuration không thay đổi.
Trong production, đây là điều không mong muốn.
2. Idempotency là gì?
Idempotency có thể hiểu đơn giản:
Chạy automation nhiều lần với cùng một input thì trạng thái cuối cùng vẫn giống nhau và không tạo ra những thay đổi không cần thiết.
Ví dụ:
Desired State:
Nginx
running
port 80
config đúng
Ansible chạy lần đầu:
Current State
↓
Sai
↓
Change
↓
Desired State
Chạy lần hai:
Current State
↓
Đã đúng
↓
Không cần change
Chạy lần ba:
Current State
↓
Đã đúng
↓
Không cần change
Đây chính là tư duy:
Current State
+
Desired State
↓
Ansible
↓
Required Changes
3. Idempotency không có nghĩa là "chạy lại không làm gì"
Đây là một hiểu nhầm phổ biến.
Idempotency không có nghĩa:
Playbook chạy lần 2
→ Không thực hiện task nào
Mà là:
Playbook chạy lần 2
→ Không thực hiện những thay đổi không cần thiết
Ví dụ:
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
Nếu Nginx đã chạy:
changed=0
Nếu Nginx đang stopped:
changed=1
Ansible chỉ thay đổi khi cần.
4. changed trong Ansible
Một trong những khái niệm quan trọng nhất của Lab này là:
changed
Ví dụ:
ansible-playbook playbook.yml
Kết quả:
ok=3
changed=1
failed=0
Có nghĩa:
3 tasks thành công
1 task thực sự thay đổi hệ thống
0 task thất bại
Nếu chạy lại:
ok=3
changed=0
failed=0
thì automation đã đạt trạng thái ổn định.
5. Vì sao changed quan trọng?
Nhiều tính năng của Ansible dựa vào trạng thái:
changed = true
Ví dụ:
Template changed
↓
notify
↓
Handler
↓
Reload service
Nếu:
Template unchanged
↓
notify không xảy ra
↓
Handler không chạy
Do đó:
changedchính là cầu nối giữa Task và Handler.
6. Handler là gì?
Handler là một task đặc biệt chỉ chạy khi được một task khác notify.
Ví dụ:
tasks:
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Flow:
Template
│
│ changed?
│
├── No ──────────────→ Handler không chạy
│
└── Yes
│
▼
notify
│
▼
Reload nginx
7. Tạo Lab
Tạo project:
mkdir ansible-lab-07
cd ansible-lab-07
Cấu trúc:
ansible-lab-07/
├── ansible.cfg
├── inventory.ini
├── group_vars/
│ └── webservers.yml
├── templates/
│ └── nginx.conf.j2
└── playbook.yml
8. ansible.cfg
[defaults]
inventory = ./inventory.ini
9. Inventory
[webservers]
web-01 ansible_host=192.168.56.101
web-02 ansible_host=192.168.56.102
Kiểm tra:
ansible-inventory --graph
10. Variables
Tạo:
group_vars/webservers.yml
app_name: todo
app_host: 127.0.0.1
app_port: 8080
server_name: todo.example.com
nginx_port: 80
11. Template
Tạo:
templates/nginx.conf.j2
server {
listen {{ nginx_port }};
server_name {{ server_name }};
location / {
proxy_pass http://{{ app_host }}:{{ app_port }};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
12. Playbook đầu tiên
Tạo:
playbook.yml
---
- name: Configure nginx
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
owner: root
group: root
mode: "0644"
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
Chạy:
ansible-playbook playbook.yml
13. Chạy lần đầu
Bạn có thể thấy:
TASK [Install nginx]
changed: [web-01]
TASK [Deploy nginx configuration]
changed: [web-01]
TASK [Ensure nginx is running]
ok: [web-01]
Ví dụ:
changed=2
Bởi vì:
Nginx chưa tồn tại
↓
Install
Configuration chưa tồn tại
↓
Deploy
14. Chạy lần thứ hai
ansible-playbook playbook.yml
Bạn kỳ vọng:
TASK [Install nginx]
ok
TASK [Deploy nginx configuration]
ok
TASK [Ensure nginx is running]
ok
Cuối cùng:
changed=0
Đây là một Playbook idempotent.
15. Nhưng configuration thay đổi thì sao?
Thay:
app_port: 8080
thành:
app_port: 8081
Chạy:
ansible-playbook playbook.yml
Template thay đổi:
proxy_pass http://127.0.0.1:8080;
thành:
proxy_pass http://127.0.0.1:8081;
Nhưng Nginx vẫn đang chạy với configuration cũ.
Chúng ta cần:
systemctl reload nginx
Đây chính là lúc Handler xuất hiện.
16. Thêm Handler
Sửa:
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
owner: root
group: root
mode: "0644"
notify: Reload nginx
Thêm:
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Full Playbook:
---
- name: Configure nginx
hosts: webservers
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
owner: root
group: root
mode: "0644"
notify: Reload nginx
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: started
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
17. Flow mới
Bây giờ:
Template
│
▼
Configuration
│
┌──────┴──────┐
│ │
Changed Unchanged
│ │
▼ ▼
notify Nothing
│
▼
Handler: Reload
│
▼
Nginx
Đây là pattern bạn sẽ sử dụng cực kỳ nhiều trong DevOps.
18. Test Handler
Chạy:
ansible-playbook playbook.yml
Lần đầu:
TASK [Deploy nginx configuration]
changed
RUNNING HANDLER [Reload nginx]
changed
Chạy lại:
ansible-playbook playbook.yml
Lần hai:
TASK [Deploy nginx configuration]
ok
Không có:
RUNNING HANDLER [Reload nginx]
Đây chính là điều chúng ta muốn.
19. Thay đổi Configuration
Sửa:
app_port: 8081
Chạy:
ansible-playbook playbook.yml
Bạn sẽ thấy:
TASK [Deploy nginx configuration]
changed
RUNNING HANDLER [Reload nginx]
changed
Vì:
Template
↓
Rendered output thay đổi
↓
changed = true
↓
notify
↓
Reload nginx
20. Handler chỉ chạy một lần
Giả sử Playbook có:
tasks:
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
- name: Deploy another nginx config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/nginx/conf.d/app.conf
notify: Reload nginx
Nếu cả hai thay đổi:
Task 1
↓
notify Reload nginx
Task 2
↓
notify Reload nginx
Handler không reload 2 lần.
Ansible sẽ gom notification:
Task 1 ──┐
│
Task 2 ──┼──→ Reload nginx
│
Task 3 ──┘
Handler chạy một lần ở cuối play.
Đây là một ưu điểm rất quan trọng.
21. Vì sao không dùng systemctl restart trực tiếp?
Cách không tốt:
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
- name: Restart nginx
ansible.builtin.command:
cmd: systemctl restart nginx
Vấn đề:
Playbook chạy
↓
Restart nginx
↓
Dù config có thay đổi hay không
Nếu chạy mỗi 5 phút:
00:00 → restart
00:05 → restart
00:10 → restart
00:15 → restart
Không tốt cho production.
22. Handler giải quyết vấn đề gì?
Với Handler:
Configuration changed?
│
┌───┴───┐
Yes No
│ │
▼ ▼
notify nothing
│
▼
reload
Do đó:
Handler giúp automation phản ứng với thay đổi thay vì thực hiện hành động một cách mù quáng.
23. state: started và state: restarted
Hai trạng thái này rất khác nhau.
started
state: started
Ý nghĩa:
Service phải đang chạy.
Nếu đang chạy:
Không làm gì
Nếu stopped:
Start
restarted
state: restarted
Ý nghĩa:
Restart service.
Do đó không nên dùng:
state: restarted
một cách tùy tiện.
24. reload vs restart
Restart
Stop
↓
Start
Có thể gây gián đoạn.
Reload
Process đang chạy
↓
Đọc configuration mới
↓
Tiếp tục phục vụ
Với những service hỗ trợ reload, thường nên ưu tiên:
reload
khi chỉ thay đổi configuration.
Ví dụ:
state: reloaded
25. Khi nào cần Restart?
Không phải mọi thay đổi đều có thể reload.
Ví dụ:
Configuration
↓
Service hỗ trợ reload?
│
┌───┴────┐
Yes No
│ │
▼ ▼
Reload Restart
Do đó DevOps Engineer cần biết lifecycle của service đang quản lý.
26. Handler với nhiều service
Ví dụ application:
- name: Deploy application config
ansible.builtin.template:
src: application.yml.j2
dest: /opt/todo/application.yml
notify: Restart todo application
Handler:
handlers:
- name: Restart todo application
ansible.builtin.service:
name: todo
state: restarted
27. Handler cho Docker Compose
Ví dụ:
- name: Deploy compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: /opt/todo/docker-compose.yml
notify: Restart todo application
Handler:
handlers:
- name: Restart todo application
community.docker.docker_compose_v2:
project_src: /opt/todo
state: present
recreate: auto
Pattern:
docker-compose.yml
↓
changed
↓
notify
↓
Docker Compose
↓
Recreate required containers
28. Handler Naming
Tên Handler nên mô tả hành động:
Tốt:
notify: Reload nginx
notify: Restart todo application
notify: Reload systemd
Không nên:
notify: Handler 1
hoặc:
notify: Something
Tên rõ ràng giúp debug dễ hơn.
29. Handler và listen
Khi project lớn, nhiều task có thể cần cùng một hành động.
Ví dụ:
notify: Restart application
Có thể dùng:
handlers:
- name: Restart application
ansible.builtin.service:
name: todo
state: restarted
Ngoài ra Ansible có cơ chế listen:
handlers:
- name: Restart application service
ansible.builtin.service:
name: todo
state: restarted
listen: Restart application
- name: Clear application cache
ansible.builtin.command:
cmd: /opt/todo/clear-cache.sh
listen: Restart application
Task:
notify: Restart application
sẽ kích hoạt các Handler cùng listen:
notify
│
▼
Restart application
│
├── Restart service
│
└── Clear cache
Phần này khá hữu ích khi project lớn.
30. Handler chạy khi nào?
Mặc định Handler chạy:
Sau khi tất cả Tasks trong Play hoàn thành.
Ví dụ:
Task 1
Task 2
Task 3
Task 4
Task 5
│
▼
Handlers
Điều này rất quan trọng.
Không phải:
Task 1
↓
Handler
↓
Task 2
↓
Handler
31. meta: flush_handlers
Đôi khi chúng ta muốn Handler chạy ngay.
Có thể sử dụng:
- name: Flush handlers
ansible.builtin.meta: flush_handlers
Ví dụ:
tasks:
- name: Deploy configuration
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.conf
notify: Restart application
- name: Flush handlers
ansible.builtin.meta: flush_handlers
- name: Run application health check
ansible.builtin.uri:
url: http://localhost:8080/health
Flow:
Deploy config
↓
notify
↓
flush_handlers
↓
Restart
↓
Health check
Nếu không flush_handlers, Handler thường sẽ đợi đến cuối Play.
32. Tại sao flush_handlers hữu ích?
Scenario:
Deploy configuration
↓
Restart application
↓
Health check
Nếu Handler đợi đến cuối:
Deploy config
↓
Health check
↓
Restart application
Health check có thể kiểm tra configuration cũ.
Do đó:
meta: flush_handlers
cho phép chúng ta kiểm soát thời điểm Handler chạy.
33. Exercise 1 — Basic Handler
Tạo Playbook:
---
- name: Handler exercise
hosts: webservers
become: true
tasks:
- name: Deploy configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
notify: Reload nginx
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Yêu cầu:
Lần 1:
changed → handler chạy
Lần 2:
ok → handler không chạy
34. Exercise 2 — Trigger Handler
Thay đổi:
app_port: 8080
thành:
app_port: 9090
Chạy:
ansible-playbook playbook.yml
Xác nhận:
Template changed
↓
Handler chạy
35. Exercise 3 — Không Trigger Handler
Không thay đổi bất kỳ Variable nào.
Chạy:
ansible-playbook playbook.yml
Yêu cầu:
changed=0
và:
Handler không chạy
36. Exercise 4 — Hai Template, Một Handler
Tạo:
templates/
├── nginx.conf.j2
└── security.conf.j2
Tasks:
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
- name: Deploy security config
ansible.builtin.template:
src: security.conf.j2
dest: /etc/nginx/conf.d/security.conf
notify: Reload nginx
Chỉ cần một:
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Thử thay đổi cả hai file.
Kiểm tra Handler chỉ chạy một lần.
37. Exercise 5 — Detect Idempotency
Chạy Playbook liên tiếp 3 lần:
ansible-playbook playbook.yml
ansible-playbook playbook.yml
ansible-playbook playbook.yml
Ghi lại:
Run 1:
changed = ?
Run 2:
changed = ?
Run 3:
changed = ?
Mục tiêu:
Run 1 → changed > 0
Run 2 → changed = 0
Run 3 → changed = 0
38. Exercise 6 — Intentional Failure
Thử sửa Template thành:
server {
listen {{ nginx_port }}
server_name {{ server_name }};
}
Bạn đã cố tình bỏ:
;
Chạy:
ansible-playbook playbook.yml
Sau đó kiểm tra:
nginx -t
Mục tiêu của bài này là hiểu:
Template render thành công không đồng nghĩa configuration hợp lệ.
Đây là một insight rất quan trọng trong production.
39. Exercise 7 — Validate Template
template module hỗ trợ validate.
Ví dụ với Nginx:
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
validate: "nginx -t -c %s"
notify: Reload nginx
Flow:
Template
↓
Render temporary file
↓
nginx -t
│
├── FAIL → Không deploy
│
└── OK
↓
Deploy
↓
notify
↓
Reload
Đây là một pattern rất đáng học.
40. Tại sao validate quan trọng?
Không có validate:
Template
↓
Configuration lỗi
↓
Deploy
↓
Reload
↓
Nginx fail
Có validate:
Template
↓
Validate
│
├── FAIL → Stop
│
└── PASS
↓
Deploy
↓
Reload
Tư duy production:
Validate trước khi đưa configuration vào service.
41. Exercise 8 — Production Nginx Deployment
Xây dựng Playbook:
Template
↓
Validate nginx configuration
↓
Deploy
↓
Notify
↓
Reload nginx
Yêu cầu:
Nếu configuration đúng:
Template → changed
Validate → success
Reload → success
Nếu configuration sai:
Template
↓
Validate FAILED
↓
Không reload nginx
42. Idempotency với Package
Ví dụ:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
Nếu chưa có:
changed=1
Nếu đã có:
changed=0
Không nên:
- name: Install nginx
ansible.builtin.command:
cmd: apt install -y nginx
Vì bạn đang nói với Ansible:
"Hãy chạy command này."
thay vì:
"Hãy đảm bảo nginx tồn tại."
43. Imperative vs Declarative
Đây là một khái niệm rất quan trọng.
Imperative
Run this command
Run this command
Run this command
Ví dụ:
apt install nginx
systemctl restart nginx
Bạn mô tả cách làm.
Declarative
Nginx phải được cài đặt
Nginx phải đang chạy
Configuration phải như thế này
Ansible tự quyết định cần thay đổi gì.
Bạn mô tả trạng thái mong muốn.
44. Ansible và Desired State
Có thể hình dung:
Desired State
│
▼
┌──────────────┐
│ Ansible │
└──────┬───────┘
│
▼
Current State
│
┌──────┴──────┐
│ │
Same Different
│ │
▼ ▼
Nothing Change
Đây chính là tư duy nền tảng của Infrastructure as Code.
45. Những module thường có tính Idempotent
Các module Ansible chính thức thường được thiết kế để quản lý trạng thái.
Ví dụ:
apt
yum
dnf
package
service
systemd
file
copy
template
user
group
lineinfile
blockinfile
Ví dụ:
- name: Create deploy user
ansible.builtin.user:
name: deploy
state: present
Chạy nhiều lần:
Lần 1 → tạo user
Lần 2 → không tạo lại
Lần 3 → không tạo lại
46. Những module cần cẩn thận
Ví dụ:
ansible.builtin.command
ansible.builtin.shell
Ansible không phải lúc nào cũng biết command đó đã được thực hiện hay chưa.
Ví dụ:
- name: Run migration
ansible.builtin.command:
cmd: ./migrate.sh
Mỗi lần Playbook chạy:
migrate.sh
migrate.sh
migrate.sh
Có thể gây vấn đề.
47. Làm command idempotent hơn
Ví dụ sử dụng:
creates:
- name: Create initialization file
ansible.builtin.command:
cmd: touch /opt/app/initialized
creates: /opt/app/initialized
Nếu file chưa tồn tại:
Run command
Nếu đã tồn tại:
Skip
Flow:
File exists?
│
┌─┴─┐
Yes No
│ │
Skip Run
48. changed_when
Một kỹ thuật nâng cao hơn:
- name: Check application
ansible.builtin.command:
cmd: /opt/todo/check.sh
changed_when: false
Nói với Ansible:
Task này chỉ kiểm tra, không làm thay đổi hệ thống.
Do đó:
changed=0
49. failed_when
Tương tự:
- name: Run health check
ansible.builtin.command:
cmd: /opt/todo/health-check.sh
register: health_check
failed_when: health_check.rc != 0
Điều này cho phép kiểm soát chính xác:
Task result
↓
failed_when
↓
Success / Failure
Đây là kỹ thuật sẽ rất hữu ích khi làm automation phức tạp.
50. Tư duy Senior #1 — Đừng restart service nếu không cần
Một nguyên tắc rất quan trọng:
Configuration changed
↓
Service needs reload/restart
Không phải:
Playbook chạy
↓
Restart service
Đây là khác biệt giữa:
Script automation
và:
Infrastructure automation
51. Tư duy Senior #2 — Handler biểu diễn Dependency
Ví dụ:
nginx.conf
│
│ changed
▼
nginx reload
Ta có dependency:
Configuration
↓
Service
Hoặc:
application.yml
↓
Application restart
Hoặc:
sshd_config
↓
SSH reload
Handler giúp mô hình hóa dependency này.
52. Tư duy Senior #3 — Idempotency giúp chạy automation an toàn
Trong production, automation có thể chạy:
Deployment
Configuration management
CI/CD
Scheduled job
Disaster recovery
Server rebuild
Nếu Playbook không idempotent:
Run
↓
Unexpected change
↓
Service restart
↓
Downtime
Nếu idempotent:
Run
↓
Compare state
↓
Only required changes
Automation trở nên đáng tin cậy hơn rất nhiều.
53. Tư duy Senior #4 — "No change" là một kết quả tốt
Người mới thường nghĩ:
changed=0
là:
"Ansible không làm gì."
Nhưng trong Infrastructure as Code:
changed=0
có thể là kết quả hoàn toàn chính xác.
Nó có nghĩa:
Infrastructure đã ở đúng trạng thái mong muốn.
54. Tư duy Senior #5 — Validate trước khi Reload
Một deployment tốt:
Generate
↓
Validate
↓
Deploy
↓
Reload
↓
Health Check
Không nên:
Generate
↓
Deploy
↓
Reload
↓
Oops!
Pattern này sẽ trở thành nền tảng cho các bài:
Application Deployment
Production Provisioning
CI/CD
sau này.
55. Mini Production Project
Bây giờ hãy kết hợp toàn bộ kiến thức Lab 1 → Lab 7.
Yêu cầu:
Ansible
│
▼
Install Nginx
│
▼
Deploy Template
│
▼
Validate
│
▼
Notify Handler
│
▼
Reload Nginx
Variables:
app_name: todo
app_host: 127.0.0.1
app_port: 8080
server_name: todo.example.com
nginx_port: 80
Template:
server {
listen {{ nginx_port }};
server_name {{ server_name }};
location / {
proxy_pass http://{{ app_host }}:{{ app_port }};
}
}
Task:
- name: Deploy nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/todo.conf
validate: "nginx -t -c %s"
notify: Reload nginx
Handler:
handlers:
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
56. Test Matrix
Hãy test ít nhất 5 scenario.
| Scenario | Template | Handler |
|---|---|---|
| Server mới | Changed | Chạy |
| Chạy lại Playbook | OK | Không chạy |
Đổi app_port |
Changed | Chạy |
Đổi server_name |
Changed | Chạy |
| Template lỗi | Fail validation | Không reload |
Nếu toàn bộ đều hoạt động đúng, bạn đã thực sự hiểu Lab 7.
57. Debug Handler
Khi Handler không chạy, kiểm tra:
1. Task có changed không?
changed: [web-01]
Nếu:
ok: [web-01]
thì Handler sẽ không được trigger.
2. Có notify không?
notify: Reload nginx
3. Tên có khớp không?
notify: Reload nginx
và:
- name: Reload nginx
phải giống nhau.
4. Handler có nằm trong Play không?
handlers:
phải cùng cấp với:
tasks:
58. Debug bằng Verbose
Có thể chạy:
ansible-playbook playbook.yml -v
hoặc:
ansible-playbook playbook.yml -vv
Nếu cần sâu hơn:
ansible-playbook playbook.yml -vvv
Nhưng không nên mặc định dùng -vvv mọi lúc vì output rất dài.
59. Kiểm tra Check Mode
Chạy:
ansible-playbook playbook.yml --check --diff
Bạn có thể thấy:
- proxy_pass http://127.0.0.1:8080;
+ proxy_pass http://127.0.0.1:8081;
Đây là một workflow rất tốt trước khi thay đổi Production.
60. Checklist hoàn thành Lab 7
Người học cần hiểu:
- [ ] Idempotency là gì.
- [ ] Desired State là gì.
- [ ]
changednghĩa là gì. - [ ]
oknghĩa là gì. - [ ] Handler là gì.
- [ ]
notifyhoạt động thế nào. - [ ] Handler chỉ chạy khi task thay đổi.
- [ ] Handler thường chạy cuối Play.
- [ ] Một Handler có thể được notify bởi nhiều Task.
- [ ] Nhiều notify có thể được gom thành một lần Handler.
- [ ]
meta: flush_handlers. - [ ]
state: started. - [ ]
state: restarted. - [ ]
state: reloaded. - [ ]
validate. - [ ]
changed_when. - [ ]
failed_when. - [ ]
creates. - [ ] Vì sao
shell/commandcần cẩn thận với idempotency. - [ ] Phân biệt imperative và declarative.
- [ ] Biết kiểm tra
--check --diff. - [ ] Biết debug khi Handler không chạy.
61. Kiến thức sau Lab 7
Đến đây người học đã có một nền tảng Ansible khá tốt:
Lab 1
Ansible Fundamentals
│
▼
Lab 2
Inventory & SSH
│
▼
Lab 3
Ad-Hoc Commands
│
▼
Lab 4
Playbook
│
▼
Lab 5
Variables & Facts
│
▼
Lab 6
Jinja2 Templates
│
▼
Lab 7
Handlers & Idempotency
Quan trọng hơn, họ đã bắt đầu chuyển tư duy từ:
"Chạy một loạt command trên server"
sang:
"Đảm bảo server luôn đạt Desired State"
Đây chính là tư duy cốt lõi của Infrastructure as Code.
62. Bài tập tổng hợp cuối Lab
🎯 Production Nginx Automation
Hãy xây dựng một Playbook có khả năng:
Ansible
│
▼
Install Nginx
│
▼
Create directories
│
▼
Deploy Template
│
▼
Validate
│
┌──────┴──────┐
│ │
FAIL PASS
│ │
▼ ▼
STOP Deploy
│
▼
Notify
│
▼
Reload Nginx
Yêu cầu:
-
Nginx phải được cài đặt.
-
Nginx phải luôn ở trạng thái
started. -
Configuration được tạo bằng Jinja2.
-
Configuration phải được validate bằng
nginx -t. -
Configuration thay đổi → reload Nginx.
-
Configuration không thay đổi → không reload.
-
Chạy Playbook 3 lần liên tiếp phải đạt:
Run 1 → changed > 0 Run 2 → changed = 0 Run 3 → changed = 0 -
Nếu cố tình làm configuration sai:
validation fail ↓ không reload nginx
Kết quả mong muốn
Sau Lab 7, người học không chỉ biết viết Handler, mà phải hiểu được nguyên tắc:
Ansible không nên chỉ "thực thi lệnh". Ansible phải xác định trạng thái hiện tại, so sánh với trạng thái mong muốn và chỉ thực hiện thay đổi khi thực sự cần thiết.
Đây là nền tảng rất quan trọng trước khi bước sang Lab 8 — Ansible Roles, nơi toàn bộ các Task, Template, Handler và Variables sẽ được tổ chức thành cấu trúc có thể tái sử dụng ở quy mô lớn.
All rights reserved