notify
機制,實現條件式服務重啟想像一下這個情況:你管理著 50 台 web server,每次更新 nginx 設定檔後都要重啟服務。
如果用傳統方式,不管設定檔有沒有真的改變,你都會重啟所有的 nginx,這樣:
白話來說 Handlers 就像個煙霧偵測器,只有在偵測到煙霧時才會觸發警報,沒事就不會亂響。
---
- name: Update nginx config and restart if needed
hosts: web
become: yes
tasks:
- name: Copy new nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
backup: yes
notify: Restart nginx
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
執行邏輯:
💡Tips:這就是 Ansible 冪等性,只有真正被改變時才會執行任務。
---
- name: Multiple tasks with handlers demo
hosts: web
become: yes
tasks:
- name: Update nginx config
copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify:
- Restart nginx
- Check nginx status
- name: Update Rails app config
template:
src: application.yml.j2
dest: /var/www/myapp/config/application.yml
notify: Restart puma
- name: Some other task
debug:
msg: "This runs before any handlers"
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
- name: Restart puma
systemd:
name: puma
state: restarted
- name: Check nginx status
command: nginx -t
listen: "Check nginx status" # 可以用 listen 給 handler 別名
執行順序:
- name: Update config only for production
template:
src: app.conf.j2
dest: /etc/app/app.conf
notify: Restart application
when: environment == "production"
- name: Update development config
copy:
src: app-dev.conf
dest: /etc/app/app.conf
notify: Reload application # 開發環境只需 reload
when: environment == "development"
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
notify: Verify nginx # Handler 可以觸發其他 Handler
- name: Verify nginx
uri:
url: http://localhost
method: GET
status_code: 200
tasks:
- name: Update critical config
copy:
src: critical.conf
dest: /etc/app/critical.conf
notify: Restart application
- name: Force handlers to run now
meta: flush_handlers # 立即執行所有 pending 的 handlers
- name: Run health check after restart
uri:
url: http://localhost/health
status_code: 200
---
- name: Deploy web application with smart restart
hosts: web
become: yes
vars:
app_version: "1.2.3"
tasks:
- name: Update application code
unarchive:
src: "app-{{ app_version }}.tar.gz"
dest: /var/www/html
remote_src: no
notify:
- Restart puma
- Precompile assets
- name: Update nginx virtual host
template:
src: nginx-vhost.j2
dest: /etc/nginx/sites-available/app
notify:
- Reload nginx
- Test nginx config
- name: Update database schema
command: rails db:migrate
args:
chdir: /var/www/html
environment:
RAILS_ENV: production
register: migration_result
changed_when: "'migrated' in migration_result.stdout"
notify: Clear rails cache
handlers:
- name: Test nginx config
command: nginx -t
- name: Reload nginx
service:
name: nginx
state: reloaded # 可以多多善用 nginx reload 特性,避免使用 restart
- name: Restart puma
systemd:
name: puma
state: restarted
- name: Precompile assets
command: rails assets:precompile
args:
chdir: /var/www/html
environment:
RAILS_ENV: production
- name: Clear rails cache
command: rails cache:clear
args:
chdir: /var/www/html
environment:
RAILS_ENV: production
---
- name: Docker application deployment
hosts: app
become: yes
tasks:
- name: Update docker-compose config
template:
src: docker-compose.yml.j2
dest: /opt/myapp/docker-compose.yml
backup: yes
notify:
- Validate compose config
- Restart docker services
- name: Update application environment
template:
src: app.env.j2
dest: /opt/myapp/.env
notify: Restart app container
no_log: yes # 不要在 log 中顯示敏感環境變數
- name: Update nginx proxy config
template:
src: nginx.conf.j2
dest: /opt/myapp/nginx/nginx.conf
notify:
- Test nginx config
- Restart nginx container
handlers:
- name: Validate compose config
command: docker-compose -f /opt/myapp/docker-compose.yml config
register: compose_config_check
failed_when: compose_config_check.rc != 0
- name: Test nginx config
command: docker exec myapp_nginx nginx -t
register: nginx_config_check
failed_when: nginx_config_check.rc != 0
- name: Restart docker services
command: docker-compose -f /opt/myapp/docker-compose.yml up -d --force-recreate
args:
chdir: /opt/myapp
- name: Restart app container
command: docker-compose -f /opt/myapp/docker-compose.yml restart app
args:
chdir: /opt/myapp
- name: Restart nginx container
command: docker-compose -f /opt/myapp/docker-compose.yml restart nginx
args:
chdir: /opt/myapp
方法一:使用 debug handler
handlers:
- name: Debug notification
debug:
msg: "Handler was triggered! Config changed."
- name: Restart nginx
service:
name: nginx
state: restarted
方法二:查看 Ansible 輸出
# 使用 -v 參數可以看到更多詳細資訊
ansible-playbook -i inventory deploy.yml -v
# 輸出範例:
# RUNNING HANDLER [Restart nginx] ****
# changed: [web01]
方法三:檢查服務狀態變化
- name: Get nginx process start time before
shell: ps -o lstart= -p $(pgrep nginx | head -1)
register: nginx_start_before
# ... 你的 tasks 和 handlers ...
- name: Verify nginx was restarted
shell: ps -o lstart= -p $(pgrep nginx | head -1)
register: nginx_start_after
failed_when: nginx_start_before.stdout == nginx_start_after.stdout
when: config_changed is defined
# 1. 強制觸發 handler(測試用)
- name: Force handler trigger
command: /bin/true
notify: Restart nginx
changed_when: true # 強制標記為 changed
# 2. 條件式 handler 執行
- name: Conditional restart
service:
name: nginx
state: restarted
when: force_restart | default(false)
listen: "Restart nginx"
# 3. Handler 執行狀態檢查
- name: Check if service is running after handler
service:
name: nginx
state: started
listen: "Verify nginx running"
# 錯誤:大小寫不符
tasks:
- copy:
src: config.conf
dest: /etc/config.conf
notify: restart nginx # 小寫
handlers:
- name: Restart Nginx # 大寫開頭
service:
name: nginx
state: restarted
# 這個 handler 永遠不會被觸發!
# 如果 copy 失敗,handler 不會執行
- name: Copy config
copy:
src: nonexistent.conf # 檔案不存在會失敗
dest: /etc/app.conf
notify: Restart service
# 解決方案:加上錯誤處理
ignore_errors: yes
# 或使用 block/rescue
# 問題:想要立即重啟服務再繼續
- name: Update config
copy:
src: app.conf
dest: /etc/app.conf
notify: Restart app
- name: Test application
uri:
url: http://localhost/health
# 這時 app 還沒重啟!會失敗
# 解決方案:使用 flush_handlers
- name: Update config
copy:
src: app.conf
dest: /etc/app.conf
notify: Restart app
- meta: flush_handlers # 強制執行 handlers
- name: Test application
uri:
url: http://localhost/health
# 現在 app 已經重啟了
# 錯誤認知:多個 task 觸發同一個 handler 會執行多次
tasks:
- copy:
src: file1.conf
dest: /etc/file1.conf
notify: Restart service
- copy:
src: file2.conf
dest: /etc/file2.conf
notify: Restart service
# 實際上:不管幾個 task 觸發,handler 只會執行一次
建立一個 Playbook,實現以下功能:
提示:
# 你的 handlers 應該包含:
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
- name: Verify nginx
uri:
url: http://localhost
status_code: 200
建立一個各位常用的應用(ex: Rails、Laravel、Django)環境更新的 Playbook:
挑戰要求:
flush_handlers
確保服務重啟順序設計一個零停機部署流程:
serial: 1
逐台更新# 提示架構:
- name: Rolling deployment
hosts: web
serial: 1 # 一次只處理一台
tasks:
# 從負載均衡器移除
- name: Remove from load balancer
# ...
# 更新配置
- name: Update config
# ...
notify: Restart service
# 等待 handler 執行
- meta: flush_handlers
# 健康檢查
- name: Health check
# ...
# 加回負載均衡器
- name: Add back to load balancer
# ...
# 不要一次重啟所有服務
- name: Rolling restart
hosts: web
serial: "30%" # 一次重啟 30% 的機器
tasks:
- name: Update config
template:
src: app.conf.j2
dest: /etc/app.conf
notify: Restart app
handlers:
- name: Restart application
service:
name: myapp
state: restarted
notify: Wait for application
- name: Wait for application
wait_for:
port: 8080
host: "{{ inventory_hostname }}"
delay: 5
timeout: 60
notify: Verify application health
- name: Verify application health
uri:
url: "http://{{ inventory_hostname }}:8080/health"
method: GET
status_code: 200
寫程式都會有 exception 了,自動化任務想必也會有,所以明天我們來學學怎麼做好錯誤處理吧!