Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
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:

  1. Stop service trước
  2. Update database schema
  3. 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...